JabChapter 9

From WikiContent

(Difference between revisions)
Jump to: navigation, search
Current revision (15:35, 8 October 2009) (edit) (undo)
 
(25 intermediate revisions not shown.)
Line 1: Line 1:
 +
[http://fusedlo.ojaru.jp/topic-465.html kid volunteer work for animal shelters in nj] [http://s1.shard.jp/dentage/article368.html yakima jobs] [http://voutoze.strefa.pl/lindy-hop.html lindy hop] [http://qedelkomf.nengu.jp/article327.html anzac day trading times] [http://www.weeaseta.orgs.hk/article21.html personal loans leeds] [http://jatoqas.oueb.eu/index.html links] [http://zvlraac.your2000.net/bahamas-five-star.html bahamas five star hotels] [http://s1.shard.jp/ladyloulou/comment452.html natural gas stocks] [http://www.weeaseta.orgs.hk/article216.html gratis dating no credit] [http://bugalpa.isuisse.com/text320.html age of mythology the titans no cd crack] [http://voureelfv.ibelgique.com/219.html activemark crack] [http://careqelox.o-oku.jp/resource3.html indiana high school athletic association football rules] [http://s1.shard.jp/ladyloulou/comment306.html panty job compilation] [http://s1.shard.jp/corecyclone/wsfs-online.html wsfs online banking] [http://fusedlo.ojaru.jp/topic-337.html presidential campaign spend way too much money] [http://s1.shard.jp/cest1avie/text-200.html cigna healthcare fee schedule] [http://page.freett.com/theomacy/index.html url] [http://bugalpa.isuisse.com/text455.html phone student teacher video] [http://koongetlq.mukade.jp/new16.html philadelphia eagles trade] [http://page.freett.com/xgratx/topic-415.html black bitchs] [http://koongetlq.mukade.jp/new205.html club penguin money maker how to download it] [http://fautayt.iquebec.com/news-474.html pamerla anderson videos] [http://listlisalove.finito-web.com/index.html sitemap] [http://wicaencxi.suppa.jp/article483.html free printable gift card] [http://rofirelc.321webs.com/article252.html caesars palace hotel deals] [http://delvous.tonosama.jp/text-388.html tamil nadu tourist board] [http://fusedlo.ojaru.jp/topic-23.html home down payment loan] [http://insanofangirl.finito-web.com/article479.html uk bank holiday] [http://fautayt.iquebec.com/news-267.html pooh bear video] [http://rofirelc.321webs.com/article234.html buying ex rental cars] [http://s1.shard.jp/cest1avie/text-202.html advance tubo merida yucatan] [http://tito999.finito-web.com/article1.html free interracial blow jobs] [http://yulcha1106.finito-web.com/new376.html how to start a limo business] [http://page.freett.com/themotion/comment-130.html foo fighters best of you lyrics] [http://bugalpa.isuisse.com/text272.html perry mason videos] [http://s1.shard.jp/corecyclone/cum-pot-obtine.html cum pot obtine un credit daca sunt rau] [http://deltrmxre.ojaru.jp/index.html sitemap] [http://deltrmxre.ojaru.jp/comment172.html application letter healthcare] [http://fautayt.iquebec.com/news-417.html album king t.i torrent] [http://delnrtrvo.strefa.pl/caballeros-del.html caballeros del zodiaco] [http://eltdelfar.isuisse.com/text-127.html pam and tommy lee video free] [http://rofirelc.321webs.com/index.html index] [http://delnrtrvo.strefa.pl/index.html domain] [http://listlisalove.finito-web.com/comment-219.html master clock] [http://allibaswb.321webs.com/page-347.html bozeman american west airlines] [http://www.weeaseta.orgs.hk/article483.html interior design master bedroom] [http://deltrmxre.ojaru.jp/comment427.html elmo birthday cards] [http://zvlraac.your2000.net/best-deal-for.html best deal for hotels in orlando] [http://listlisalove.finito-web.com/comment-125.html public health job] [http://roclorocz.ibelgique.com/comment-70.html pinback videos]
 +
{{Content Programming Jabber}}
=Groupchat, : Components, and Event Models=
=Groupchat, : Components, and Event Models=
<br>By now, you should have a
<br>By now, you should have a
Line 9: Line 11:
conference room and alerts us to words and phrases that we want it to
conference room and alerts us to words and phrases that we want it to
listen for. There are two popular conference protocols, as mentioned in
listen for. There are two popular conference protocols, as mentioned in
-
Section 6.2.6—the presence-based Groupchat protocol, and the
+
Section 6.2.6 the presence-based Groupchat protocol, and the
<tt>jabber:iq:conference</tt>-based Conference protocol. The assistant
<tt>jabber:iq:conference</tt>-based Conference protocol. The assistant
recipe, a foray into the world of 'bots, takes a look at the original
recipe, a foray into the world of 'bots, takes a look at the original
Line 19: Line 21:
where this melding needs to happen. The first is a homage to the Trojan
where this melding needs to happen. The first is a homage to the Trojan
Room Coffee Machine (http://www.cl.cam.ac.uk/coffee/coffee.html), where
Room Coffee Machine (http://www.cl.cam.ac.uk/coffee/coffee.html), where
-
we give life, or at least presence, to a coffeepot, using LEGO®
+
we give life, or at least presence, to a coffeepot, using LEGO
-
MINDSTORMS™. The second is a Tk-based RSS headline viewer. Both the
+
MINDSTORMS. The second is a Tk-based RSS headline viewer. Both the
coffeepot and the Tk programming library have event loops of their own.
coffeepot and the Tk programming library have event loops of their own.
With the coffeepot, we need to have a loop that polls the coffeepot's
With the coffeepot, we need to have a loop that polls the coffeepot's
Line 44: Line 46:
between programming a client and programming a component in this chapter
between programming a client and programming a component in this chapter
and build a complete component that can be queried and interacted with
and build a complete component that can be queried and interacted with
-
using the third of Jabber's building blocks—the <tt>&lt;iq/&gt;</tt>
+
using the third of Jabber's building blocks the <tt>&lt;iq/&gt;</tt>
element.
element.
Line 87: Line 89:
becoming a conference user identified by a nickname that is chosen upon
becoming a conference user identified by a nickname that is chosen upon
entering that room. Nicknames are generally used in conference rooms to
entering that room. Nicknames are generally used in conference rooms to
-
provide a modicum of privacy—it is assumed that by default you don't
+
provide a modicum of privacy it is assumed that by default you don't
want to let the other conference room members know your real JID.
want to let the other conference room members know your real JID.
Line 93: Line 95:
interaction: a simple one that provides basic features and a more
interaction: a simple one that provides basic features and a more
complex one that provides the basic features plus facilities such as
complex one that provides the basic features plus facilities such as
-
password-protected rooms and room descriptions—Groupchat and Conference.
+
password-protected rooms and room descriptions Groupchat and Conference.
Line 243: Line 245:
: <tt>error</tt> and comes from the artificial JID constructed in the
: <tt>error</tt> and comes from the artificial JID constructed in the
: room entry attempt. The element is addressed to <tt>qmacro</tt>'s real
: room entry attempt. The element is addressed to <tt>qmacro</tt>'s real
-
: JID, of course—<tt>qmacro@jabber.com/jarltk</tt>—as otherwise it
+
: JID, of course<tt>qmacro@jabber.com/jarltk</tt> as otherwise it
: wouldn't reach him. : The error code 409 and text "Conflict" tells
: wouldn't reach him. : The error code 409 and text "Conflict" tells
: <tt>qmacro</tt> that the nickname conflicted with one already in the
: <tt>qmacro</tt> that the nickname conflicted with one already in the
Line 257: Line 259:
</code>
</code>
-
: This time, there is no conflict—no other user is in the room "cellar"
+
: This time, there is no conflict
-
: with that nickname—and the conference component registers the entry.
+
-
: It does this by sending <tt>qmacro</tt> the presence of all the room
+
-
: occupants, including that of himself:
+
-
<code>RECV: &lt;presence to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/flash'/&gt;
+
-
 
+
-
RECV: &lt;presence to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/roscoe'/&gt;
+
-
 
+
-
RECV: &lt;presence to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/deejay'/&gt;
+
-
 
+
-
</code>
+
-
: These presence elements are also sent to the other room occupants so
+
-
: they know that <tt>deejay</tt> is present.
+
-
; Conference component-generated notification
+
-
: In addition to the presence elements sent for each room occupant, a
+
-
: general roomwide message noting that someone with the nickname
+
-
: <tt>deejay</tt> just entered the room is sent out by the component as
+
-
: a <tt>type='groupchat'</tt> message to all the room occupants:
+
-
<code>RECV: &lt;message to='qmacro@jabber.com/jarltk' type='groupchat'
+
-
from='cellar@conf.merlix.dyndns.org'&gt; &lt;body&gt;deejay has become
+
-
available&lt;/body&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
: The text "has become available" used in the body of the message is
+
-
: taken directly from the ''Action Notices'' definitions, part of the
+
-
: Conferencing component instance configuration described in Section
+
-
: 4.10.3. Note that the identity of the room itself is simply a generic
+
-
: version of the JID that the room occupants use to enter:
+
-
<code>cellar@conf.merlix.dyndns.org
+
-
 
+
-
</code>
+
-
 
+
-
; Roomwide chat
+
-
: Once the user with the nickname <tt>roscoe</tt> sees someone enter the
+
-
: room, he sends a greeting, and <tt>qmacro</tt> waves back:
+
-
<code>RECV: &lt;message to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/roscoe' type='groupchat' cnu=''&gt;
+
-
&lt;body&gt;hi qmacro&lt;/body&gt; &lt;/message&gt;
+
-
 
+
-
SEND: &lt;message to='cellar@conf.merlix.dyndns.org'
+
-
type='groupchat'&gt; &lt;body&gt;/me waves to everyone&lt;/body&gt;
+
-
&lt;/message&gt;
+
-
 
+
-
</code>
+
-
: As with the notification message, each message is a
+
-
: <tt>groupchat</tt>-type message. The one received appears to come from
+
-
: <tt>cellar@conf.merlix.dyndns.org/roscoe</tt>, which is the JID
+
-
: representing the user in the room with the nickname <tt>roscoe</tt>.
+
-
: This way, <tt>roscoe</tt>'s real JID is never sent to <tt>qmacro</tt>.
+
-
: The message <tt>deejay</tt> sends is addressed to the room's identity
+
-
: <tt>cellar@conf.merlix.dyndns.org</tt>, and contains a message that
+
-
: starts with <tt>/me</tt>. This is simply a convention that is
+
-
: understood by clients that support conferencing, meant to represent an
+
-
: action and displayed thus:
+
-
<code>* deejay waves to everyone
+
-
 
+
-
</code>
+
-
 
+
-
{{Note|Ignore the <tt>cnu</tt> attribute; it's put there and used by the
+
-
component and should never make it out to the client endpoints. The
+
-
attribute name is a short name for the conference user and refers to the
+
-
internal structure that represents a conference room occupant within the
+
-
component.
+
-
 
+
-
</code>
+
-
 
+
-
; One-on-one chat
+
-
: The Conferencing component also supports a one-on-one chat mode, which
+
-
: is just like normal chat mode (where messages with the type
+
-
: <tt>chat</tt> are exchanged) except that the routing goes through the
+
-
: component. The intended recipient of a conference-routed chat message
+
-
: is identified by his room JID. So in this example:
+
-
<code>RECV: &lt;message to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/flash' type='chat'&gt;
+
-
&lt;body&gt;Is that you, qmacro?&lt;/body&gt;
+
-
&lt;thread&gt;jarl1998911094&lt;/thread&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
: the user nicknamed <tt>flash</tt> actually addressed the chat message
+
-
: to the JID:
+
-
<code>cellar@conf.merlix.dyndns.org/deejay
+
-
</code>
+
-
: which arrived at the Conferencing component (because of the hostname,
+
-
: <tt>conf.merlix.dyndns.org</tt> causes the <tt>&lt;message/&gt;</tt>
+
-
: element to be routed there), which then looked up internally who
+
-
: <tt>deejay</tt> really was (<tt>qmacro@jabber.com/jarltk</tt>) and
+
-
: sent it on. This way, the recipient of a conference-routed message
+
-
: never discovers the real JID of the sender. In all other ways, the
+
-
: actual <tt>&lt;message/&gt;</tt> element is like any other
+
-
: <tt>&lt;message/&gt;</tt> element—in this case, it contains a message
+
-
: <tt>&lt;body/&gt;</tt> and a chat <tt>&lt;thread/&gt;</tt>. (See
+
-
: Section 5.4.1 for details on the <tt>&lt;message/&gt;</tt> element.)
+
-
; Leaving the room
+
-
: In the same way that room entrance is effected by sending an
+
-
: <tt>available</tt> presence (remember, a <tt>&lt;presence/&gt;</tt>
+
-
: element without an explicit <tt>type</tt> attribute is understood to
+
-
: represent <tt>type="available'</tt>), leaving a room is achieved by
+
-
: doing the opposite:
+
-
<code>RECV: &lt;presence to='qmacro@jabber.com/jarltk'
+
-
type='unavailable' from='cellar@conf.merlix.dyndns.org/roscoe'/&gt;
+
-
 
+
-
</code>
+
-
: The people in the conference room are sent a message that
+
-
: <tt>roscoe</tt> has left the room by the <tt>unavailable</tt> presence
+
-
: packet. This is by and large for the benefit of each user's client, so
+
-
: that the room occupant list can be updated. The component also sends
+
-
: out a verbal notification, in the same way as it sends a verbal
+
-
: notification out when someone joins:
+
-
<code>RECV: &lt;message to='qmacro@jabber.com/jarltk' type='groupchat'
+
-
from='cellar@conf.merlix.dyndns.org'&gt; &lt;body&gt;roscoe has
+
-
left&lt;/body&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
: Like the join notification, the text for the leave notification ("has
+
-
: left") comes directly from the component instance configuration
+
-
: described in Section 4.10.3.
+
-
<br>
+
-
=== The Script's Scope ===
+
-
<br>The Keyword Assistant (''keyassist'') script
+
-
will be written in Python using the <tt>Jabberpy</tt> library. As
+
-
mentioned earlier, the script will perform the following tasks:
+
-
 
+
-
* Connect to a predetermined Jabber server
+
-
* Join a predetermined
+
-
conference room
+
-
* Sit there quietly, listening to the conversation
+
-
* Take simple commands from people to watch for, or stop watching for,
+
-
particular words or phrases uttered in the room
+
-
* Relay the context of those words or phrases to whomever requested them, if heard In addition
+
-
to setting the identity of the Jabber server and the conference room in
+
-
variables, we'll also need to keep track of which users ask the
+
-
assistant for words and phrases.
+
-
<br>We'll use a ''dictionary'' (''hash'' in
+
-
Perl terms), as shown in Example 9-3, because we want to manage the data
+
-
in there by key, the JID of those users that the script will be
+
-
assisting. Having a look at what this dictionary will look like during
+
-
the lifetime of this script will help us to visualize what we're trying
+
-
to achieve.
+
-
 
+
-
 
+
-
''Typical contents of the Keyword Assistant's dictionary''
+
-
 
+
-
<code>{ 'dj@gnu.pipetree.com/home': { 'http:': 1, 'ftp:': 1
+
-
},
+
-
 
+
-
'piers@jabber.org/work': { 'Perl': 1, 'Java': 1, 'SAP
+
-
R/3': 1
+
-
},
+
-
 
+
-
'cellar@conf.merlix.dyndns.org/roscoe': { 'dialback': 1
+
-
}
+
-
}
+
-
 
+
-
</code>
+
-
We can see from the contents of the dictionary in Example 9-3 that three
+
-
people have asked the script to look out for words and phrases. Two of
+
-
those people—<tt>dj</tt> and <tt>piers</tt>—have interacted with the
+
-
script directly by sending a ''normal'' (or <tt>chat</tt>)
+
-
<tt>&lt;message/&gt;</tt>. The other person, with the conference
+
-
nickname <tt>roscoe</tt>, is in the "cellar" room and has sent the
+
-
script a message routed through the Conference component in the same way
+
-
that <tt>flash</tt> sent <tt>qmacro</tt> a private message in Example
+
-
9-2: the JID of the sender belongs to (has the hostname set to) the
+
-
conference component. Technically, there's nothing to distinguish the
+
-
three JIDs here; it's just that we know from the name that
+
-
<tt>conf.merlix.dyndns.org</tt> is the name that identifies such a
+
-
component.
+
-
 
+
-
If we dissect the dictionary, we can see that:
+
-
 
+
-
* <tt>dj</tt> wants to be notified if any web or FTP URLs are mentioned.
+
-
* <tt>piers</tt> is interested in references to two of his favorite
+
-
languages and his favorite business software solution.
+
-
* <tt>roscoe</tt>
+
-
is interested in any talk about dialback. We said we'd give the script a
+
-
little bit of intelligence. This was a reference to the ability for
+
-
users to interact with the script while it runs, rather than having to
+
-
give the script a static list of words and phrases in a configuration
+
-
file. <tt>dj</tt>, <tt>piers</tt>, and <tt>roscoe</tt> have done this by
+
-
sending the script messages (directly, not within the room) with simple
+
-
keyword commands, such as:
+
-
 
+
-
: dj: "''watch http:''" script: "''ok, watching for http:''" dj:
+
-
: "''watch gopher:''" script: "''ok, watching for gopher:''" dj:
+
-
: "''watch ftp:''" script: "''ok, watching for ftp:''" dj: "''ignore
+
-
: gopher:''" script: "''ok, now ignoring gopher:''"
+
-
...
+
-
 
+
-
: piers: "''list''" script: "''watching for: Perl, Java, SAP R/3''"
+
-
...
+
-
 
+
-
: roscoe: "''stop''" script: "''ok, I've stopped watching''"
+
-
 
+
-
<br>
+
-
=== The keyassist Script ===
+
-
<br>Example 9-4 shows the ''keyassist'' script
+
-
in its entirety. The script is described in detail, step by step, in the
+
-
next section.
+
-
 
+
-
 
+
-
''The keyassist Perl script''
+
-
 
+
-
<code>import jabber from string import split, join, find import sys
+
-
 
+
-
keywords = {}
+
-
 
+
-
def addword(jid, word): if not keywords.has_key(jid): keywords[jid] = {}
+
-
keywords[jid][word] = 1
+
-
 
+
-
def delword(jid, word): if keywords.has_key(jid): if
+
-
keywords[jid].has_key(word): del keywords[jid][word]
+
-
 
+
-
def messageCB(con, msg):
+
-
 
+
-
type = msg.getType() if type == None: type = 'normal'
+
-
 
+
-
# Deal with interaction
+
-
if type == 'chat' or type == 'normal': jid = str(msg.getFrom())
+
-
 
+
-
message = split(msg.getBody(), None, 1); reply = ""
+
-
 
+
-
if message[0] == 'watch': addword(jid, message[1]) reply =
+
-
"Okay, watching for " + message[1]
+
-
 
+
-
if message[0] == 'ignore': delword(jid, message[1]) reply =
+
-
"Okay, now ignoring " + message[1]
+
-
 
+
-
if message[0] == 'list': if keywords.has_key(jid): reply =
+
-
"Watching for: " + join(keywords[jid].keys(), ", ") else: reply
+
-
= "Not watching for any keywords"
+
-
 
+
-
if message[0] == 'stop': if keywords.has_key(jid): del
+
-
keywords[jid] reply = "Okay, I've stopped watching"
+
-
 
+
-
if reply: con.send(msg.build_reply(reply))
+
-
 
+
-
 
+
-
# Scan room talk
+
-
if type == 'groupchat': message = msg.getBody()
+
-
 
+
-
for jid in keywords.keys(): for word in keywords[jid].keys(): if
+
-
find(message, word) &gt;= 0: con.send(jabber.Message(jid, word +
+
-
": " + message))
+
-
 
+
-
 
+
-
def presenceCB(con, prs):
+
-
 
+
-
# Deal with nickname conflict in room
+
-
if str(prs.getFrom()) == roomjid and prs.getType() == 'error':
+
-
prsnode = prs.asNode() error = prsnode.getTag('error') if error: if
+
-
(error.getAttr('code') == '409'): print "Cannot join room -
+
-
conflicting nickname" con.disconnect() sys.exit(0)
+
-
 
+
-
# Remove keyword list for groupchat correspondent
+
-
if prs.getType() == 'unavailable': jid = str(prs.getFrom()) if
+
-
keywords.has_key(jid): del keywords[jid]
+
-
 
+
-
Server = 'gnu.mine.nu' Username = 'kassist' Password = 'pass' Resource
+
-
= 'py'
+
-
 
+
-
Room = 'jdev' ConfServ = 'conference.jabber.org' Nick =
+
-
'kassist'
+
-
 
+
-
con = jabber.Client(host=Server) try: con.connect() except IOError, e:
+
-
print "Couldn't connect: %s" % e sys.exit(0) else: print "Connected"
+
-
 
+
-
if con.auth(Username,Password,Resource): print "Logged in as %s to
+
-
server %s" % ( Username, Server ) else: print "Problems authenticating:
+
-
", con.lastErr, con.lastErrCode sys.exit(1)
+
-
 
+
-
con.setMessageHandler(messageCB) con.setPresenceHandler(presenceCB)
+
-
 
+
-
con.send(jabber.Presence())
+
-
 
+
-
roomjid = Room + '@' + ConfServ + '/' + Nick print "Joining " + Room
+
-
con.send(jabber.Presence(to=roomjid))
+
-
 
+
-
while(1): con.process(5)
+
-
 
+
-
</code>
+
-
 
+
-
<br>
+
-
=== Dissecting the keyassist Script ===
+
-
<br>Taking ''keyassist'' step by
+
-
step, the first section is probably familiar if you've seen the previous
+
-
Python-based scripts in Section 8.1 and Section 8.3, both in Chapter 8.
+
-
 
+
-
 
+
-
<code>import jabber from string import split, join, find import sys
+
-
 
+
-
</code>
+
-
Here, all of the functions and libraries that we'll need are brought in.
+
-
We'll use the <tt>find</tt> function from the <tt>string</tt> library to
+
-
help with the keyword searching.
+
-
 
+
-
Next, we declare the dictionary. This will hold a list of the words that
+
-
the script will look for, as defined by each person, as shown in Example
+
-
9-3.
+
-
 
+
-
 
+
-
<code>keywords = {}
+
-
 
+
-
</code>
+
-
 
+
-
<br>
+
-
==== Maintaining the keyword dictionary ====
+
-
<br>To maintain this
+
-
dictionary, we will use two subroutines to add words to and remove words
+
-
from a user's word list. These subroutines are called when a command
+
-
such as ''watch'' or ''ignore'' is recognized in the callback subroutine
+
-
that handles incoming <tt>&lt;message/&gt;</tt> elements:
+
-
 
+
-
 
+
-
<code>def addword(jid, word): if not keywords.has_key(jid):
+
-
keywords[jid] = {} keywords[jid][word] = 1
+
-
 
+
-
def delword(jid, word): if keywords.has_key(jid): if
+
-
keywords[jid].has_key(word): del keywords[jid][word]
+
-
 
+
-
</code>
+
-
A string representation of the JID (in <tt>jid</tt>) of the
+
-
correspondent giving the command is passed to the subroutines along with
+
-
the word or phrase specified (in <tt>word</tt>) by the user. The
+
-
dictionary has two levels: the first level is keyed by the JID, and the
+
-
second by word or phrase. We use a dictionary, rather than an array, at
+
-
the second level simply to make removal of words and phrases easier.
+
-
 
+
-
 
+
-
<br>
+
-
==== Message callback ====
+
-
<br>Next, we define the callback to handle
+
-
incoming <tt>&lt;message/&gt;</tt> elements:
+
-
 
+
-
 
+
-
<code>def messageCB(con, msg):
+
-
 
+
-
type = msg.getType() if type == None: type = 'normal'
+
-
 
+
-
</code>
+
-
As usual, we're expecting the message callback to be passed the
+
-
connection object (in <tt>con</tt>) and the message object itself
+
-
(<tt>msg</tt>). How this callback is to proceed is determined by the
+
-
''type'' of message received. We determine the type (taken from the
+
-
<tt>&lt;message/&gt;</tt> element's <tt>type</tt> attribute) and store
+
-
it in the variable called <tt>type</tt>. Remember that if no
+
-
<tt>type</tt> attribute is present, a message type of <tt>normal</tt> is
+
-
assumed. (See Section 5.4.1.1 for details of <tt>&lt;message/&gt;</tt>
+
-
attributes.)
+
-
 
+
-
The two types of incoming messages we're expecting this script to
+
-
receive are those conveying the room's conversation—in
+
-
<tt>groupchat</tt>-type messages—and those over which the commands such
+
-
as watch and ignore are carried, which we expect in the form of
+
-
<tt>normal</tt>- or <tt>chat</tt>-type messages.
+
-
 
+
-
The first main section of the <tt>messageCB</tt> handler deals with
+
-
incoming commands:
+
-
 
+
-
 
+
-
<code> # Deal with interaction if type == 'chat' or type ==
+
-
'normal': jid = str(msg.getFrom())
+
-
 
+
-
message = split(msg.getBody(), None, 1); reply = ""
+
-
 
+
-
if message[0] == 'watch': addword(jid, message[1]) reply =
+
-
"Okay, watching for " + message[1]
+
-
 
+
-
if message[0] == 'ignore': delword(jid, message[1]) reply =
+
-
"Okay, now ignoring " + message[1]
+
-
 
+
-
if message[0] == 'list': if keywords.has_key(jid): reply =
+
-
"Watching for: " + join(keywords[jid].keys(), ", ") else: reply
+
-
= "Not watching for any keywords"
+
-
 
+
-
if message[0] == 'stop': if keywords.has_key(jid): del
+
-
keywords[jid] reply = "Okay, I've stopped watching"
+
-
 
+
-
if reply: con.send(msg.build_reply(reply))
+
-
 
+
-
 
+
-
</code>
+
-
If the <tt>&lt;message/&gt;</tt> element turns out to be of the type in
+
-
which we're expecting a potential command, we want to determine the JID
+
-
of the correspondent who sent that message. Calling the
+
-
<tt>getFrom()</tt> method will return us a JID object. What we need is
+
-
the string representation of that, which can be determined by calling
+
-
the <tt>str()</tt> function on that JID object:
+
-
 
+
-
 
+
-
<code> jid = str(msg.getFrom())
+
-
 
+
-
</code>
+
-
Then we grab the content of the message by calling the
+
-
<tt>getBody()</tt> on the <tt>msg</tt> object and split the whole thing
+
-
on the first bit of whitespace. This should be enough for us to
+
-
distinguish a command (watch, ignore, and so on) from the keywords.
+
-
After the split, the first element (index 0) in the <tt>message</tt>
+
-
array will be the command, and the second element (index 1) will be the
+
-
word or phrase, if given. At this stage, we also declare an empty reply:
+
-
 
+
-
 
+
-
<code> message = split(msg.getBody(), None, 1); reply = ""
+
-
 
+
-
</code>
+
-
Now it's time to determine if what the script was sent made sense as a
+
-
command:
+
-
 
+
-
 
+
-
<code> if message[0] == 'watch': addword(jid, message[1]) reply
+
-
= "Okay, watching for " + message[1]
+
-
 
+
-
if message[0] == 'ignore': delword(jid, message[1]) reply =
+
-
"Okay, now ignoring " + message[1]
+
-
 
+
-
if message[0] == 'list': if keywords.has_key(jid): reply =
+
-
"Watching for: " + join(keywords[jid].keys(), ", ") else: reply
+
-
= "Not watching for any keywords"
+
-
 
+
-
if message[0] == 'stop': if keywords.has_key(jid): del
+
-
keywords[jid] reply = "Okay, I've stopped watching"
+
-
 
+
-
</code>
+
-
We go through a series of checks, taking appropriate action for the
+
-
supported commands:
+
-
 
+
-
 
+
-
; watch
+
-
: Watch for a particular word or phrase.
+
-
; ignore
+
-
: Stop watching for a particular word or phrase.
+
-
; list
+
-
: List the words and phrases currently being watched.
+
-
; stop
+
-
: Stop watching altogether; remove the list of words and phrases. The
+
-
: <tt>addword()</tt> and <tt>delword()</tt> functions defined earlier
+
-
: are used here, as well as other simpler functions; one that lists the
+
-
: words and phrases for a particular JID:
+
-
 
+
-
 
+
-
<code>keywords[jid].keys()
+
-
 
+
-
</code>
+
-
and one that removes them:
+
-
 
+
-
 
+
-
<code>del keywords[jid]
+
-
 
+
-
</code>
+
-
If there was something recognizable for the script to do, we get it to
+
-
reply appropriately:
+
-
 
+
-
 
+
-
<code> if reply: con.send(msg.build_reply(reply))
+
-
 
+
-
</code>
+
-
The <tt>build_reply()</tt> function creates a reply out of a message
+
-
object by setting <tt>to</tt> to the value of the original
+
-
<tt>&lt;message/&gt;</tt> element's <tt>from</tt> attribute and
+
-
preserving the element <tt>type</tt> attribute and
+
-
<tt>&lt;thread/&gt;</tt> tag, if present. The <tt>&lt;body/&gt;</tt> of
+
-
the reply object (which is just a <tt>&lt;message/&gt;</tt> element) is
+
-
set to whatever is passed in the function call; in this case, it's the
+
-
text in the <tt>reply</tt> variable.
+
-
 
+
-
Now that we've dealt with incoming commands, we need another section in
+
-
the message callback subroutine to scan for the words and phrases. The
+
-
target texts for this scanning will be the snippets of room
+
-
conversation, which arrive at the callback in the form of
+
-
<tt>groupchat</tt>-type <tt>&lt;message/&gt;</tt> elements:
+
-
 
+
-
 
+
-
<code> # scan room talk if type == 'groupchat': message =
+
-
msg.getBody()
+
-
 
+
-
</code>
+
-
The <tt>message</tt> variable holds the string we need to scan; it's
+
-
just a case of checking for each of the words or phrases on behalf of
+
-
each of the users who have asked:
+
-
 
+
-
 
+
-
<code> for jid in keywords.keys(): for word in
+
-
keywords[jid].keys(): if find(message, word) &gt;= 0:
+
-
con.send(jabber.Message(jid, word + ": " + message))
+
-
 
+
-
</code>
+
-
If we get a hit, we construct a new <tt>Message</tt> object, passing the
+
-
JID of the person for whom the string has matched (in the <tt>jid</tt>
+
-
variable) and the notification, consisting of the word or phrase that
+
-
was found (in <tt>word</tt>) and the context in which it was found (the
+
-
sentence uttered, in <tt>message</tt>). Once found and constructed, the
+
-
<tt>&lt;message/&gt;</tt> is sent to that user. By default, the
+
-
<tt>Message</tt> constructor specifies no <tt>type</tt> attribute, so
+
-
the user is sent a "normal" message.
+
-
 
+
-
<br>
+
-
==== Presence callback ====
+
-
<br>Having dealt with the incoming
+
-
<tt>&lt;message/&gt;</tt> elements, we turn to the
+
-
<tt>&lt;presence/&gt;</tt> elements. Most of those we receive in this
+
-
conference room will be notifications from people entering and leaving
+
-
the room, as shown in Example 9-2. We want to perform housekeeping on
+
-
our <tt>keywords</tt> dictionary so the entries don't become stale. We
+
-
also want to deal with the potential problem of conflicting nicknames.
+
-
Let's look at that first.
+
-
 
+
-
We want to check for the possibility of nickname conflict problems that
+
-
may occur when we enter the room, and the chosen nickname
+
-
(<tt>flash</tt>) is already taken.
+
-
 
+
-
Remembering that a conflict notification will look something like this:
+
-
 
+
-
 
+
-
<code>&lt;presence to='qmacro@jabber.com/jarltk'
+
-
from='cellar@conf.merlix.dyndns.org/flash' type='error'&gt; &lt;error
+
-
code='409'&gt;Conflict&lt;/error&gt; &lt;/presence&gt;
+
-
</code>
+
-
we test for the receipt of a <tt>&lt;presence/&gt;</tt> element with the
+
-
following:
+
-
 
+
-
 
+
-
<code>def presenceCB(con, prs):
+
-
 
+
-
# Deal with nickname conflict in room
+
-
if str(prs.getFrom()) == roomjid and prs.getType() == 'error':
+
-
prsnode = prs.asNode() error = prsnode.getTag('error') if error: if
+
-
(error.getAttr('code') == '409'): print "Cannot join room -
+
-
conflicting nickname" con.disconnect() sys.exit(0)
+
-
 
+
-
</code>
+
-
The <tt>&lt;presence/&gt;</tt> element will appear to be sent from the
+
-
JID that we constructed for the initial room entry negotiation (in the
+
-
<tt>roomjid</tt> variable further down in the script); for example:
+
-
 
+
-
 
+
-
<code>jdev@conference.jabber.org/kassist
+
-
</code>
+
-
We compare this value to the value of the incoming
+
-
<tt>&lt;presence/&gt;</tt>'s <tt>from</tt> attribute, and also make sure
+
-
that the <tt>type</tt> attribute is set to <tt>error</tt>. If it is, we
+
-
want to extract the details from the <tt>&lt;error/&gt;</tt> tag that
+
-
will be contained as a direct child of the <tt>&lt;presence/&gt;</tt>.
+
-
 
+
-
The <tt>Jabberpy</tt> library currently doesn't offer a direct
+
-
high-level function to get at this tag from the <tt>Presence</tt> object
+
-
(in <tt>prs</tt>), but we can strip away the presence object "mantle"
+
-
and get at the underlying object, which is a neutral "node"—a Jabber
+
-
element, or XML fragment, without any preconceived ideas of what it is
+
-
(and therefore without any accompanying high-level methods such as
+
-
<tt>getBody()</tt> or <tt>setPriority()</tt>).
+
-
 
+
-
 
+
-
{{Note|If this seems a little cryptic, just think of it like this: each
+
-
of the <tt>Presence</tt>, <tt>Message</tt>, and <tt>IQ</tt> classes are
+
-
merely superclasses of the base class <tt>Protocol</tt>, which
+
-
represents elements generically.
+
-
 
+
-
</code>
+
-
The <tt>asNode()</tt> method gives us what we need—a <tt>Protocol</tt>
+
-
object representation of the <tt>&lt;presence/&gt;</tt> element. From
+
-
this we can get to the <tt>&lt;error/&gt;</tt> tag and its contents. If
+
-
we find that we do have a nickname conflict, we abort by disconnecting
+
-
from the Jabber server and ending the script.
+
-
 
+
-
The general idea is that this script will run indefinitely and notify
+
-
the users on a continuous basis, so we need to do a spot of keyword
+
-
housekeeping. No presence subscription relationships are built (mostly
+
-
to keep the script small and simple; you could adapt the mechanism from
+
-
the recipe in Section 8.3 if you wanted to make this script sensitive to
+
-
presence), so notifications will get queued up for the user if he is
+
-
offline with the use of the ''mod_offline'' module of the Jabber Session
+
-
Manager (JSM). This makes a lot of sense for the most part; however, we
+
-
still want to have the script send notifications even if the user is
+
-
offline. Additionally, a command could be sent to the script to watch
+
-
for a keyword or phrase from a user within the room. We would receive
+
-
the command from a JID like this:
+
-
 
+
-
 
+
-
<code>jdev@conference.jabber.org/nickname
+
-
</code>
+
-
This is a ''transient JID'', in that it represents a user's presence in
+
-
the ''jdev'' room for a particular session. If a word is spotted by the
+
-
script hours or days later, there's a good chance that the user has left
+
-
the room, making the JID invalid as a recipient. Although the JID is
+
-
''technically'' valid and will reach the conferencing component, there
+
-
will be no real user JID that it is paired up with. Potentially worse,
+
-
the room occupant's identity JID may be assigned to someone else at a
+
-
later stage, if the original user left, and a new user entered choosing
+
-
the same nickname the original user had chosen. See the upcoming sidebar
+
-
titled "Transient and Nonexistent JIDs" for a short discussion of the
+
-
difference between a transient JID and a nonexistent JID.
+
-
 
+
-
So as soon as we notice a user leave the room we're in, which will be
+
-
indicated through a <tt>&lt;presence/&gt;</tt> element conveying that
+
-
occupant's ''unavailability'', we should remove any watched-for words
+
-
and phrases from the dictionary:
+
-
 
+
-
 
+
-
<code> # Remove keyword list for groupchat correspondent if
+
-
prs.getType() == 'unavailable': jid = str(prs.getFrom()) if
+
-
keywords.has_key(jid): del keywords[jid]
+
-
 
+
-
</code>
+
-
As before, we obtain the string representation of the JID using the
+
-
<tt>str()</tt> function on the JID object that represents the presence
+
-
element's sender, obtained via the <tt>getFrom()</tt> method.
+
-
 
+
-
 
+
-
{{Sidebar|Transient and Nonexistent JIDsWhat happens when you send a
+
-
message to a "transient" conference room JID? Superficially, the same as
+
-
when you send one to a ''nonexistent'' JID. But there are some subtle
+
-
differences.
+
-
 
+
-
A ''transient JID'' is one that reflects a user's alternate identity in
+
-
the context of the Conferencing component. When you construct and send a
+
-
message to a conference transient JID, it goes first to the conference
+
-
component because of the hostname in the JID that identifies that
+
-
component, for example:
+
-
 
+
-
 
+
-
<code>jdev@conference.jabber.org/qmacro
+
-
</code>
+
-
The hostname <tt>conference.jabber.org</tt> is what the jabberd backbone
+
-
uses to route the element. As mentioned earlier, the Conferencing
+
-
component will relay a message to the real JID that belongs to the user
+
-
currently in a room hosted by that component.
+
-
 
+
-
While the component itself is usually persistent, the room occupants
+
-
(and so their transient JIDs) are not. When a message is sent to the JID
+
-
<tt>jdev@conference.jabber.org/qmacro</tt> and there is no room occupant
+
-
in the ''jdev'' room with the nickname <tt>qmacro</tt>, the message will
+
-
still reach its ''first'' destination—the component—but be rejected at
+
-
that stage, as shown in Example 9-5.
+
-
 
+
-
 
+
-
''A message to a nonexistent transient JID is rejected''
+
-
 
+
-
<code>SEND: &lt;message to='jdev@conference.jabber.org/qmacro'&gt;
+
-
&lt;body&gt;Hello there&lt;/body&gt; &lt;/message&gt;
+
-
 
+
-
RECV: &lt;message to='dj@gnu.mine.nu/jarl'
+
-
from='jdev@conference.jabber.org/qmacro' type='error'&gt;
+
-
&lt;body&gt;Hello there&lt;/body&gt; &lt;error code='404'&gt;Not
+
-
Found&lt;/error&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
Although the rejection—the "Not Found" error—is the same as if a message
+
-
had been sent to a JSM user that didn't exist, the difference is that
+
-
the transient user always had the ''potential'' to exist, whereas the
+
-
JSM user never did. Of course, if the JID referred to a nonexistent
+
-
Jabber server, then the error returned wouldn't be a "Not Found" error
+
-
404, but an "Unable to resolve hostname" error 502.
+
-
 
+
-
</code>
+
-
<br>
+
-
 
+
-
==== The main script ====
+
-
<br>Now that we have the subroutines and callbacks
+
-
set up, all we need to do is define the Jabber server and room
+
-
information:
+
-
 
+
-
 
+
-
<code>Server = 'gnu.mine.nu' Username = 'kassist' Password = 'pass'
+
-
Resource = 'py'
+
-
 
+
-
Room = 'jdev' ConfServ = 'conference.jabber.org' Nick =
+
-
'kassist'
+
-
 
+
-
</code>
+
-
The <tt>kassist</tt> user can be set up simply by using the reguser
+
-
script presented in Section 7.4:
+
-
 
+
-
 
+
-
<code> $ <tt>./reguser gnu.mine.nu username=kassist password=pass</tt>
+
-
[Attempt] (kassist) Successful registration $
+
-
</code>
+
-
In the same way as in previous recipes' scripts, a connection attempt is
+
-
made, followed by an authentication attempt:
+
-
 
+
-
 
+
-
<code>con = jabber.Client(host=Server,debug=0,log=0) try: con.connect()
+
-
except IOError, e: print "Couldn't connect: %s" % e sys.exit(0) else:
+
-
print "Connected"
+
-
 
+
-
if con.auth(Username,Password,Resource): print "Logged in as %s to
+
-
server %s" % ( Username, Server ) else: print "Problems authenticating:
+
-
", con.lastErr, con.lastErrCode sys.exit(1)
+
-
 
+
-
</code>
+
-
Then the message and presence callbacks <tt>messageCB()</tt> and
+
-
<tt>presenceCB()</tt> are defined to the connection object in
+
-
<tt>con</tt>:
+
-
 
+
-
 
+
-
<code>con.setMessageHandler(messageCB)
+
-
con.setPresenceHandler(presenceCB)
+
-
 
+
-
</code>
+
-
After sending initial presence, informing the JSM (and anyone who might
+
-
be subscribed to <tt>kassist</tt>'s presence) of the assistant's
+
-
availability:
+
-
 
+
-
 
+
-
<code>con.send(jabber.Presence())
+
-
 
+
-
</code>
+
-
we also construct—from the <tt>Room</tt>, <tt>ConfServ</tt>, and
+
-
<tt>Nick</tt> variables—and send the <tt>&lt;presence/&gt;</tt> element
+
-
for negotiating entry to the ''jdev'' room hosted by the Conferencing
+
-
component at <tt>conference.jabber.org</tt>:
+
-
 
+
-
 
+
-
<code>roomjid = Room + '@' + ConfServ + '/' + Nick print "Joining " +
+
-
Room con.send(jabber.Presence(to=roomjid))
+
-
 
+
-
</code>
+
-
The <tt>con.send()</tt> function will send a <tt>&lt;presence/&gt;</tt>
+
-
element that looks like this:
+
-
 
+
-
 
+
-
<code>SEND: &lt;presence to='jdev@conference.jabber.org/kassist'/&gt;
+
-
</code>
+
-
We're sending available presence to the room, to negotiate entry, but
+
-
what about the initial presence? Why do we send that too if there are no
+
-
users who will be subscribed to the <tt>kassist</tt> JID? If no initial
+
-
presence is sent, the JSM will merely store up any
+
-
<tt>&lt;message/&gt;</tt> elements destined for <tt>kassist</tt>, as it
+
-
will think the JID is offline.
+
-
 
+
-
<br>
+
-
==== The processing loop ====
+
-
<br>Once everything has been set up, we simply
+
-
need to have the script sit back and wait for incoming packets and
+
-
handle them appropriately. For this, we simply call the
+
-
<tt>process()</tt> function every 5 seconds to look for elements
+
-
arriving on the XML stream:
+
-
 
+
-
 
+
-
<code>while(1): con.process(5)
+
-
 
+
-
</code>
+
-
<br>
+
-
 
+
-
== Connecting Devices to Jabber ==
+
-
<br>LEGO MINDSTORMS. What a great reason
+
-
to dig out that box of LEGO bricks you haven't touched in years. When I
+
-
found out that LEGO was bringing out a programmable brick, the
+
-
RCX,<ref>Contrary to popular belief, "RCX" stands for "Robotic Command
+
-
Explorer," according to LEGO's official MINDSTORMS page. </ref>, I went
+
-
to my favorite toy shop and purchased the set. In addition to the RCX
+
-
(shown in Figure 9-1), the MINDSTORMS set comes with an infrared (IR)
+
-
port and an IR tower, which you can connect to the serial port of your
+
-
PC, a battery compartment,<ref>There's also a DC power socket for those
+
-
of us without rechargeable batteries. </ref> motors, touch and light
+
-
sensors, and various LEGO Technic parts.
+
-
 
+
-
 
+
-
{{Figure|title=The LEGO MINDSTORMS RCX, or "programmable
+
-
brick"|image=0596002025-jab_0901.png</code> There are plenty of ways to
+
-
interact with the RCX. The MINDSTORMS Robotics Invention System (RIS)™
+
-
set comes with Windows software with which you can build programs by
+
-
moving blocks of logic around graphically on the screen and chaining
+
-
them together. In addition, various efforts on the parts of talented
+
-
individuals have come up with many different ways to program the RCX.
+
-
''The Unofficial Guide to LEGO® MINDSTORMS™ Robots'' (O'Reilly &amp;
+
-
Associates, Inc., 1999) tells you all you need to know about programing
+
-
the RCX. What's important to know for this recipe is detailed in
+
-
Programming the RCX.
+
-
 
+
-
 
+
-
{{Sidebar|Programming the RCXThere are two approaches to programming the
+
-
RCX. One approach is to write a program on your PC, download it to the
+
-
RCX, and start and stop the program using the buttons on the RCX itself.
+
-
 
+
-
The other approach is to control the RCX directly from a program that
+
-
you write ''and'' execute on your PC, sending control signals and
+
-
receiving sensor values over the IR connection.
+
-
 
+
-
Both approaches have their merits. How appropriate each one is boils
+
-
down to one thing: connections. On the one hand, building autonomous
+
-
machines that find their way around the kitchen to scare the cat and
+
-
bring you a sandwich calls for the first approach, when, once you've
+
-
downloaded the program to the RCX, you can dispense with any further
+
-
connection with your PC because the entire logic is situated in your
+
-
creation. On the other hand, if you want to build a physical extension
+
-
to a larger system that, for example, has a connection to the Internet,
+
-
the second approach is likely to be more fruitful, because you can
+
-
essentially use the program that runs on your PC and talks to the RCX
+
-
over the IR link as a conduit, a proxy of sorts, to other programs and
+
-
systems that can be reached over the network. We're going to use the
+
-
second approach.
+
-
 
+
-
The RIS software that comes as standard centers around an ActiveX
+
-
control. While there are plenty of ways to talk to the RCX without using
+
-
this control (the book mentioned earlier describes many of these ways),
+
-
the features offered by the control—<tt>Spirit.ocx</tt>—are fine for
+
-
many a project. And with Perl's <tt>Win32::OLE</tt> module, we can
+
-
interact with this ActiveX control without having to resort to Visual
+
-
Basic.
+
-
 
+
-
</code>
+
-
 
+
-
<br>
+
-
=== What We're Going to Do ===
+
-
<br>Everyone knows that one of the virtues of
+
-
a programmer is ''laziness''. We're going to extend this virtue (perhaps
+
-
a little too far) and enhance it with a hacker's innate ability to
+
-
combine two favorite pastimes—programming and playing with LEGO—to build
+
-
contrived but fun devices.
+
-
 
+
-
Often being a key part of a programmer's intake, coffee figures highly
+
-
on the daily agenda. It's important to have a good cup of coffee to keep
+
-
the brain cells firing, but it's even more important to know whether
+
-
there's actually any coffee left in the pot. Going over to the coffeepot
+
-
to find out is time away from the keyboard and therefore time wasted. So
+
-
let's put the RCX to good use and build a device to tell us, via Jabber,
+
-
whether the coffeepot has enough for another cup.
+
-
 
+
-
In building the device, a light sensor was connected to the RCX to "see"
+
-
the level of coffee in the pot. Since the coffeepot is made of glass,
+
-
light passes through it unless the coffee gets in the way, thus creating
+
-
a simple binary switch:
+
-
 
+
-
* No (or a small amount of) light measured: there's coffee in the pot. *
+
-
Some (or a larger amount of) light: there's no coffee in the pot. We
+
-
want to be able to send the availability of coffee to all interested
+
-
parties in a way that their off-the-shelf Jabber clients can easily
+
-
understand and display.
+
-
 
+
-
Figure 9-2 shows the LEGO MINDSTORMS device in action. The brick mounted
+
-
on the gantry is the light sensor, which extends to the glass coffeepot;
+
-
a wire runs from it to the connector on the RCX. Behind the RCX is the
+
-
IR tower, which is connected to the PC.
+
-
 
+
-
 
+
-
{{Figure|title=Our device "looking" at the
+
-
coffeepot|image=0596002025-jab_0902.png</code> Remembering that
+
-
<tt>&lt;presence/&gt;</tt> elements are a simple way of broadcasting
+
-
information about availability ''and'' that they contain a
+
-
<tt>&lt;status/&gt;</tt> tag to describe the detail or context of that
+
-
availability (see Section 5.4.2 for details on the
+
-
<tt>&lt;presence/&gt;</tt> element), we have a perfect mechanism that's
+
-
ready to be used. What's more, most, if not all, of the off-the-shelf
+
-
Jabber client implementations will display the content of the
+
-
<tt>&lt;status/&gt;</tt> tag in the client user's roster next to the JID
+
-
to which it applies. Figure 9-3 shows how the content of the
+
-
<tt>&lt;status/&gt;</tt> tag is displayed as a hovering "tooltip" in
+
-
WinJab.
+
-
 
+
-
 
+
-
{{Figure|title=Receiving information on the coffee's status in
+
-
WinJab|image=0596002025-jab_0903.png</code> Here's what we need to do:
+
-
 
+
-
 
+
-
; Step 1: Set up the RCX
+
-
: We need to set the RCX up, with the light sensor, so that it's close
+
-
: enough to the coffeepot to take reliable and consistent light
+
-
: readings. Luckily the serial cable that comes with the MINDSTORMS set
+
-
: and connects to the IR tower is long enough to stretch from the
+
-
: computer to within the infrared line of sight to the RCX.
+
-
; Step 2: Make the correct calibrations
+
-
: There are bound to be differences in ambient light, sensitivity of the
+
-
: light sensor, and how strong you make your coffee. So we need a way of
+
-
: calibrating the setup, so that we can find the appropriate "pivot
+
-
: point" light reading value that lies between the two states of
+
-
: ''coffee'' and ''no coffee''.
+
-
; Step 3: Set up a connection to Jabber
+
-
: We need a connection to a Jabber server and a client account there. We
+
-
: can set one up using the reguser script from Section 7.4. We also need
+
-
: the script to honor presence from users who want to be informed of the
+
-
: coffee state.
+
-
; Step 4: Set up a sensor poll/presence push loop
+
-
: Once the RCX has been set up, the sensor calibrations taken, and the
+
-
: connection has been made, we need to monitor the light sensor on the
+
-
: RCX at regular intervals. At each interval, we determine the coffee
+
-
: state by comparing the value received from the sensor with the pivot
+
-
: point determined in the calibration step and send any change in that
+
-
: state as a new availability <tt>&lt;presence/&gt;</tt> element
+
-
: containing an appropriate description in the <tt>&lt;status/&gt;</tt>
+
-
: tag.
+
-
<br>
+
-
=== The Coffee Script ===
+
-
<br>We're going to use Perl and the
+
-
<tt>Net::Jabber</tt> libraries to build the script shown in Example 9-6.
+
-
Perl allows us a comfortable way to interact with an ActiveX control,
+
-
through the <tt>Win32::OLE</tt> module, so let's have a look at the
+
-
''coffee'' script as a whole, then we'll go back and look at the script
+
-
in detail.
+
-
 
+
-
 
+
-
''The coffee script, written in Perl''
+
-
 
+
-
<code>use Net::Jabber qw(Client); use Win32::OLE; use Getopt::Std; use
+
-
strict;
+
-
 
+
-
my %opts; getopt('ls', \%opts);
+
-
 
+
-
use constant SERVER =&gt; "merlix.dyndns.org"; use constant PORT
+
-
=&gt; 5222; use constant USERNAME =&gt; "coffee"; use constant PASSWORD
+
-
=&gt; "pass"; use constant RESOURCE =&gt; "perlscript";
+
-
 
+
-
use constant NOCOFFEE =&gt; 0; use constant COFFEE =&gt; 1;
+
-
 
+
-
use constant SENSOR =&gt; defined($opts{'s'}) ? $opts{'s'} : 0; use
+
-
constant GRAIN =&gt; 1;
+
-
 
+
-
my $current_status = -1; my @status; $status[NOCOFFEE] = 'xa/Coffeepot
+
-
is empty'; $status[COFFEE] = '/Coffee is available!';
+
-
 
+
-
my $rcx = &amp;setup_RCX(SENSOR);
+
-
 
+
-
# Either calibrate if no parameters given, or run with the parameter
+
-
# given as -l, which will be taken as the pivot between coffee and no
+
-
# coffee
+
-
&amp;calibrate($rcx) unless defined($opts{'l'});
+
-
 
+
-
# Determine initial status (will be either 0 or 1)
+
-
my $s = &amp;set_status($rcx-&gt;Poll(9, SENSOR));
+
-
 
+
-
my $jabber = &amp;setup_Jabber(SERVER, PORT, USERNAME, PASSWORD,
+
-
RESOURCE, $s);
+
-
 
+
-
# Main loop: check Jabber and RCX
+
-
while (1) { defined($jabber-&gt;Process(GRAIN)) or die "The connection
+
-
to the Jabber server was broken\n"; my $s =
+
-
&amp;set_status($rcx-&gt;Poll(9, SENSOR)); &amp;set_presence($jabber,
+
-
$s) if defined $s;
+
-
}
+
-
 
+
-
 
+
-
# Set up Jabber client connection, sending initial presence
+
-
sub setup_Jabber { my ($server, $port, $user, $pass, $resource,
+
-
$initial_status) = @_; my $connection = new Net::Jabber::Client;
+
-
 
+
-
# Connect
+
-
my $status = $connection-&gt;Connect( hostname =&gt; $server, port
+
-
=&gt; $port ); die "Cannot connect to Jabber server $server on port
+
-
$port\n" unless $status;
+
-
 
+
-
# Callbacks
+
-
$connection-&gt;SetCallBacks( presence =&gt; \&amp;InPresence );
+
-
 
+
-
# Ident/Auth
+
-
my @result = $connection-&gt;AuthSend( username =&gt; $user, password
+
-
=&gt; $pass, resource =&gt; $resource ); die "Ident/Auth failed:
+
-
$result[0] - $result[1]\n" if $result[0] ne "ok";
+
-
 
+
-
# Roster
+
-
$connection-&gt;RosterGet();
+
-
 
+
-
# Initial presence dependent upon initial status
+
-
&amp;set_presence($connection, $initial_status);
+
-
 
+
-
return $connection;
+
-
}
+
-
 
+
-
 
+
-
sub set_presence { my ($connection, $s) = @_; my $presence =
+
-
Net::Jabber::Presence-&gt;new(); my ($show, $status) = split("/",
+
-
$status[$s], 2); $presence-&gt;SetPresence( show =&gt; $show, status
+
-
=&gt; $status ); print $status, "\n"; $connection-&gt;Send($presence);
+
-
}
+
-
 
+
-
 
+
-
# Handle presence messages
+
-
sub InPresence { my $presence = $_[1]; my $from =
+
-
$presence-&gt;GetFrom(); my $type = $presence-&gt;GetType();
+
-
 
+
-
if ($type eq "subscribe") { print "Subscribe request ($from) ...\n";
+
-
$jabber-&gt;Send($presence-&gt;Reply(type =&gt; 'subscribed'));
+
-
}
+
-
 
+
-
if ($type eq "unsubscribe") { print "Unsubscribe request ($from)
+
-
...\n"; $jabber-&gt;Send($presence-&gt;Reply(type =&gt;
+
-
'unsubscribed'));
+
-
}
+
-
}
+
-
 
+
-
 
+
-
sub setup_RCX { my $sensor = shift; my $rcx =
+
-
Win32::OLE-&gt;new('SPIRIT.SpiritCtrl.1'); $Win32::OLE::Warn = 0;
+
-
$rcx-&gt;{ComPortNo} = 1; $rcx-&gt;{InitComm};
+
-
$rcx-&gt;SetSensorType($sensor, 3); $rcx-&gt;SetSensorMode($sensor, 2);
+
-
return $rcx;
+
-
}
+
-
 
+
-
 
+
-
sub calibrate { my $rcx = shift;
+
-
 
+
-
print &lt;&lt;EOT; Calibration mode. Note the sensor values and decide
+
-
on a 'pivot' value above which 'no coffee' is signified and below
+
-
which 'coffee' is signified.
+
-
 
+
-
End the calibration mode with Ctrl-C.
+
-
 
+
-
Press Enter to start calibration... EOT
+
-
 
+
-
&lt;STDIN&gt;;
+
-
 
+
-
while (1) { print $rcx-&gt;Poll(9, SENSOR), " "; sleep 1;
+
-
}
+
-
 
+
-
}
+
-
 
+
-
 
+
-
sub set_status { my $val = shift;
+
-
 
+
-
my $new_status = $val &lt; $opts{'l'} ? COFFEE : NOCOFFEE;
+
-
 
+
-
if ($new_status != $current_status) { $current_status = $new_status;
+
-
return $current_status;
+
-
}
+
-
else { return undef;
+
-
}
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
<br>
+
-
=== Examining the Coffee Script Step by Step ===
+
-
<br>Now that we've seen the
+
-
''coffee'' script as a whole, let's examine it step by step to see how
+
-
it works.
+
-
 
+
-
 
+
-
 
+
-
==== Declaring the modules, constants, and variables ==== We first
+
-
declare the packages we're going to use. In addition to
+
-
<tt>Net::Jabber</tt> and <tt>Win32::OLE</tt>, we're going to use
+
-
<tt>Getopt::Std</tt>, which affords us a comfortable way of accepting
+
-
and parsing command-line options. We also want to use the
+
-
<tt>strict</tt> pragma, which should keep us from making silly coding
+
-
mistakes by not allowing undeclared variables and the like.
+
-
 
+
-
We specify <tt>Client</tt> on the usage declaration for the
+
-
<tt>Net::Jabber</tt> package to specify what should be loaded. The
+
-
package is a large and comprehensive set of modules, and only some of
+
-
those are relevant for what we wish to do in the script—build and work
+
-
with a Jabber ''client'' connection. Other module sets are pulled in by
+
-
specifying <tt>Component</tt> or <tt>Server</tt>.
+
-
 
+
-
 
+
-
<code>use Net::Jabber qw(Client); use Win32::OLE; use Getopt::Std; use
+
-
strict;
+
-
 
+
-
</code>
+
-
We're going to allow the command-line options -l and -s, which perform
+
-
the following tasks:
+
-
 
+
-
 
+
-
; No options specified (or just the -s options): calibration mode.
+
-
: When we run the script for the first time, we need to perform the
+
-
: calibration and read values from the sensor to determine a midpoint
+
-
: value. A number above the midpoint signifies the presence of light
+
-
: and therefore the absence of coffee; below signifies the absence of
+
-
: light and therefore the presence of coffee. This step is necessary
+
-
: because not every environment (ambient light, sensitivity of the light
+
-
: sensor, and so on) will be the same. The upper and lower values,
+
-
: representing lightness and darkness, respectively, will vary across
+
-
: different environments. The point is to obtain a value in between
+
-
: these upper and lower values—the midpoint—with which we can compare a
+
-
: light value read at any particular time. : If we don't specify any
+
-
: options, the script will start up automatically in calibration mode:
+
-
<code>C:\temp&gt; <tt>perl coffee.pl</tt></code>
+
-
: Figure 9-4 shows the script run in calibration mode. The values
+
-
: displayed, one each second, represent the values read from the light
+
-
: sensor. When the sensor was picking up lots of light, the values were
+
-
: 60. When the sensor was moved in front of some coffee, the values went
+
-
: down to around 45. Based upon this small test, the pivot point value
+
-
: was 50, somewhere in between those two values.
+
-
; -l: Specify the pivot value.
+
-
: Once we've determined a pivot point value, we run the script and tell
+
-
: it this pivot value with the -l (light pivot):
+
-
<code>C:\temp&gt; <tt>perl coffee.pl -l 50</tt></code>
+
-
 
+
-
; -s: Specify the sensor number.
+
-
: The RCX, shown in Figure 9-1, has three connectors to which you can
+
-
: attach sensors. They're the three gray 2-by-2 pieces, labeled 1, 2,
+
-
: and 3, near the top of the brick. The script assumes you've attached
+
-
: the light sensor to the one marked 1, which internally is 0. If you
+
-
: attach it to either of the other two, you can specify the connector
+
-
: using the -s (sensor) with a value of 1 (for the middle connector) or
+
-
: 2 (for the rightmost connector), like this:
+
-
<code>C:\temp&gt; <tt>perl coffee.pl -l 50 -s 2</tt></code>
+
-
: You can specify the -s option when running in calibration or normal
+
-
: modes.
+
-
{{Figure|title=Running coffee in calibration
+
-
mode|image=0596002025-jab_0904.png</code> The options, summarized in Table
+
-
9-1, are defined with the <tt>Getopt::Std</tt> function:
+
-
 
+
-
 
+
-
<code>my %opts; getopt('ls', \%opts);
+
-
 
+
-
</code>
+
-
 
+
-
{|
+
-
 
+
-
|+ Summary of the startup options -
+
-
! Option !! Meaning
+
-
|-
+
-
| No option || Start the script automatically in calibration mode.
+
-
|-
+
-
| -l || Specify the pivot value.
+
-
|-
+
-
| -s || Specify the sensor number.
+
-
|}
+
-
Next comes a list of constants, which describe:
+
-
 
+
-
* The script's Jabber relationship, including the server it will connect
+
-
to and the username, password, and resource it will connect with. * The
+
-
representation of the two states of ''coffee'' and ''no coffee'', which
+
-
will be used to determine the content of the <tt>&lt;status/&gt;</tt>
+
-
tag sent along inside any <tt>&lt;presence/&gt;</tt> element emitted. *
+
-
The identification of the connector to which the light sensor is
+
-
attached and the polling granularity of the sensor (poll/presence push
+
-
loop) described earlier. This item is measured in seconds.
+
-
 
+
-
<code>use constant SERVER =&gt; "merlix.dyndns.org"; use constant
+
-
PORT =&gt; 5222; use constant USERNAME =&gt; "coffee"; use constant
+
-
PASSWORD =&gt; "pass"; use constant RESOURCE =&gt; "perlscript";
+
-
 
+
-
use constant NOCOFFEE =&gt; 0; use constant COFFEE =&gt; 1;
+
-
 
+
-
use constant SENSOR =&gt; defined($opts{'s'}) ? $opts{'s'} : 0; use
+
-
constant GRAIN =&gt; 1;
+
-
 
+
-
</code>
+
-
The last part of the script's setup deals with the coffee state:
+
-
 
+
-
 
+
-
<code>my $current_status = -1; my @status; $status[NOCOFFEE] =
+
-
'xa/Coffeepot is empty'; $status[COFFEE] = '/Coffee is available!';
+
-
 
+
-
</code>
+
-
We use a two-element array (<tt>@status</tt>) to represent the two
+
-
possible coffee states. The value of each array element is a two-part
+
-
string, with each part separated by a slash (<tt>/</tt>). Each of these
+
-
parts will be transmitted in a <tt>&lt;presence/&gt;</tt> element, with
+
-
the first part (which is empty in the element representing the
+
-
<tt>COFFEE</tt> state) representing the presence <tt>&lt;show/&gt;</tt>
+
-
value and the second part representing the presence
+
-
<tt>&lt;status/&gt;</tt> value. Example 9-7 shows what a
+
-
<tt>&lt;presence/&gt;</tt> element looks like when built up with values
+
-
to represent the <tt>NOCOFFEE</tt> state.
+
-
 
+
-
 
+
-
''A presence element representing the NOCOFFEE state''
+
-
 
+
-
<code>&lt;presence&gt; &lt;show&gt;xa&lt;/show&gt;
+
-
&lt;status&gt;Coffeepot is empty&lt;/status&gt; &lt;/presence&gt;
+
-
 
+
-
</code>
+
-
Most Jabber clients use different icons in the roster to represent
+
-
different <tt>&lt;show/&gt;</tt> values. In this case, we will use
+
-
<tt>xa</tt> for ''no coffee'' and a blank (which represents "online" or
+
-
"available") for ''coffee'' to trigger the icon change.
+
-
 
+
-
<br>
+
-
 
+
-
==== Initialization and calibration ====
+
-
<br>Whenever we need to talk to the
+
-
RCX, some initialization is required via the ActiveX control. That's the
+
-
same whether we're going to calibrate or poll for values. The
+
-
<tt>setup_RCX()</tt> function takes a single argument—the identification
+
-
of which connector the light sensor is connected to—and performs the
+
-
initialization, which is described later in Section 9.2.3.8. The
+
-
function returns a handle on the <tt>Win32::OLE</tt> object that
+
-
represents the ActiveX control, which in turn represents the RCX via the
+
-
IR tower:
+
-
 
+
-
 
+
-
<code>my $rcx = &amp;setup_RCX(SENSOR);
+
-
 
+
-
</code>
+
-
If the -l option is not specified, it means we're going to be running
+
-
calibration. So we call the <tt>calibrate()</tt> function to do this for
+
-
us. We pass the RCX handle (in <tt>$rcx</tt>) so the calibration can run
+
-
properly:
+
-
 
+
-
 
+
-
<code># Either calibrate if no parameters given, or
+
-
# run with the parameter given as -l, which will be taken as the pivot
+
-
# between coffee and no coffee
+
-
&amp;calibrate($rcx) unless defined($opts{'l'});
+
-
 
+
-
</code>
+
-
As with the <tt>setup_RCX()</tt> function, <tt>calibrate()</tt> is
+
-
described later.
+
-
 
+
-
Calibration mode will be terminated by ending the script with
+
-
''Ctrl-C'', so the next thing we come across is the call to the function
+
-
<tt>set_status()</tt>, which represents the first stage in the normal
+
-
script mode; <tt>set_status()</tt> is used to determine the ''initial''
+
-
coffee status.
+
-
 
+
-
A value is retrieved by calling the ActiveX control's <tt>Poll()</tt>
+
-
function. (Table 9-2 lists the ActiveX control's functions and
+
-
properties used in this script.) We specify that we're after a sensor
+
-
value (the 9 as the first argument) from the sensor attached to the
+
-
connector indicated by the <tt>SENSOR</tt> constant:
+
-
 
+
-
 
+
-
<code># Determine initial status (will be either 0 or 1) my $s =
+
-
&amp;set_status($rcx-&gt;Poll(9, SENSOR));
+
-
 
+
-
</code>
+
-
The value retrieved is passed to the <tt>set_status()</tt> function,
+
-
which determines whether the value is above or below the pivot value and
+
-
whether the new status is different from the current one. It's going to
+
-
be something along the lines of one of the values displayed when the
+
-
script was run in calibration mode. If it is (and in this case, it will
+
-
be, because in this first call, the value of <tt>$current_status</tt> is
+
-
set to <tt>-1</tt>, which represents neither the <tt>COFFEE</tt> nor the
+
-
<tt>NOCOFFEE</tt> state), that status will be returned; otherwise,
+
-
<tt>undef</tt> will be returned.
+
-
 
+
-
 
+
-
{|
+
-
 
+
-
|+ RCX Spirit.ocx ActiveX control properties and functions used -
+
-
! Function/Property !! Description
+
-
|-
+
-
| <tt>Poll(''SOURCE'', ''NUMBER'')</tt> || Retrieves information from
+
-
| the RCX. In this script, the value for the ''SOURCE'' argument is
+
-
| always <tt>9</tt>, which represents a sensor value (i.e., a value
+
-
| measured at a sensor), as opposed to an internal RCX variable or a
+
-
| timer. The ''NUMBER'' argument represents the connector to which the
+
-
| sensor we want to read is attached.
+
-
|-
+
-
| <tt>SetSensorMode(''NUMBER'', ''MODE ''[, ''SLOPE''])</tt> || This
+
-
| function returns a value from the sensor. As with <tt>Poll()</tt> and
+
-
| <tt>SetSensorType()</tt>, ''NUMBER'' represents the sensor connector.
+
-
| The ''MODE'' argument can be used to determine the sensor mode, which
+
-
| can be ''Raw'' (mode 0), ''Boolean'' (mode 1), ''Transitional'' (mode
+
-
| 2), ''Periodic'' (mode 3), ''Percentage'' (mode 4), ''Celcius'' (mode
+
-
| 5), ''Farenheit'' (mode 6), or ''Angle'' (mode 7). The ''SLOPE''
+
-
| argument qualifies the ''Boolean'' mode by specifying how ''True'' and
+
-
| ''False'' are to be determined.
+
-
|-
+
-
| <tt>SetSensorType(''NUMBER'', ''TYPE'')</tt> || This function is used
+
-
| to specify the ''type'' of sensor the values will be read from. The
+
-
| ''NUMBER'' argument is the same as for the <tt>Poll()</tt> and
+
-
| represents the sensor connector. The ''TYPE'' argument represents the
+
-
| type of sensor that you want to set: ''None'' (type 0), ''Switch''
+
-
| (type 1), ''Temperature'' (type 2), ''Light'' (type 3), or ''Angle''
+
-
| (type 4).
+
-
|-
+
-
| <tt>property:ComPortNo</tt> || The serial port to which the IR tower
+
-
| is connected (e.g., 1 = COM1, 2 = COM2, and so on).
+
-
|-
+
-
| <tt>property:InitComm</tt> || When invoked, the serial communication
+
-
| port is initialized in preparation for the IR connection to the RCX.
+
-
|}
+
-
 
+
-
<br>
+
-
==== Connecting to the Jabber server ==== <br>
+
-
At this stage, we're ready to
+
-
connect to the Jabber server. The call to <tt>setup_Jabber()</tt> does
+
-
this for us, returning a handle to the Jabber connection object that we
+
-
store in <tt>$jabber</tt>. This handle will be used later in the script
+
-
to send out <tt>&lt;presence/&gt;</tt> elements. The <tt>$jabber</tt>
+
-
variable contains a reference to a <tt>Net::Jabber::Client</tt> object.
+
-
This is the equivalent of the <tt>con</tt> variable used in the earlier
+
-
Python scripts to hold the <tt>jabber.Client</tt> object and the
+
-
<tt>ConnectionBean</tt> object (<tt>cb</tt>) in the earlier Java
+
-
script.<ref>For the Python scripts, see Section 8.1 and Section 8.3. For
+
-
the Java script, see Section 8.2.</ref>
+
-
 
+
-
 
+
-
<code>my $jabber = &amp;setup_Jabber(SERVER, PORT, USERNAME, PASSWORD,
+
-
RESOURCE, $s);
+
-
 
+
-
</code>
+
-
In addition to passing the constants needed for the client connection to
+
-
the Jabber server, we pass the initial coffee status, held in
+
-
<tt>$s</tt>. We'll have a look at what the <tt>setup_Jabber()</tt>
+
-
function does with this initial status a bit later when we get to the
+
-
function's definition.
+
-
 
+
-
<br>
+
-
 
+
-
==== Sensor poll/presence push loop ====
+
-
<br>Now that we've set everything
+
-
up, determined the initial coffee status, and connected to the Jabber
+
-
server, we're ready to start the main loop:
+
-
 
+
-
 
+
-
<code># Main loop: check Jabber and RCX while (1) {
+
-
defined($jabber-&gt;Process(GRAIN)) or die "The connection to the Jabber
+
-
server was broken\n"; my $s = &amp;set_status($rcx-&gt;Poll(9, SENSOR));
+
-
&amp;set_presence($jabber, $s) if defined $s;
+
-
}
+
-
 
+
-
</code>
+
-
The <tt>while (1)</tt> loop is a bit of a giveaway. This script won't
+
-
stop until you force it to by entering ''Ctrl-C''—but that's essentially
+
-
what we want. In the loop, we call the <tt>Process()</tt> method on the
+
-
Jabber connection object in <tt>$jabber</tt>.
+
-
 
+
-
<tt>Process()</tt> is the equivalent of the <tt>Jabberpy</tt>'s
+
-
<tt>process()</tt> method in the Python scripts. <tt>Process()</tt>
+
-
waits around for up to the number of seconds specified as the single
+
-
argument (or not at all if no argument is specified) for XML to appear
+
-
on the stream connection from the Jabber server. If complete fragments
+
-
do appear, callbacks, defined in the connection object, are called with
+
-
the elements (<tt>&lt;iq/&gt;</tt>, <tt>&lt;message/&gt;</tt>, and
+
-
<tt>&lt;presence/&gt;</tt>) that the fragments represent. This is in the
+
-
same way as, for example, callbacks are used in the Python scripts using
+
-
the <tt>Jabberpy</tt> library. The <tt>setup_Jabber()</tt>, which will
+
-
be discussed in the next section, is where the callback definition is
+
-
made.
+
-
 
+
-
<tt>Net::Jabber</tt>'s <tt>Process()</tt> method returns <tt>undef</tt>
+
-
if the connection to the Jabber server is terminated while waiting for
+
-
XML. The <tt>undef</tt> value is dealt with appropriately by ending the
+
-
script.
+
-
 
+
-
The <tt>GRAIN</tt> constant, set to 1 second in the script's setup
+
-
section, is used to specify how long to wait for any packets from the
+
-
Jabber server. For the most part, we're not expecting to receive much
+
-
incoming Jabber traffic—the occasional presence subscription (or
+
-
unsubscription) request perhaps (see later), but other than that, the
+
-
only packets traveling over the connection to the Jabber server will be
+
-
availability <tt>&lt;presence/&gt;</tt> packets representing coffee
+
-
state changes, sent from the script. This delay is normally set to 1
+
-
second. And because that's a comfortable polling interval for the light
+
-
sensor, we can set that within the same loop.
+
-
 
+
-
Calling the ActiveX control's <tt>Poll()</tt> again with the same
+
-
arguments as before ("get a sensor value from the sensor attached to the
+
-
<tt>SENSOR</tt>th connector"), we pass the value to the
+
-
<tt>set_status()</tt> to determine the coffee state. If the state was
+
-
different from last time (if <tt>$s</tt> receives a value and not
+
-
<tt>undef</tt>), then we want to emit a <tt>&lt;presence/&gt;</tt>
+
-
element to reflect that state. We achieve this by calling the
+
-
<tt>set_presence()</tt> function, passing it the connection object and
+
-
the state.
+
-
 
+
-
<br>
+
-
 
+
-
==== The setup_Jabber() function ====
+
-
<br>Here we define the
+
-
<tt>setup_Jabber()</tt> function, which is called to set up the
+
-
connection to the Jabber server and authenticate with a predefined user:
+
-
 
+
-
 
+
-
<code># Set up Jabber client connection, sending intial presence sub
+
-
setup_Jabber { my ($server, $port, $user, $pass, $resource,
+
-
$initial_status) = @_; my $connection = new Net::Jabber::Client;
+
-
 
+
-
# Connect
+
-
my $status = $connection-&gt;Connect( hostname =&gt; $server, port
+
-
=&gt; $port ); die "Cannot connect to Jabber server $server on port
+
-
$port\n" unless $status;
+
-
 
+
-
# Callbacks
+
-
$connection-&gt;SetCallBacks( presence =&gt; \&amp;InPresence );
+
-
 
+
-
# Ident/Auth
+
-
my @result = $connection-&gt;AuthSend( username =&gt; $user, password
+
-
=&gt; $pass, resource =&gt; $resource ); die "Ident/Auth failed:
+
-
$result[0] - $result[1]\n" if $result[0] ne "ok";
+
-
 
+
-
# Roster
+
-
$connection-&gt;RosterGet();
+
-
 
+
-
# Initial presence dependent upon initial status
+
-
&amp;set_presence($connection, $initial_status);
+
-
 
+
-
return $connection;
+
-
}
+
-
 
+
-
</code>
+
-
First, we instantiate a new <tt>Net::Jabber::Client</tt> object.
+
-
<tt>Net::Jabber</tt> distinguishes between client- and component-based
+
-
connections to Jabber; the component-based equivalent of this class is
+
-
<tt>Net::Jabber::Component</tt>. The <tt>Connect()</tt> method is passed
+
-
arguments that specify the hostname and port of the Jabber server to
+
-
connect to. It returns a 0 status if the connection could not be made.
+
-
 
+
-
We can register handlers for Jabber elements received over the XML
+
-
stream carried by the connection we just made. Here we are interested in
+
-
incoming presence subscription or unsubscription requests, as we'll see
+
-
in the definition of the <tt>InPresence()</tt> function.
+
-
 
+
-
The single method, <tt>SetCallBacks()</tt>, does what the collective
+
-
<tt>jabber.Client</tt> methods <tt>setPresenceHandler()</tt>,
+
-
<tt>setMessageHandler()</tt>, and <tt>setIqHandler()</tt> do in a single
+
-
call—taking a list of element types and subroutine references, in the
+
-
form of a hash.
+
-
 
+
-
After registering the callback for <tt>&lt;presence/&gt;</tt> elements,
+
-
it's time to authenticate, passing the username, password, and resource
+
-
defined in the list of constants at the start of the script. If
+
-
authentication is successful, the result of the call to the
+
-
<tt>AuthSend()</tt> method is a single string with the value
+
-
<tt>ok</tt>. If not, that value is replaced with an error code and the
+
-
descriptive text is available in a further string. (This is why we catch
+
-
the results of a call in an array, called <tt>@result</tt>.) A complete
+
-
list of Jabber error codes and texts can be found in Table 5-3.
+
-
 
+
-
Why <tt>RosterGet()</tt>? We're not subscribing to anyone, and we're not
+
-
really interested in anything but the values we're polling from our
+
-
brick. So theoretically there's no reason to make a request to retrieve
+
-
our roster from the server. However, because we want the script to
+
-
receive and process subscription and unsubscription requests, we need to
+
-
request the roster beforehand; otherwise, the JSM won't send such
+
-
requests to us. See Section 8.3.3.4 in Chapter 8 for an explanation as
+
-
to why.
+
-
 
+
-
Once we've requested the roster, so as to receive presence subscription
+
-
and unsubscription requests, the job is almost done. The last thing to
+
-
do in setting up the Jabber connection is to send initial availability
+
-
information. The <tt>setup_Jabber()</tt> function receives the initial
+
-
coffee status as the last argument in the call (in
+
-
<tt>$initial_status</tt>), which it passes on to the function that sends
+
-
a <tt>&lt;presence/&gt;</tt> element, <tt>set_presence()</tt>. Along
+
-
with the initial coffee status, we also send the <tt>$connection</tt>
+
-
object that represents the connection to the Jabber server that we've
+
-
just established (referred to outside of this function with the
+
-
<tt>$jabber</tt> variable). This is so the <tt>set_presence()</tt>
+
-
function can use the connection handle to send the element down the
+
-
stream.
+
-
 
+
-
<br>
+
-
==== The set_presence() function ====
+
-
<br>This function is used by
+
-
<tt>setup_Jabber()</tt> to send the script's (and therefore the
+
-
coffee's) initial presence. It's also used within the main sensor
+
-
poll/presence push loop to send further presence packets if the coffee's
+
-
state changes.
+
-
 
+
-
 
+
-
<code>sub set_presence { my ($connection, $s) = @_; my $presence =
+
-
Net::Jabber::Presence-&gt;new(); my ($show, $status) = split("/",
+
-
$status[$s], 2); $presence-&gt;SetPresence( show =&gt; $show, status
+
-
=&gt; $status ); print $status, "\n"; $connection-&gt;Send($presence);
+
-
}
+
-
 
+
-
</code>
+
-
On receipt of the Jabber connection object and the coffee status, which
+
-
will be 0 (<tt>NOCOFFEE</tt>) or 1 (<tt>COFFEE</tt>),
+
-
<tt>set_presence()</tt> constructs a new <tt>Net::Jabber::Presence</tt>
+
-
object. This object represents a <tt>&lt;presence/&gt;</tt> element,
+
-
upon which we can make method calls to hone the element as we wish.
+
-
<tt>SetPresence()</tt> is one of these methods, with which we can set
+
-
values for each of the <tt>&lt;show/&gt;</tt> and
+
-
<tt>&lt;status/&gt;</tt> tags. We retrieve the values for each of these
+
-
tags by pulling the strings from the appropriate member of the
+
-
<tt>@status</tt> array, as described earlier in Section 9.2.3.1.
+
-
 
+
-
We print the coffee's status (remember, this function is called only
+
-
when the status ''changes'', not every time the sensor is polled) and
+
-
send the newly built <tt>&lt;presence/&gt;</tt> element down the XML
+
-
stream to the Jabber server. This is accomplished by passing the
+
-
presence object as an argument to the <tt>Send()</tt> method of the
+
-
connection object in <tt>$connection</tt>. This works in the same way as
+
-
the <tt>send()</tt> function in <tt>Jabberpy</tt> and the
+
-
<tt>send()</tt> function in <tt>JabberBeans</tt>. Everyone who has
+
-
subscribed to the script user's presence, and who is available, will
+
-
receive the coffee status information.
+
-
 
+
-
Figure 9-3 shows the status information received in the WinJab client.
+
-
The string sent in the <tt>&lt;status/&gt;</tt> tag is shown in the
+
-
tooltip that appears when the mouse hovers over the "coffee" roster
+
-
item.
+
-
 
+
-
<br>
+
-
 
+
-
==== The InPresence() subroutine ====
+
-
<br>Our presence handler, the callback
+
-
subroutine <tt>InPresence()</tt>, honors requests for subscription and
+
-
unsubscription to the script user's (and therefore the coffee's)
+
-
presence. This callback is designed to work in the same way as the
+
-
<tt>presenceCB()</tt> callback in the Python recipe described in Section
+
-
8.3.
+
-
 
+
-
However, while the Python <tt>Jabberpy</tt> library hands to the
+
-
callbacks a <tt>jabber.Client</tt> object and the element to be handled,
+
-
the Perl <tt>Net::Jabber</tt> library hands over a session ID and the
+
-
element to be handled. Don't worry about the session ID here; it's
+
-
related to functionality for building Jabber servers, not clients, and
+
-
we can and should ignore it for the purposes of this recipe. What is
+
-
important is the element to be handled, which appears as the second
+
-
argument passed to the subroutine collected by the <tt>$presence</tt>
+
-
variable from <tt>$_[1]</tt>.
+
-
 
+
-
What is common between the two libraries is that the element that is
+
-
passed to be handled as the subject of the callback is an instance of
+
-
the class that the callback represents. In other words, a callback is
+
-
used to handle <tt>&lt;presence/&gt;</tt> elements, and the element
+
-
received is an instance of the <tt>Net::Jabber::Presence</tt> class
+
-
(just as the element received by a <tt>Jabberpy</tt> presence callback
+
-
is an instance of the <tt>jabber.Presence</tt> class).
+
-
 
+
-
 
+
-
<code># Handle presence messages sub InPresence { my $presence = $_[1];
+
-
my $from = $presence-&gt;GetFrom(); my $type = $presence-&gt;GetType();
+
-
 
+
-
if ($type eq "subscribe") { print "Subscribe request ($from) ...\n";
+
-
$jabber-&gt;Send($presence-&gt;Reply(type =&gt; 'subscribed'));
+
-
}
+
-
 
+
-
if ($type eq "unsubscribe") { print "Unsubscribe request ($from)
+
-
...\n"; $jabber-&gt;Send($presence-&gt;Reply(type =&gt;
+
-
'unsubscribed'));
+
-
}
+
-
}
+
-
 
+
-
</code>
+
-
With an object in <tt>$presence</tt>, we can get information from the
+
-
element using data retrieval methods such as those used here:
+
-
<tt>GetFrom()</tt> and <tt>GetType()</tt>, which extract the values from
+
-
the <tt>from</tt> and <tt>type</tt> attributes of the
+
-
<tt>&lt;presence/&gt;</tt> element, respectively.
+
-
 
+
-
If the <tt>&lt;presence/&gt;</tt> element type represents a subscription
+
-
request (<tt>type="subscribe'</tt>), we unquestioningly honor the
+
-
request, by sending back an affirmative reply. The <tt>Reply()</tt>
+
-
method of the presence object is one of a number of high-level functions
+
-
that make it possible to turn elements around and send them back. In
+
-
this case, the method replaces the value of the
+
-
<tt>&lt;presence/&gt;</tt>'s <tt>to</tt> attribute with the value of the
+
-
<tt>from</tt> attribute, and preserves its <tt>id</tt>. It also allows
+
-
us to pass arguments as if we were calling the <tt>SetPresence()</tt>
+
-
method described earlier. Rather than set the <tt>&lt;show/&gt;</tt> and
+
-
<tt>&lt;status/&gt;</tt> tags as we did earlier in the
+
-
<tt>set_presence()</tt> function, we merely set the element's
+
-
<tt>type</tt> attribute to <tt>subscribed</tt> or <tt>unsubscribed</tt>,
+
-
depending on the request.
+
-
 
+
-
So, with an incoming <tt>&lt;presence/&gt;</tt> element in
+
-
<tt>$presence</tt> that looks like this:
+
-
 
+
-
 
+
-
<code>&lt;presence from='qmacro@jabber.org/office' type='subscribe'
+
-
to='coffee@merlix.dyndns.org' id='21'&gt;
+
-
 
+
-
</code>
+
-
calling the <tt>Reply()</tt> method would cause the element in $presence
+
-
to change to this:
+
-
 
+
-
 
+
-
<code>&lt;presence to='qmacro@jabber.org/office' type='subscribed'
+
-
id='21'&gt;
+
-
 
+
-
</code>
+
-
Remember, the <tt>from</tt> attribute on elements originating from the
+
-
client is set by the ''server'', not by the ''client''. The script
+
-
doesn't ask for a subscription to the user's presence in return. The
+
-
script isn't interested in whether the people who have subscribed to its
+
-
presence are available—its purpose is to let people know whether there's
+
-
any coffee left in the pot.
+
-
 
+
-
 
+
-
<br>
+
-
==== The setup_RCX() function ====
+
-
<br>This function is called once every
+
-
time the script is started and is required to initialize the RCX:
+
-
 
+
-
 
+
-
<code>sub setup_RCX { my $sensor = shift; my $rcx =
+
-
Win32::OLE-&gt;new('SPIRIT.SpiritCtrl.1'); $Win32::OLE::Warn = 0;
+
-
$rcx-&gt;{ComPortNo} = 1; $rcx-&gt;{InitComm};
+
-
$rcx-&gt;SetSensorType($sensor, 3); $rcx-&gt;SetSensorMode($sensor, 2);
+
-
return $rcx;
+
-
}
+
-
 
+
-
</code>
+
-
A <tt>Win32::OLE</tt> object representing the RCX's ActiveX control
+
-
<tt>Spirit</tt> is instantiated. A <tt>Win32::OLE</tt> function is used
+
-
to suppress warnings, and the RCX is initialized by setting the COM port
+
-
to COM1 for serial communications. The sensor type and mode are set for
+
-
the light sensor attached to the connector identified by the value
+
-
passed into the <tt>$sensor</tt> variable. Table 9-2 shows us that
+
-
sensor type 3 represents ''Light'', and sensor mode 2 specifies a
+
-
''Transitional'' measurement mode, the upshot of which is that the
+
-
values returned on a poll are all within a certain restricted range,
+
-
which makes it easier to decide whether there's any coffee in the pot.
+
-
 
+
-
We return the <tt>Win32::OLE</tt> RCX object to be used elsewhere in the
+
-
script for calibration and polling.
+
-
 
+
-
 
+
-
<br>
+
-
==== The calibrate() function ====
+
-
<br>The <tt>calibrate()</tt> function is
+
-
called if the script is started without the -l option. This function
+
-
simply prints a message, waits for the user to press ''Enter'', and then
+
-
goes into a a gentle loop, emitting whatever value was polled from the
+
-
light sensor so the user can determine the pivot point:
+
-
 
+
-
 
+
-
<code>sub calibrate { my $rcx = shift;
+
-
 
+
-
print &lt;&lt;EOT; Calibration mode. Note the sensor values and decide
+
-
on a 'pivot' value above which 'no coffee' is signified and below
+
-
which 'coffee' is signified.
+
-
 
+
-
End the calibration mode with Ctrl-C.
+
-
 
+
-
Press Enter to start calibration... EOT
+
-
 
+
-
&lt;STDIN&gt;;
+
-
 
+
-
while (1) { print $rcx-&gt;Poll(9, SENSOR), " "; sleep 1;
+
-
}
+
-
 
+
-
}
+
-
 
+
-
</code>
+
-
The output produced from this function can be seen in Figure 9-4.
+
-
 
+
-
 
+
-
<br>
+
-
==== The set_status() function ====
+
-
<br>The <tt>set_status()</tt> function
+
-
receives the latest light value as polled from the sensor and compares
+
-
it with the pivot value. If the status defined in <tt>$new_status</tt>
+
-
is different from the ''current'' status (in <tt>$current_status</tt>),
+
-
then the current status is updated and returned; otherwise,
+
-
<tt>undef</tt> is returned:
+
-
 
+
-
 
+
-
<code>sub set_status { my $val = shift;
+
-
 
+
-
my $new_status = $val &lt; $opts{'l'} ? COFFEE : NOCOFFEE;
+
-
 
+
-
if ($new_status != $current_status) { $current_status = $new_status;
+
-
return $current_status;
+
-
}
+
-
else { return undef;
+
-
}
+
-
}
+
-
 
+
-
</code>
+
-
If this function returns a status value, a new
+
-
<tt>&lt;presence/&gt;</tt> element is generated and emitted by the
+
-
script. Otherwise, there's no change ("the coffee's still there," or
+
-
"there's still no coffee!") and nothing happens.
+
-
 
+
-
 
+
-
<br>
+
-
== An RSS News Agent ==
+
-
<br>While the Jabber clients available off the shelf
+
-
are orientated toward receiving (and sending) messages from other
+
-
people, the possibilities don't stop there, as is clear from the recipes
+
-
we've seen already. In this recipe, we're going to build a Jabber
+
-
component that retrieves news items from various sources on the Web and
+
-
sends them on to Jabber users who have expressed an interest in
+
-
receiving them. We're going to use the Web for our news sources, but
+
-
they could just as easily be sources within a corporate intranet. The
+
-
key thing is that the sources are available in a readily parseable
+
-
format.
+
-
 
+
-
RSS (RDF<ref>RDF stands for "Resource Description Framework." </ref>
+
-
Site Summary or, alternatively, Really Simple Syndication) is an XML
+
-
format used for describing the content of a web site, where that site
+
-
typically contains news items, diary entries, event information, or
+
-
generally anything that grows, item by item, over time. A classic
+
-
application of RSS is to describe a news site such as JabberCentral
+
-
(http://www.jabbercentral.org). JabberCentral's main page (see Figure
+
-
9-5) consists of a number of news items—in the "Recent News"
+
-
section—about Jabber and its developer community. These items appear in
+
-
reverse chronological order, and each one is succinct, sharing a common
+
-
set of properties:
+
-
 
+
-
 
+
-
; Title
+
-
: Each item has a title ("JabberCon Update 11:45am - Aug 20").
+
-
; Short description
+
-
: Each item contains a short piece of text describing the content and
+
-
: context of the news story ("JabberCon Update - Monday Morning").
+
-
; Link to main story
+
-
: The short description should be enough to help the reader decide if he
+
-
: wants to read the whole item. If he does, there's a link ("Read More")
+
-
: to the news item itself.
+
-
{{Figure|title=JabberCentral's main page|image=0596002025-jab_0905.png</code>
+
-
It is this collection of item-level properties that are summarized in an
+
-
RSS file. The formality of the XML structure makes it a straightforward
+
-
matter for:
+
-
 
+
-
* Automating the retrieval of story summaries for inclusion in other
+
-
sites (syndication) * Combining these items with items from other
+
-
similar sources (aggregation) * Checking to see whether there is any new
+
-
content (new items) since the last visit Example 9-8 shows what the RSS
+
-
XML for JabberCentral's news items shown in Figure 9-5 looks like.
+
-
 
+
-
 
+
-
''RSS source for JabberCentral''
+
-
 
+
-
<code>&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;
+
-
 
+
-
&lt;!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN"
+
-
"http://my.netscape.com/publish/formats/rss-0.91.dtd"&gt;
+
-
 
+
-
&lt;rss version="0.91"&gt;
+
-
 
+
-
&lt;channel&gt;
+
-
 
+
-
&lt;title&gt;JabberCentral&lt;/title&gt;
+
-
 
+
-
&lt;description&gt; JabberCentral is the premiere Jabber end-user
+
-
news and support site. Many Jabber developers are actively involved
+
-
at JabberCentral to provide fresh and authoritative information for
+
-
users. &lt;/description&gt;
+
-
 
+
-
&lt;language&gt;en-us&lt;/language&gt;
+
-
&lt;link&gt;http://www.jabbercentral.com/&lt;/link&gt;
+
-
&lt;copyright&gt;Copyright 2001, Aspect Networks&lt;/copyright&gt;
+
-
 
+
-
&lt;image&gt;
+
-
&lt;url&gt;http://jabbercentral.com/images/jc_button.gif&lt;/url&gt;
+
-
&lt;title&gt;JabberCentral&lt;/title&gt;
+
-
&lt;link&gt;http://www.jabbercentral.com/&lt;/link&gt;
+
-
&lt;/image&gt;
+
-
 
+
-
&lt;item&gt; &lt;title&gt;JabberCon Update 11:45am - Aug
+
-
20&lt;/title&gt;
+
-
&lt;link&gt;http://www.jabbercentral.com/news/view.php?news_id=
+
-
998329970&lt;/link&gt; &lt;description&gt;JabberCon Update - Monday
+
-
Morning&lt;/description&gt; &lt;/item&gt;
+
-
 
+
-
&lt;item&gt; &lt;title&gt;Jabcast Promises Secure Jabber
+
-
Solutions&lt;/title&gt;
+
-
&lt;link&gt;http://www.jabbercentral.com/news/view.php?news_id=
+
-
998061331&lt;/link&gt; &lt;description&gt; Jabcast announces their
+
-
intention to release security plugins with their line of products
+
-
and services. &lt;/description&gt; &lt;/item&gt;
+
-
 
+
-
... (more items) ...
+
-
 
+
-
&lt;/channel&gt;
+
-
 
+
-
&lt;/rss&gt;
+
-
 
+
-
</code>
+
-
The structure is very straightforward. Each RSS file describes a
+
-
''channel'', which is defined as follows:
+
-
 
+
-
 
+
-
; Channel information
+
-
: The channel header information includes the channel's title
+
-
: (<tt>&lt;title/&gt;</tt>), short description
+
-
: (<tt>&lt;description/&gt;</tt>), main URL (<tt>&lt;link/&gt;</tt>),
+
-
: and so on. The channel in this case is JabberCentral.
+
-
; Channel image
+
-
: Often RSS information is rendered into HTML to provide a concise
+
-
: "current index" summary of the channel it describes. An image can be
+
-
: used in that summary rendering, and its definition is held in the
+
-
: <tt>&lt;image/&gt;</tt> section of the file.
+
-
; Channel items
+
-
: The bulk of the RSS file content is made up of the individual
+
-
: <tt>&lt;item/&gt;</tt> sections, each of which reflects an item on the
+
-
: site that the channel represents. We can see in Example 9-8 that the
+
-
: first <tt>&lt;item/&gt;</tt> tag:
+
-
<code>&lt;item&gt; &lt;title&gt;JabberCon Update 11:45am - Aug
+
-
20&lt;/title&gt;
+
-
&lt;link&gt;http://www.jabbercentral.com/news/view.php?news_id=998329970
+
-
&lt;/link&gt; &lt;description&gt;JabberCon Update - Monday
+
-
Morning&lt;/description&gt; &lt;/item&gt;
+
-
 
+
-
</code>
+
-
: describes the most recent news item shown on JabberCentral's main
+
-
: page—"JabberCon Update 11:45am - Aug 20." Each of the news item
+
-
: properties are contained within that <tt>&lt;item/&gt;</tt> tag: the
+
-
: title (<tt>&lt;title/&gt;</tt>), short description
+
-
: (<tt>&lt;description/&gt;</tt>), and link to main story
+
-
: (<tt>&lt;link/&gt;</tt>).
+
-
; Channel interactive feature
+
-
: There is a possibility for each channel to describe an interactive
+
-
: feature on the site it represents; often this is a search engine
+
-
: fronted by a text input field and Submit button. The interactive
+
-
: feature section of an RSS file is used to describe how that mechanism
+
-
: is to work (the name of the input field and the Submit button and the
+
-
: URL to invoke when the button is clicked, for example). This is so
+
-
: HTML renderings of the site can include the feature otherwise
+
-
: available only on the original site.
+
-
{{Note|This interactive feature definition is not shown in the RSS
+
-
example here.
+
-
 
+
-
</code>
+
-
RSS information lends itself very well to various methods of viewing.
+
-
There are custom "headline viewer" clients available—focused
+
-
applications that allow you to select from a vast array of RSS sources
+
-
and have links to items displayed on your desktop (so, yes, the personal
+
-
newspaper—of sorts—is here!). There are also possibilities for having
+
-
RSS items scroll by on your desktop control bar.
+
-
 
+
-
And then there's Jabber. As described in Section 5.4.1, the Jabber
+
-
<tt>&lt;message/&gt;</tt> element can represent something that looks
+
-
suspiciously like an RSS item. The message type "headline" defines a
+
-
message that carries news headline information. In this case, the
+
-
<tt>&lt;message/&gt;</tt> element itself is usually embellished with an
+
-
''extension'', qualified by the <tt>jabber:x:oob</tt> namespace
+
-
(described in Section 6.3.8). Example 9-9 shows what the element would
+
-
look like if the first news item from the JabberCentral site were
+
-
carried in a headline message.
+
-
 
+
-
 
+
-
''A headline message carrying a JabberCentral news item''
+
-
 
+
-
<code>&lt;message type='headline' to='dj@qmacro.dyndns.org'&gt;
+
-
&lt;subject&gt;JabberCon Update 11:45am - Aug 20&lt;/subject&gt;
+
-
&lt;body&gt;JabberCon Update - Monday Morning&lt;/body&gt; &lt;x
+
-
xmlns='jabber:x:oob'&gt;
+
-
&lt;url&gt;http://www.jabbercentral.com/news/view.php?news_id=998329970&
+
-
lt;/url&gt; &lt;desc&gt;JabberCon Update - Monday Morning&lt;/desc&gt;
+
-
&lt;/x&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
The <tt>jabber:x:oob</tt> namespace carries the crucial parts of the RSS
+
-
item. Clients, such as WinJab and Jarl, can understand this extension
+
-
and display the content in a clickable list of headlines, each
+
-
representing a single RSS item, similar to the headline viewer clients
+
-
mentioned earlier.
+
-
 
+
-
Of course, we could send RSS items to clients in nonheadline type
+
-
messages:
+
-
 
+
-
 
+
-
<code>&lt;message type='headline' to='dj@qmacro.dyndns.org'&gt;
+
-
&lt;subject&gt;JabberCon Update 11:45am - Aug 20&lt;/subject&gt;
+
-
&lt;body&gt; JabberCon Update - Monday Morning
+
-
http://www.jabbercentral.com/news/view.php?news_id=998329970
+
-
&lt;/body&gt; &lt;/x&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
where the complete item information is transmitted in a combination of
+
-
the <tt>&lt;subject/&gt;</tt> and <tt>&lt;body/&gt;</tt> tags. This
+
-
works, but the clients can only display the message, as with any other
+
-
message. However, if we send formalized ''metadata'', the value of the
+
-
message content increases enormously. (Figure 9-8 shows Jarl displaying
+
-
RSS-sourced news headlines.)
+
-
 
+
-
Distributing RSS-sourced headlines over Jabber to standard Jabber
+
-
clients is a great combination of off-the-shelf technologies. In fact,
+
-
we'll see in the next section that it's not just standard Jabber clients
+
-
that fit the bill; we'll write a Jabber-based headline viewer to show
+
-
that not all Jabber clients are, nor should they be, made equal.
+
-
 
+
-
<br>
+
-
 
+
-
=== Writing the News Agent ===
+
-
<br>We're going to write an RSS news agent,
+
-
which we'll simply call ''newsagent''. The ''newsagent'' is a mechanism
+
-
that checks predefined sources for new RSS items and sends (or pushes)
+
-
them to people who are interested in receiving them. For the sake of
+
-
simplicity, we'll define the list of RSS sources in ''newsagent''
+
-
itself. See Section 9.3.4 later in this chapter for details on how to
+
-
further develop this script.
+
-
 
+
-
<br>
+
-
 
+
-
==== The newsagent script as a component ====
+
-
<br>Until now, the examples
+
-
we've used, such as ''cvsmsg'', ''HostAlive'', and ''keyassist'' (shown
+
-
in Chapter 8), have all existed as Jabber ''clients''. That is, they've
+
-
performed a service while connected to the Jabber server via the JSM.
+
-
There's nothing wrong with this. Indeed, it's more than just fine to
+
-
build Jabber-based mechanisms using a Jabber client stub connection;
+
-
that way, your script, through its ''identity''—the user JID—can avail
+
-
itself of all the IM-related functions that the JSM offers—presence,
+
-
storage and forwarding of messages, and so on. Perhaps even more
+
-
interesting is that the mechanism needs only an account, a username, and
+
-
a password on a Jabber server to be part of the big connected picture.
+
-
 
+
-
However, we know from Chapter 4 that there are other entities that
+
-
connect to Jabber to provide services. These entities are called
+
-
''components''. You can look at components as philosophically less
+
-
"transient" than their client-connected brethren and also closer to the
+
-
Jabber server in terms of function and connection.
+
-
 
+
-
We know from Section 4.1.1 that there are various ways to connect a
+
-
component: library load, STDIO, and TCP sockets. The first two dictate
+
-
that the component will be located on the same host as the jabberd
+
-
backbone to which it connects, although a Jabber server could consist of
+
-
a ''collection'' of jabberds running on separate hosts. The TCP
+
-
sockets connection type uses a socket connection between the component
+
-
and the jabberd backbone, over which streamed XML documents are
+
-
exchanged (in the same way they are exchanged in a client connection).
+
-
It allows us to run components on any host and connect them to a Jabber
+
-
server running on another host if we wish. This approach is the most
+
-
desirable because of the connection flexibility. But it's not just the
+
-
flexibility that matters: because the component is abstracted away from
+
-
the Jabber server core libraries, it's up to us to decide how the
+
-
component should be written. All the component has to do to get the
+
-
Jabber server to cooperate is to establish the socket connection as
+
-
described in the component instance configuration, perform an
+
-
authenticating handshake, and correctly exchange XML stream headers.
+
-
 
+
-
Let's review how a TCP socket-based component connects. We'll base the
+
-
review on what we're actually going to have to do to get ''newsagent''
+
-
up and running.
+
-
 
+
-
First, we have to tell the Jabber server that it is to expect an
+
-
incoming socket connection attempt, which it is to ''accept''. We do
+
-
this by defining a component instance definition (or "description"—see
+
-
Section 4.2.1) for our component. We include this definition in the main
+
-
Jabber server configuration file, usually called jabber.xml. Example
+
-
9-10 shows a component instance definition for the RSS news agent, known
+
-
as <tt>rss.qmacro.dyndns.org</tt>.
+
-
 
+
-
 
+
-
''A component instance definition for the RSS news agent''
+
-
 
+
-
<code>&lt;service id='rss.qmacro.dyndns.org'&gt; &lt;accept&gt;
+
-
&lt;ip&gt;localhost&lt;/ip&gt; &lt;port&gt;5999&lt;/port&gt;
+
-
&lt;secret&gt;secret&lt;/secret&gt; &lt;/accept&gt; &lt;/service&gt;
+
-
 
+
-
</code>
+
-
The name of the host on which the main Jabber server is running is
+
-
<tt>qmacro.dyndns.org</tt>; it just so happens that the plan is to run
+
-
the RSS news agent component on the same host. We give it a unique name
+
-
(<tt>rss.qmacro.dyndns.org</tt>) to enable the jabberd backbone, or hub,
+
-
to distinguish it from other components and to be able to route elements
+
-
to it.
+
-
 
+
-
An alternate way of writing the component instance definition is shown
+
-
in Example 9-11. The difference is simply in the way we specify the
+
-
name. In Example 9-10, we specified an <tt>id</tt> in the
+
-
<tt>&lt;service/&gt;</tt> tag with the value
+
-
<tt>rss.qmacro.dyndns.org</tt>. In the absence of any
+
-
<tt>&lt;host/&gt;</tt> tag specification in the definition, this
+
-
<tt>id</tt> value is used by the jabberd routing logic as the
+
-
identification for the component when determining where elements
+
-
addressed with that destination should be sent. In Example 9-11, we have
+
-
an explicit <tt>&lt;host/&gt;</tt> specification that will be used
+
-
instead; we simply identify the service with an <tt>id</tt> attribute
+
-
value of <tt>rss</tt>. In this latter case, it doesn't really matter
+
-
from an addressability point of view what we specify as the value for
+
-
the <tt>id</tt> attribute.
+
-
 
+
-
 
+
-
''An alternative instance definition for the RSS news agent''
+
-
 
+
-
<code>&lt;service id='rss'&gt;
+
-
&lt;host&gt;rss.qmacro.dyndns.org&lt;/host&gt; &lt;accept&gt;
+
-
&lt;ip&gt;localhost&lt;/ip&gt; &lt;port&gt;5999&lt;/port&gt;
+
-
&lt;secret&gt;secret&lt;/secret&gt; &lt;/accept&gt; &lt;/service&gt;
+
-
 
+
-
</code>
+
-
The instance definition contains all the information the Jabber server
+
-
needs. We can tell from the <tt>&lt;accept/&gt;</tt> tag that this
+
-
definition describes a TCP sockets connection. The socket connection
+
-
detail is held in the <tt>&lt;ip/&gt;</tt> and <tt>&lt;port/&gt;</tt>
+
-
tags. In this case, as we're going to run the RSS News Agent component
+
-
on the same host as the Jabber server itself, we might as well kill two
+
-
related birds—performance and security—with one stone by specifying in
+
-
the <tt>&lt;ip/&gt;</tt> tag:<ref>Despite the tag name, you can specify
+
-
an IP address or a hostname in the <tt>&lt;ip/&gt;</tt> tag. </ref>
+
-
 
+
-
 
+
-
; Performance
+
-
: Connecting over the loopback device, as opposed to a real network
+
-
: interface, will give us a slight performance boost.
+
-
; Security
+
-
: Accepting only on the loopback device is a simple security measure
+
-
: that leaves one less port open to the world. The
+
-
: <tt>&lt;secret/&gt;</tt> tag holds the secret that the connecting
+
-
: component must present in the authentication handshake. How the secret
+
-
: is specified is described later on in this section.
+
-
 
+
-
Now let's look at the component's view of things. It will need to
+
-
establish a socket connection to 127.0.0.1:5999. Once that connection
+
-
has been established, jabberd will be expecting it to announce itself by
+
-
sending its XML document stream header. Example 9-12 shows a typical
+
-
stream header that the component will need to send.
+
-
 
+
-
 
+
-
''The RSS component's stream header''
+
-
 
+
-
<code>SEND: &lt;?xml version='1.0'?&gt; &lt;stream:stream
+
-
xmlns='jabber:component:accept'
+
-
xmlns:stream='http://etherx.jabber.org/streams' to='localhost'&gt;
+
-
 
+
-
</code>
+
-
This matches the description of a Jabber XML stream header (also known
+
-
as a stream "root" as it's the root tag of the XML document) from
+
-
Section 5.3. The namespace that is specified as the one qualifying the
+
-
''content'' of the stream is <tt>jabber:component:accept</tt>. This
+
-
namespace matches the component connection method (TCP sockets) and the
+
-
significant tag name in the component instance definition (). Likewise,
+
-
the namespace <tt>jabber:component:exec</tt> matches the ''STDIO''
+
-
component connection method and the significant tag name in ''its''
+
-
component instance definition format: ()—see Section 4.1.3.3. The value
+
-
specified in the <tt>to</tt> attribute matches the hostname specified in
+
-
the configuration's <tt>&lt;ip/&gt;</tt> tag.
+
-
 
+
-
After receiving a valid stream header, jabberd responds with a similar
+
-
root to head up its own XML document stream going in the opposite
+
-
direction (from server to component). A typical response to the header
+
-
(Example 9-12) received from the server by the component is shown in
+
-
Example 9-13.
+
-
 
+
-
 
+
-
''The server's stream header reply''
+
-
 
+
-
<code>RECV: &lt;?xml version='1.0'?&gt; &lt;stream:stream
+
-
xmlns:stream='http://etherx.jabber.org/streams' id='3B8E3540'
+
-
xmlns='jabber:component:accept' from='rss'&gt;
+
-
 
+
-
</code>
+
-
The stream header sent in response shows that the server is confirming
+
-
the component instance's identification as <tt>rss</tt>. This reflects
+
-
whatever was specified in the <tt>&lt;service/&gt;</tt> tag's
+
-
<tt>id</tt> attribute of the component instance definition. Here, the
+
-
value of the <tt>id</tt> attribute was <tt>rss</tt> as in Example 9-11.
+
-
 
+
-
It also contains an ID for the component instance itself
+
-
(<tt>id="3B8E3540'</tt>). This ID is a random string shared between both
+
-
connecting parties; the value is used in the next stage of the
+
-
connection attempt—the authenticating handshake.
+
-
 
+
-
The digest authentication method for clients connecting to the JSM is
+
-
described in Section 7.3.1.2. This method uses a similar shared random
+
-
string. On receipt of the server's stream header, the component takes
+
-
the ID and prepends it onto the secret that it must authenticate itself
+
-
with. It then creates a NIST SHA-1 message digest (in a hexadecimal
+
-
format) of that value:
+
-
 
+
-
 
+
-
<code>''SHA1_HEX(ID+SECRET)''</code> After the digest is created, it is sent
+
-
in a <tt>&lt;handshake/&gt;</tt> element as the first XML fragment
+
-
following the root:
+
-
 
+
-
 
+
-
<code>SEND: &lt;handshake
+
-
id="1"&gt;14d437033d7735f893d509c002194be1c69dc500&lt;/handshake&gt;
+
-
 
+
-
</code>
+
-
On receipt of this authentication request, jabberd combines the ID value
+
-
with the value from the <tt>&lt;secret/&gt;</tt> tag in the component
+
-
instance definition and performs the same digest algorithm. If the
+
-
digests match, the component is deemed to have authenticated itself
+
-
correctly, and it is then sent back an empty <tt>&lt;handshake/&gt;</tt>
+
-
tag in confirmation:
+
-
 
+
-
 
+
-
<code>&lt;handshake/&gt;
+
-
</code>
+
-
The component may commence sending (and being sent) elements.
+
-
 
+
-
If the component sends an invalid handshake value—the secret may be
+
-
wrong or the digest may not have been calculated correctly—the
+
-
connection is closed: jabberd sends a stream error, ending the
+
-
conversation:
+
-
 
+
-
 
+
-
<code>RECV: &lt;stream:error&gt;Invalid handshake&lt;/stream:error&gt;
+
-
</code>
+
-
 
+
-
 
+
-
==== Working out who gets what newsfeeds ==== Definitions of the RSS
+
-
sources are held within the ''newsagent'' itself, but there's no
+
-
reference to who might want to receive new items from which sources. We
+
-
need a way for the component to accept requests, from users, that say
+
-
things like:
+
-
 
+
-
<blockquote>"I'd like to have pointers to new items from Slashdot sent
+
-
to me, please."
+
-
 
+
-
</blockquote>or:
+
-
 
+
-
<blockquote>"I'd ''also'' like pointers to new items on Jon Udell's
+
-
site, please."
+
-
 
+
-
</blockquote>or even:
+
-
 
+
-
<blockquote>"Whoa, information overflow! Stop all my feeds!"
+
-
 
+
-
</blockquote>There's a common theme that binds together components such
+
-
as the Jabber User Directory (JUD), and the transports to other IM
+
-
systems such as Yahoo! and ICQ. This theme is known as ''registration''.
+
-
We've seen this before in the form of ''user registration'', described
+
-
in Section 6.5.2. This is the process of creating a new account with the
+
-
JSM. Registration with a ''service'' such as the JUD or an IM transport,
+
-
however, follows a similar process, and both types of registration have
+
-
one thing in common: the <tt>jabber:iq:register</tt> namespace.
+
-
 
+
-
The <tt>jabber:iq:register</tt> namespace is used to qualify the
+
-
exchange of information during a registration process. The registration
+
-
process to create a new user account with the JSM uses the
+
-
<tt>jabber:iq:register</tt> namespace to qualify registration data
+
-
exchanged. The registration process with the JSM to modify the account
+
-
details (name, email address, and so on) also uses
+
-
<tt>jabber:iq:register</tt> to qualify the account amendment data
+
-
exchanged. Both types of registration requests are addressed to the JSM.
+
-
The difference, which allows the JSM to distinguish between ''what'' is
+
-
being requested, is that no session is active on the stream between
+
-
client and server in the new user registration process, whereas in the
+
-
account amendment process, a session ''is'' active. This is also
+
-
mentioned in Section 7.2.2.5Chapter 7.
+
-
 
+
-
The <tt>jabber:iq:register</tt> namespace is described in Section 6.2.11
+
-
in Chapter 6. It shows us how a typical conversation between requester
+
-
and responder takes place:
+
-
 
+
-
# The client sends an IQ-get: "''How do I register?''" The component
+
-
# sends an IQ-result: "''Here's how:
+
-
follow these instructions to fill in these fields.''"
+
-
# The client then sends an IQ-set with values in the fields: "''OK,
+
-
# here's my registration request.''" To which the component responds,
+
-
# with another IQ-result: "''Looks fine. Your registration details have
+
-
# been stored.''"
+
-
It's clear that this sort of model will lend itself well to the process
+
-
of allowing users to make requests to receive pointers to new items from
+
-
RSS sources chosen from a list. Example 9-14 shows this conversational
+
-
model in Jabber XML. There are many fields that can be used in a
+
-
registration request; the description in Section 6.2.11 in Chapter 6
+
-
includes a few of these—<tt>&lt;name/&gt;</tt>, <tt>&lt;first/&gt;</tt>,
+
-
<tt>&lt;last/&gt;</tt>, and <tt>&lt;email/&gt;</tt>—but there are more.
+
-
We'll take the <tt>&lt;text/&gt;</tt> field to accept the name of an RSS
+
-
source when a user attempts to register his interest to receive pointers
+
-
to new items from that source. The conversational model is shown from
+
-
the component's perspective.
+
-
 
+
-
 
+
-
''A registration conversation for RSS sources'' "''How do I register?''"
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq type='get' id='JCOM_3' to='rss.qmacro.dyndns.org'
+
-
from='dj@qmacro.dyndns.org/basement'&gt; &lt;query
+
-
xmlns='jabber:iq:register'/&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
"''Here's how:''"
+
-
 
+
-
 
+
-
<code>SEND: &lt;iq id='JCOM_3' type='result'
+
-
to='dj@qmacro.dyndns.org/basement' from='rss.qmacro.dyndns.org'&gt;
+
-
&lt;query xmlns='jabber:iq:register'&gt; &lt;instructions&gt; Choose an
+
-
RSS source from: Slashdot, Jon Udell[, ...] &lt;/instructions&gt;
+
-
&lt;text/&gt; &lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
"''OK, here's my registration request:''"
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq type='set' id='JCOM_5' to='rss.qmacro.dyndns.org'
+
-
from='dj@qmacro.dyndns.org/basement'&gt; &lt;query
+
-
xmlns='jabber:iq:register'&gt; &lt;text&gt;Slashdot&lt;/text&gt;
+
-
&lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
"''Looks fine. Your registration details have been stored.''"
+
-
 
+
-
 
+
-
<code>SEND: &lt;iq id='JCOM_5' type='result'
+
-
to='dj@qmacro.dyndns.org/basement' from='rss.qmacro.dyndns.org'&gt;
+
-
&lt;query xmlns='jabber:iq:register'&gt;
+
-
&lt;text&gt;Slashdot&lt;/text&gt; &lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
After some time passes...
+
-
 
+
-
"''Whoa, information overflow! Stop all my feeds!''"
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq id='JCOM_11' to='rss.qmacro.dyndns.org' type='set'
+
-
from='dj@qmacro.dyndns.org/basement'&gt; &lt;query
+
-
xmlns='jabber:iq:register'&gt; &lt;remove/&gt; &lt;/query&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
</code>
+
-
"''OK, you've been removed. All feeds stopped.''"
+
-
 
+
-
 
+
-
<code>SEND: &lt;iq id='JCOM_11' to='dj@qmacro.dyndns.org/basement'
+
-
type='result' from='rss.qmacro.dyndns.org'&gt; &lt;query
+
-
xmlns='jabber:iq:register'&gt; &lt;remove/&gt; &lt;/query&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
</code>
+
-
A lightweight persistent storage system is used for the user/source
+
-
registrations—DataBase Manager (DBM)—to keep the script fairly simple.
+
-
 
+
-
The bigger question here is: how will the user know he can register to a
+
-
particular RSS feed? Or more importantly: how can he determine if the
+
-
RSS News Agent system exists? Most clients, having connected to the
+
-
server and established a session with the JSM, make a request for a list
+
-
of ''agents'' (old terminology) or ''services'' (new terminology)
+
-
available from the Jabber server with the following IQ-get method:
+
-
 
+
-
 
+
-
<code>SEND: &lt;iq id="wjAgents" to="qmacro.dyndns.org" type="get"&gt;
+
-
&lt;query xmlns="jabber:iq:agents"/&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
The response to the request looks like this:
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq id='wjAgents' to='dj@qmacro.dyndns.org/basement'
+
-
type='result' from='qmacro.dyndns.org'&gt; &lt;query
+
-
xmlns='jabber:iq:agents'&gt; &lt;agent jid='conf.qmacro.dyndns.org'&gt;
+
-
&lt;name&gt;Public Chatrooms&lt;/name&gt;
+
-
&lt;service&gt;public&lt;/service&gt; &lt;groupchat/&gt; &lt;/agent&gt;
+
-
&lt;agent jid='users.jabber.org'&gt; &lt;name&gt;Jabber User
+
-
Directory&lt;/name&gt; &lt;service&gt;jud&lt;/service&gt;
+
-
&lt;search/&gt; &lt;register/&gt; &lt;/agent&gt; &lt;/query&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
</code>
+
-
which reflects the contents of the <tt>&lt;browse/&gt;</tt> section in
+
-
the JSM configuration as shown in Example 9-15.
+
-
 
+
-
 
+
-
''The JSM configuration's browse section''
+
-
 
+
-
<code>&lt;browse&gt; &lt;conference type="public"
+
-
jid="conf.qmacro.dyndns.org" name="Public Chatrooms"/&gt; &lt;service
+
-
type="jud" jid="users.jabber.org" name="Jabber User Directory"&gt;
+
-
&lt;ns&gt;jabber:iq:search&lt;/ns&gt;
+
-
&lt;ns&gt;jabber:iq:register&lt;/ns&gt; &lt;/service&gt; &lt;/browse&gt;
+
-
 
+
-
</code>
+
-
If we ''add'' a stanza that describes the component for the RSS News
+
-
Agent to the <tt>&lt;browse/&gt;</tt> section of the JSM configuration:
+
-
 
+
-
 
+
-
<code>&lt;service type="rss" jid="rss.qmacro.dyndns.org" name="RSS News
+
-
Agent"&gt; &lt;ns&gt;jabber:iq:register&lt;/ns&gt; &lt;/service&gt;
+
-
 
+
-
</code>
+
-
we get an extra section in the <tt>jabber:iq:agents</tt> response from
+
-
the server:
+
-
 
+
-
 
+
-
<code>&lt;agent jid='rss.qmacro.dyndns.org'&gt; &lt;name&gt;RSS News
+
-
Agent&lt;/name&gt; &lt;service&gt;rss&lt;/service&gt; &lt;register/&gt;
+
-
&lt;/agent&gt;
+
-
 
+
-
</code>
+
-
The client-side effect of the agents response is exactly what we're
+
-
looking for. Figure 9-6 shows WinJab's Agents menu displaying a summary
+
-
of what it received in response to its <tt>jabber:iq:agents</tt> query.
+
-
 
+
-
 
+
-
{{Figure|title=WinJab's Agents menu|image=0596002025-jab_0906.png</code> We
+
-
can see that the stanza for the RSS news agent was present in the
+
-
<tt>&lt;browse/&gt;</tt> section and the component is faithfully
+
-
displayed in the agent list, along with Public Chatrooms and Jabber User
+
-
Directory. In the main window of the screenshot we can see the Supported
+
-
Namespaces list; it contains the namespace that we specified in the
+
-
stanza. By specifying:
+
-
 
+
-
 
+
-
<code>&lt;ns&gt;jabber:iq:register&lt;/ns&gt;
+
-
</code>
+
-
we're effectively telling the client that the component will support a
+
-
registration conversation.
+
-
 
+
-
But that's not all—we've advertised the RSS news agent in the
+
-
<tt>&lt;browse/&gt;</tt> section of the configuration for the JSM on the
+
-
Jabber server running on <tt>qmacro.dyndns.org</tt>. That's why we got
+
-
the information about the RSS news agent when we connected as user
+
-
<tt>dj</tt> to <tt>qmacro.dyndns.org</tt> (see the window's titlebar in
+
-
Figure 9-6). You may have noticed something odd about the definition of
+
-
the other two agents, or services, in the <tt>&lt;browse/&gt;</tt>
+
-
section earlier or in the corresponding <tt>jabber:iq:agents</tt> IQ
+
-
response. Let's take a look at this response again, this time with the
+
-
extra detail about the component:
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq id='wjAgents' to='dj@qmacro.dyndns.org/basement'
+
-
type='result' from='qmacro.dyndns.org'&gt; &lt;query
+
-
xmlns='jabber:iq:agents'&gt; &lt;agent jid='rss.qmacro.dyndns.org'&gt;
+
-
&lt;name&gt;RSS News Agent&lt;/name&gt;
+
-
&lt;service&gt;rss&lt;/service&gt; &lt;register/&gt; &lt;/agent&gt;
+
-
&lt;agent jid='conf.qmacro.dyndns.org'&gt; &lt;name&gt;Public
+
-
Chatrooms&lt;/name&gt; &lt;service&gt;public&lt;/service&gt;
+
-
&lt;groupchat/&gt; &lt;/agent&gt; &lt;agent jid='users.jabber.org'&gt;
+
-
&lt;name&gt;Jabber User Directory&lt;/name&gt;
+
-
&lt;service&gt;jud&lt;/service&gt; &lt;search/&gt; &lt;register/&gt;
+
-
&lt;/agent&gt; &lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
While the <tt>jid</tt> attribute values for the RSS news agent and
+
-
Public Chatroom agents show that they are components connected to the
+
-
Jabber server (i.e., they both have JIDs in the
+
-
<tt>qmacro.dyndns.org</tt> "space," and so are connected to the Jabber
+
-
server running at <tt>qmacro.dyndns.org</tt>), the <tt>jid</tt>
+
-
attribute for the Jabber User Directory points to a name in the
+
-
<tt>jabber.org</tt> "space"! This is a side effect of the power and
+
-
foresight of Jabber's architectural design. If we connect a
+
-
component—whether it's one we've built ourselves or one we've downloaded
+
-
from http://download.jabber.org—we can give it an ''internal'' or
+
-
''external'' identity when we describe it in the jabber.xml
+
-
configuration.
+
-
 
+
-
Example 9-8 and Example 9-9 show two examples of an instance definition
+
-
for the RSS news agent component. Both specify potentially ''external''
+
-
identities. If the hostname <tt>rss.qmacro.dyndns.org</tt> is a valid
+
-
and resolvable hostname, the component can be reached from anywhere, not
+
-
just from within the Jabber server to which it is connected. If the
+
-
hostname wasn't resolvable by the outside world, by having a simple name
+
-
such as <tt>rss</tt>, it could be reached only from the Jabber server to
+
-
which it was connected.
+
-
 
+
-
So let's say <tt>rss.qmacro.dyndns.org</tt>''is'' a valid and resolvable
+
-
hostname. If your client is connected to a Jabber server running on
+
-
<tt>yourserver.org</tt>, this is what would happen if you were to send a
+
-
registration request (an <tt>&lt;iq/&gt;</tt> element with a query
+
-
qualified by the <tt>jabber:iq:register</tt> namespace) addressed to
+
-
<tt>rss.qmacro.dyndns.org</tt>:
+
-
 
+
-
 
+
-
; Packet reaches JSM on yourserver.org.
+
-
: You send the IQ from your client, which is connected to your Jabber
+
-
: server's JSM. This is where the packet first arrives.
+
-
; Internal routing tables consulted.
+
-
: This is how <tt>yourserver.org</tt>'s jabberd looks in its list of
+
-
: internally registered destinations and doesn't find
+
-
: <tt>rss.qmacro.dyndns.org</tt> in there.
+
-
; Name resolved and routing established.
+
-
: <tt>yourserver.org</tt>'s <tt>dnsrv</tt> (Hostname Resolution) service
+
-
: is used to resolve the <tt>rss.qmacro.dyndns.org</tt>'s address. Then,
+
-
: according to <tt>dnsrv</tt>'s instance configuration (specifically the
+
-
: &lt;resend&gt;s2s&lt;/resend&gt; part—see Section 4.9), the IQ is
+
-
: routed on to the <tt>s2s</tt> (Server to Server) component.
+
-
; Server to server connection established.
+
-
: <tt>yourserver.org</tt> establishes a connection to
+
-
: <tt>qmacro.dyndns.org</tt> via <tt>s2s</tt> and sends the IQ across
+
-
: the connection.
+
-
; Packet arrives at the RSS News Agent component on qmacro.dyndns.org.
+
-
: jabberd on <tt>qmacro.dyndns.org</tt> routes the packet correctly to
+
-
: <tt>rss.qmacro.dyndns.org</tt>. So, what do we learn from this? As
+
-
: exemplified by the reference to the JUD running at
+
-
: <tt>users.jabber.org</tt> that comes predefined in the standard
+
-
: jabber.xml with the 1.4.1 version of the Jabber server, you can
+
-
: specify references to services, components, ''on other Jabber
+
-
: servers''. If you take this RSS News Agent script and run it against
+
-
: your own Jabber server, there's no reason why you can't share its
+
-
: services with your friends who run their own Jabber servers.
+
-
 
+
-
The key is not the reference in the <tt>&lt;browse/&gt;</tt> section; it
+
-
is the resolvability of component names as hostnames and the ability of
+
-
Jabber servers to route packets to each other. The stanza in
+
-
<tt>&lt;browse/&gt;</tt> just makes it easier ''for the clients'' to
+
-
automatically know about and be able to interact with services in
+
-
general. Even if a service offered by a public component that ''wasn't''
+
-
described in the result of a <tt>jabber:iq:agents</tt> query, it
+
-
wouldn't stop you from reaching it. The agent browser is another client,
+
-
Gabber (shown in Figure 9-7), which is a GTK-based Jabber client that
+
-
allows you to specify a Jabber server name, in the Server to Browse
+
-
field, so that you can direct the <tt>jabber:iq:agents</tt> queries to
+
-
whatever server you want.
+
-
 
+
-
 
+
-
{{Figure|title=Viewing agents on different Jabber servers with
+
-
Gabber|image=0596002025-jab_0907.png</code> A good example of the distinction
+
-
between the definition of a component within a <tt>&lt;browse/&gt;</tt>
+
-
section and that component's reachability is the version query shown in
+
-
Example 9-16. Regardless of whether the conference component at
+
-
<tt>gnu.mine.nu</tt> was listed in the <tt>&lt;browse/&gt;</tt> section
+
-
of <tt>qmacro.dyndns.org</tt>'s JSM, the user <tt>dj</tt> was able to
+
-
make a version query by specifying the component's address (a valid and
+
-
resolvable hostname) in the IQ-get's <tt>to</tt> attribute.
+
-
 
+
-
 
+
-
''A Conferencing component responds to a version query''
+
-
 
+
-
<code>SEND: &lt;iq type='get' to='conf.gnu.mine.nu'&gt; &lt;query
+
-
xmlns='jabber:iq:version'/&gt; &lt;/iq&gt;
+
-
 
+
-
RECV: &lt;iq type='result' to='dj@qmacro.dyndns.org/study'
+
-
from='conf.gnu.mine.nu'&gt; &lt;query xmlns='jabber:iq:version'&gt;
+
-
&lt;name&gt;conference&lt;/name&gt; &lt;version&gt;0.4&lt;/version&gt;
+
-
&lt;os&gt;Linux 2.2.13&lt;/os&gt; &lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== Polling the RSS sources ==== Next, we need some way of
+
-
"interrupting" the process of checking for incoming elements and
+
-
dispatching them to the callbacks, while we retrieve the RSS data and
+
-
check for new items. Since we're writing this component in Perl, we
+
-
could use the <tt>alarm()</tt> feature to set an alarm and have a
+
-
subroutine invoked, to poll the RSS sources, when the alarm goes off.
+
-
However, this recipe uses the <tt>Jabber::Connection</tt> library, which
+
-
negates the needs for an external alarm. Instead, we need to take the
+
-
following steps each time we want to poll the RSS sources:
+
-
 
+
-
# Try to retrieve the source from the URL. Attempt to parse the source's
+
-
# XML. Go through the items, until we come across one we've seen before;
+
-
# the ones we go through until then are deemed to be new. (We need a
+
-
# special case the first time around, so that we don't flood everyone
+
-
# with every item of a source the first time it is retrieved.) For new
+
-
# items, look in the registration database for the users that have
+
-
# registered for that source, construct a headline message like the one
+
-
# shown in Example 9-7, and send it to those users. Remember the first
+
-
# of the new items, so that we don't go beyond it next time.
+
-
 
+
-
 
+
-
=== Other Differences Between Client and Component Programming === There
+
-
are many differences between programming a component and programming a
+
-
client. We're already aware of many of the major ones, described earlier
+
-
in Section 9.3.1.1. There are, however, also more subtle differences
+
-
that we need to bear in mind.
+
-
 
+
-
Components, unlike clients, ''do not'' connect to the JSM. They connect
+
-
as a ''peer'' of the JSM. This means not only that they cannot partake
+
-
of IM features made available by JSM's modules (see Section 4.4.4 for a
+
-
list of these modules) but also that they must do more for themselves.
+
-
This isn't as bad as it seems. Take store and forward, for example, a
+
-
feature provided by the JSM's ''mod_offline'' module. While a message
+
-
sent to a component won't be stored and forwarded if that component is
+
-
not connected, a message sent from a component to a client ''will'' get
+
-
stored and forwarded if the client is offline, because the message will
+
-
be routed to the JSM (as specified by the
+
-
<tt>[</tt>''hostname''<tt>]</tt> in the address), which can decide what
+
-
action to take. Messages can be passed directly to the client if the
+
-
user is online or can be stored and forwarded later when they're back
+
-
online.
+
-
 
+
-
When constructing an element as a client, we should not specify a
+
-
<tt>from</tt> attribute before it is sent; this is added by the JSM as
+
-
it arrives to prevent JID spoofing. If a component does not connect
+
-
through the JSM, no "from-stamping" takes place; the component itself
+
-
must stamp the element with a <tt>from</tt> attribute.
+
-
 
+
-
The addressing of a component is also slightly different. Client
+
-
addresses reflect the fact that they're connected to the JSM, always
+
-
having the form of (with the ''resource'' being optional):
+
-
 
+
-
 
+
-
<code> [''user'']@[''hostname'']/[''resource'']</code> While the basic
+
-
address form of a ''component'' is simply:
+
-
 
+
-
 
+
-
<code> [''hostname'']</code> This doesn't mean to say that the address of a
+
-
component cannot have a ''user'' or a ''resource'' part. It's just that
+
-
''all'' elements addressed to:
+
-
 
+
-
 
+
-
<code>''anything''@[''hostname'']/''anything''</code> will be routed by
+
-
jabberd to the component. This means the component can play multiple
+
-
roles and have many personalities. We'll see an example of this in the
+
-
script, where we construct an "artificial"
+
-
<tt>[</tt>''user''<tt>]@[</tt>''hostname''<tt>]</tt> address for the
+
-
<tt>from</tt> attribute of a <tt>&lt;message/&gt;</tt> element, to
+
-
convey information.
+
-
 
+
-
The component will respond to IQ queries in the
+
-
<tt>jabber:iq:register</tt> namespace. It is customary, although by no
+
-
means mandatory, for components to respond to queries in a set of common
+
-
IQ namespaces. We see that both the JUD and Conferencing components, for
+
-
example, respond to IQ queries in the <tt>jabber:iq:time</tt> and
+
-
<tt>jabber:iq:version</tt> namespaces. Example 9-16 shows a typical
+
-
version query on a Conferencing component. This responsiveness is simply
+
-
to provide a basic level of administrative information. We want the
+
-
component to conform to the customs, so we'll make sure it also responds
+
-
to queries in these namespaces.
+
-
 
+
-
 
+
-
 
+
-
=== The RSS News Agent Script === The RSS news agent script
+
-
(''newsagent'') is written in Perl and is shown here in Example 9-17.
+
-
 
+
-
 
+
-
''The RSS news agent script, written in Perl''
+
-
 
+
-
<code>my $NAME = 'RSS News Agent'; my $ID =
+
-
'rss.qmacro.dyndns.org'; my $VERSION = '0.1'; my $reg_file =
+
-
'registrations'; my %reg;
+
-
 
+
-
my %cache;
+
-
 
+
-
my %sources = ( 'jonudell' =&gt;
+
-
'http://udell.roninhouse.com/udell.rdf', 'slashdot' =&gt;
+
-
'http://slashdot.org/slashdot.rdf',
+
-
 
+
-
# etc ...
+
-
);
+
-
 
+
-
tie (%reg, 'MLDBM', $reg_file) or die "Cannot tie to $reg_file: $!\n";
+
-
 
+
-
my $c = new Jabber::Connection( server =&gt; 'localhost:5999',
+
-
localname =&gt; $ID, ns =&gt; 'jabber:component:accept', );
+
-
 
+
-
unless ($c-&gt;connect()) { die "oops: ".$c-&gt;lastError; }
+
-
 
+
-
$SIG{HUP} = $SIG{KILL} = $SIG{TERM} = $SIG{INT} = \&amp;cleanup;
+
-
 
+
-
debug("registering RSS beat"); $c-&gt;register_beat(1800, \&amp;rss);
+
-
 
+
-
debug("registering IQ handlers");
+
-
$c-&gt;register_handler('iq',\&amp;iq_register);
+
-
$c-&gt;register_handler('iq',\&amp;iq_version);
+
-
$c-&gt;register_handler('iq',\&amp;iq_browse);
+
-
$c-&gt;register_handler('iq',\&amp;iq_notimpl);
+
-
 
+
-
$c-&gt;auth('secret');
+
-
 
+
-
$c-&gt;start;
+
-
 
+
-
sub iq_register { my $node = shift; debug("[iq_register]"); return
+
-
unless my $query = $node-&gt;getTag('', NS_REGISTER); debug("--&gt;
+
-
registration request");
+
-
 
+
-
# Reg query
+
-
if ($node-&gt;attr('type') eq IQ_GET) { $node = toFrom($node);
+
-
$node-&gt;attr('type', IQ_RESULT); my $instructions = "Choose an RSS
+
-
source from: ".join(", ", keys %sources);
+
-
$query-&gt;insertTag('instructions')-&gt;data($instructions);
+
-
$query-&gt;insertTag('text'); $c-&gt;send($node);
+
-
}
+
-
 
+
-
# Reg request
+
-
if ($node-&gt;attr('type') eq IQ_SET) {
+
-
 
+
-
# Strip JID to user@host
+
-
my $jid = stripJID($node-&gt;attr('from'));
+
-
 
+
-
$node = toFrom($node); my $source;
+
-
 
+
-
# Could be an unregister
+
-
if ($query-&gt;getTag('remove')) { delete $reg{$jid};
+
-
$node-&gt;attr('type', IQ_RESULT);
+
-
}
+
-
 
+
-
# Otherwise it's a registration for a source
+
-
elsif ($source = $query-&gt;getTag('text')-&gt;data and
+
-
exists($sources{$source})) { my $element = $reg{$jid};
+
-
$element-&gt;{$source} = 1; $reg{$jid} = $element;
+
-
$node-&gt;attr('type', IQ_RESULT);
+
-
}
+
-
 
+
-
else { $node-&gt;attr('type', IQ_ERROR); my $error =
+
-
$node-&gt;insertTag('error'); $error-&gt;attr('code', '405');
+
-
$error-&gt;data('Not Allowed');
+
-
}
+
-
 
+
-
$c-&gt;send($node);
+
-
}
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
sub iq_version { my $node = shift; debug("[iq_version]"); return unless
+
-
my $query = $node-&gt;getTag('', NS_VERSION) and $node-&gt;attr('type',
+
-
IQ_GET); debug("--&gt; version request"); $node = toFrom($node);
+
-
$node-&gt;attr('code', IQ_RESULT);
+
-
$query-&gt;insertTag('name')-&gt;data($NAME);
+
-
$query-&gt;insertTag('version')-&gt;data($VERSION);
+
-
$query-&gt;insertTag('os')-&gt;data(`uname -sr`); $c-&gt;send($node);
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
sub iq_browse { my $node = shift; debug("[iq_browse]"); return unless my
+
-
$query = $node-&gt;getTag('', NS_BROWSE) and $node-&gt;attr('type',
+
-
IQ_GET); debug("--&gt; browse request"); $node = toFrom($node);
+
-
$node-&gt;attr('type', IQ_RESULT); my $rss =
+
-
$query-&gt;insertTag('service'); $rss-&gt;attr('type', 'rss');
+
-
$rss-&gt;attr('jid', $ID); $rss-&gt;attr('name', $NAME);
+
-
$rss-&gt;insertTag('ns')-&gt;data(NS_REGISTER); $c-&gt;send($node);
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
sub iq_notimpl { my $node = shift; $node = toFrom($node);
+
-
$node-&gt;attr('type', IQ_ERROR); my $error =
+
-
$node-&gt;insertTag('error'); $error-&gt;attr('code', '501');
+
-
$error-&gt;data('Not Implemented'); $c-&gt;send($node); return r_HANDLED;
+
-
}
+
-
 
+
-
sub rss { debug("[rss]");
+
-
 
+
-
# Create NodeFactory
+
-
my $nf = new Jabber::NodeFactory;
+
-
 
+
-
# Go through each of the RSS sources
+
-
foreach my $source (keys %sources) {
+
-
 
+
-
# Retrieve attempt
+
-
my $data = get($sources{$source});
+
-
 
+
-
# Didn't get it? Next one
+
-
unless (defined($data)) { debug("Cannot retrieve $source"); next;
+
-
}
+
-
 
+
-
# Parse the RSS
+
-
my $rss = XML::RSS-&gt;new(); eval { $rss-&gt;parse($data) };
+
-
 
+
-
if ($@) { debug("Problems parsing $source"); next;
+
-
}
+
-
 
+
-
my @items = @{$rss-&gt;{items</code>;
+
-
 
+
-
# Check new items
+
-
debug("$source: looking for new items"); foreach my $item (@items) {
+
-
 
+
-
# Stop checking if we get to items already seen
+
-
last if exists $cache{$source} and $cache{$source} eq
+
-
$item-&gt;{link};
+
-
 
+
-
debug("$source: new item $item-&gt;{title}");
+
-
 
+
-
# Create a headline message
+
-
my $msg = $nf-&gt;newNode('message');
+
-
 
+
-
$msg-&gt;attr('type', 'headline'); $msg-&gt;attr('from', join('@',
+
-
$source, $ID));
+
-
$msg-&gt;insertTag('subject')-&gt;data($item-&gt;{title});
+
-
$msg-&gt;insertTag('body')-&gt;data($item-&gt;{description});
+
-
 
+
-
my $xoob = $msg-&gt;insertTag('x', NS_XOOB);
+
-
$xoob-&gt;insertTag('url')-&gt;data($item-&gt;{link});
+
-
$xoob-&gt;insertTag('desc')-&gt;data($item-&gt;{description});
+
-
 
+
-
# Deliver to all that want it
+
-
foreach my $jid (keys %reg) {
+
-
 
+
-
my $registration = $reg{$jid};
+
-
 
+
-
if (exists($registration-&gt;{$source})) { $msg-&gt;attr('to',
+
-
$jid); debug("Sending to $jid"); $c-&gt;send($msg);
+
-
}
+
-
}
+
-
 
+
-
# Prevent all items counted as new the first time around
+
-
last unless exists($cache{$source});
+
-
}
+
-
 
+
-
# Remember the latest new item
+
-
$cache{$source} = $items[0]-&gt;{link};
+
-
}
+
-
}
+
-
 
+
-
sub cleanup { debug("Cleaning up"); untie %reg; $c-&gt;disconnect; exit;
+
-
}
+
-
 
+
-
sub toFrom { my $node = shift; my $to = $node-&gt;attr('to');
+
-
$node-&gt;attr('to', $node-&gt;attr('from')); $node-&gt;attr('from',
+
-
$to); return $node;
+
-
}
+
-
 
+
-
sub stripJID { my $JID = shift; $JID =~ s|/.*$||; return $JID;
+
-
}
+
-
 
+
-
sub debug { print STDERR "debug: ", @_, "\n";
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
=== Reviewing the RSS News Agent Script Step by Step === Now that we
+
-
know what the ''newsagent'' script looks like in its entirety, let's
+
-
review the script piece by piece.
+
-
 
+
-
 
+
-
 
+
-
==== Module declarations and variable definitions ==== We start out by
+
-
using the <tt>Jabber::Connection</tt> library, which is defined as
+
-
follows:
+
-
 
+
-
 
+
-
<code>use strict; use Jabber::Connection; use Jabber::NodeFactory; use
+
-
Jabber::NS qw(:all); use MLDBM 'DB_File'; use LWP::Simple; use XML::RSS;
+
-
 
+
-
</code>
+
-
The <tt>Jabber::Connection</tt> library consists of the following three
+
-
modules:
+
-
 
+
-
 
+
-
; <tt>Jabber::Connection</tt>
+
-
: This module is used to manage the connection to the server and parses
+
-
: and dispatches incoming elements.
+
-
; <tt>Jabber::NodeFactory</tt>
+
-
: This module allows us to manipulate elements, which are generically
+
-
: called ''nodes''.
+
-
; <tt>Jabber::NS</tt>
+
-
: The last module provides us with a list of constants that reflect
+
-
: namespaces and other common strings used in Jabber server, client, and
+
-
: component programming. Next we need a way of storing the registration
+
-
: information between invocations of the component script, and for that
+
-
: we'll use the Multi-Level Database Manager module, <tt>MLDBM</tt>.
+
-
: <tt>MLDBM</tt> is a useful wrapper that can be placed around the
+
-
: <tt>DB_File</tt> module. <tt>DB_File</tt> provides access to Berlekey
+
-
: Database (Berkeley DB, at http://www.sleepycat.com) facilities using
+
-
: the <tt>tie()</tt> function. While you can't store references (i.e.,
+
-
: complex data structures) via <tt>DB_File</tt>, you can with the
+
-
: <tt>MLDBM</tt> wrapper.
+
-
 
+
-
We will use the <tt>LWP::Simple</tt> module to grab the RSS sources by
+
-
URL and the <tt>XML::RSS</tt> module to parse those sources once
+
-
retrieved:
+
-
 
+
-
 
+
-
<code>my $NAME = 'RSS News Agent'; my $ID =
+
-
'rss.qmacro.dyndns.org'; my $VERSION = '0.1'; my $reg_file =
+
-
'registrations'; my %reg;
+
-
 
+
-
my %cache;
+
-
 
+
-
my %sources = ( 'jonudell' =&gt;
+
-
'http://udell.roninhouse.com/udell.rdf', 'slashdot' =&gt;
+
-
'http://slashdot.org/slashdot.rdf',
+
-
# etc ...
+
-
);
+
-
 
+
-
</code>
+
-
We start by declaring a few variables. We will see later in the script
+
-
that <tt>$NAME</tt>, <tt>$ID</tt>, and <tt>$VERSION</tt> will be used to
+
-
reflect information in response to IQ queries. The variable
+
-
<tt>$reg_file</tt> defines the name of the DB file to which we will be
+
-
tying the registration hash, <tt>%reg</tt>. <tt>%cache</tt> is the RSS
+
-
item cache, which holds items we've already seen so we know when we've
+
-
come to the end of the new items in a particular source.
+
-
 
+
-
We define the RSS sources in <tt>%sources</tt>. These can be defined
+
-
differently, perhaps outside of the script, but this gives you a general
+
-
idea of how these should look. There are a couple of examples here; add
+
-
your own favorite channels to taste.
+
-
 
+
-
To make persistent any data we store in the <tt>%reg</tt> hash, we can
+
-
use the magic of the <tt>tie()</tt> function:
+
-
 
+
-
 
+
-
<code>tie (%reg, 'MLDBM', $reg_file) or die "Cannot tie to $reg_file:
+
-
$!\n";
+
-
 
+
-
</code>
+
-
It works by binding (<tt>tie</tt>'ing) the operations on the hash (add,
+
-
delete, and so on) to Berlekey DB operations, using the <tt>MLDBM</tt>
+
-
module to stringify (and reconstruct) complex data structures so they
+
-
can be stored and retrieved.
+
-
 
+
-
 
+
-
 
+
-
==== Connecting to the Jabber server ==== Now we're ready to connect to
+
-
the Jabber server as a component. Despite what's involved (described in
+
-
Section 9.3.1.1) it's very easy to make the connection to the Jabber
+
-
server using a library such as <tt>Jabber::Connection</tt>:
+
-
 
+
-
 
+
-
<code>my $c = new Jabber::Connection( server =&gt; 'localhost:5999',
+
-
localname =&gt; $ID, ns =&gt; 'jabber:component:accept', );
+
-
 
+
-
</code>
+
-
We construct a <tt>Jabber::Connection</tt> object, specifying the
+
-
details of the connection we wish to make. The <tt>server</tt> argument
+
-
is used to specify the hostname, and optionally the port, of the Jabber
+
-
server to which we wish to connect. In the case of a component, we must
+
-
always specify the port (which is 5999 in this example, as shown in
+
-
Example 9-8). The same constructor can be used to create a client
+
-
connection to the Jabber server, in which case a default port of 5222
+
-
(the standard port for client connections) is assumed if none is
+
-
explicitly specified. The <tt>localname</tt> argument is used to specify
+
-
the component's name, which in this case is
+
-
<tt>rss.qmacro.dyndns.org</tt>. In the same way that a default port of
+
-
5222 is assumed if none is specified, a default stream namespace of
+
-
<tt>jabber:client</tt> is assumed if no <tt>ns</tt> argument is
+
-
specified. Since we want to connect as a component using the TCP sockets
+
-
connection method, we must specify the appropriate namespace:
+
-
<tt>jabber:component:accept</tt>.
+
-
 
+
-
This constructor call results in a stream header being prepared; it
+
-
looks like the one shown in Example 9-12.
+
-
 
+
-
The actual connection attempt, including the sending of the component's
+
-
stream header, is done by calling the <tt>connect()</tt> method on the
+
-
connection object in <tt>$c</tt>:
+
-
 
+
-
 
+
-
<code>unless ($c-&gt;connect()) { die "oops: ".$c-&gt;lastError; }
+
-
 
+
-
</code>
+
-
This will return a true value if the connect succeeded (success is
+
-
measured in whether the socket connection was established and whether
+
-
the Jabber server sent a stream header in response). If it didn't
+
-
succeed, we can retrieve details of what happened using the
+
-
<tt>lastError()</tt> method.
+
-
 
+
-
We're connected. Before performing the authenticating handshake, we're
+
-
going to do a bit of preparation:
+
-
 
+
-
 
+
-
<code>$SIG{HUP} = $SIG{KILL} = $SIG{TERM} = $SIG{INT} = \&amp;cleanup;
+
-
 
+
-
</code>
+
-
The idea is that the component will be run and stopped only in certain
+
-
circumstances. If it is stopped, we want to clean things up before the
+
-
script ends. Most importantly, we need to make sure the registration
+
-
data is safe, but also we want to play nicely with the server and
+
-
gracefully disconnect. This is done in the <tt>cleanup()</tt> function.
+
-
 
+
-
 
+
-
 
+
-
==== Preparing the RSS event function and element handlers ====
+
-
<tt>Jabber::Connection</tt> offers a simple way of having a function
+
-
execute at regular intervals. It avoids the need for setting and
+
-
resetting alarms. It is the <tt>register_beat()</tt> function:
+
-
 
+
-
 
+
-
<code>debug("registering RSS beat"); $c-&gt;register_beat(1800,
+
-
\&amp;rss);
+
-
 
+
-
</code>
+
-
Calling the <tt>register_beat()</tt> method takes two arguments. The
+
-
first argument represents the interval, in seconds. The second is a
+
-
reference to the function that should be invoked at each interval. Here,
+
-
we're saying we want the <tt>rss()</tt> function called every 30 minutes
+
-
(1800 seconds).
+
-
 
+
-
Most of the traffic relating to the component will be the headline
+
-
messages emanating from it. However, we are expecting incoming IQ
+
-
elements, particularly for registration in the
+
-
<tt>jabber:iq:register</tt> namespace. We've also already mentioned that
+
-
it's customary for components to honor basic "administrative" queries
+
-
such as version checks. So the list of calls to the
+
-
<tt>register_handler()</tt> method here reflects what we want to offer
+
-
in terms of handling these IQ elements:
+
-
 
+
-
 
+
-
<code>debug("registering IQ handlers");
+
-
$c-&gt;register_handler('iq',\&amp;iq_register);
+
-
$c-&gt;register_handler('iq',\&amp;iq_version);
+
-
$c-&gt;register_handler('iq',\&amp;iq_browse);
+
-
$c-&gt;register_handler('iq',\&amp;iq_notimpl);
+
-
 
+
-
</code>
+
-
Whereas with <tt>Net::Jabber</tt>'s <tt>SetCallBacks()</tt> function and
+
-
with <tt>Jabberpy</tt>'s <tt>setIqHandler()</tt> method we specify a
+
-
single function to act as a handler for incoming <tt>&lt;iq/&gt;</tt>
+
-
elements, we can specify as many handlers as we want for each element
+
-
type with the <tt>register_handler()</tt> method in
+
-
<tt>Jabber::Connection</tt>.
+
-
 
+
-
The first argument refers to the element name (the name of the element's
+
-
outermost tag), and the second refers to a function that will be called
+
-
on receipt of an element of that name. Each of the handlers for a
+
-
particular element will be called in the order they were registered. So
+
-
when an <tt>&lt;iq/&gt;</tt> element is received over the XML stream,
+
-
<tt>Jabber::Connection</tt> will dispatch it to <tt>iq_register()</tt>,
+
-
then to <tt>iq_version()</tt>, then to <tt>iq_browse()</tt>, and then to
+
-
<tt>iq_notimpl()</tt>. That is, unless one of those handler functions
+
-
decides that the element has been handled once and for all and that the
+
-
dispatch processing for that element should stop there. In this case,
+
-
that handler simply returns a special value (defined in
+
-
<tt>Jabber::NS</tt>), and the dispatching stops for that element. The
+
-
handlers can also cooperate, in that the dispatcher will pass whatever
+
-
one handler returns into the next handler in the list and so on, so that
+
-
you can effectively share data across handler events for a particular
+
-
element, building up a complex response as you go.
+
-
 
+
-
This ''contextual response chain'' model works in a similar way to how
+
-
the ''mod_auth_*'' authentication modules work in the JSM. Each one that
+
-
wishes to express its interest in authenticating a user adds its "stamp"
+
-
to the response to an IQ-get in the <tt>jabber:iq:auth</tt> namespace,
+
-
before that response is returned to the client.
+
-
 
+
-
 
+
-
{{Note|Indeed, the author of the <tt>Jabber::Connection</tt> library has
+
-
taken the (heart)beat idea—the handler chain idea and even the low-level
+
-
<tt>NodeFactory</tt> mechanisms—directly from the JSM and the server
+
-
libraries, in homage to the Jabber server's classic design.
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== Authenticating handshake and launch of main loop ==== Once we've
+
-
set up the handlers, we're ready to make the authenticating handshake.
+
-
This is simply a call to the <tt>auth()</tt> method:
+
-
 
+
-
 
+
-
<code>$c-&gt;auth('secret');
+
-
 
+
-
</code>
+
-
It takes either one or three arguments, depending on whether the
+
-
authentication is for a client or a component.
+
-
<tt>Jabber::Connection</tt> decides which authentication context is
+
-
required by looking at the specified (or default) namespace in the
+
-
connection constructor call. As we specified the namespace
+
-
<tt>jabber:component:accept</tt>, the <tt>auth()</tt> method is
+
-
expecting a single argument, the secret specified in the
+
-
<tt>&lt;secret/&gt;</tt> tag of the component instance definition. The
+
-
<tt>auth()</tt> method performs the message digest function and sends
+
-
the <tt>&lt;handshake/&gt;</tt> element.
+
-
 
+
-
It's now appropriate for us to "launch" the component, with the
+
-
<tt>start()</tt> method:
+
-
 
+
-
 
+
-
<code>$c-&gt;start;
+
-
 
+
-
</code>
+
-
This is the equivalent of the <tt>MainLoop()</tt> method in Perl's Tk
+
-
library and is a method from which there's no exit. Calling
+
-
<tt>start()</tt> causes the connection object to perform an endless
+
-
loop, which internally calls a <tt>process()</tt> method on a regular
+
-
basis, receiving, examining, and dispatching elements received on the
+
-
XML stream. It also starts and maintains the ''heartbeat'', to which the
+
-
<tt>register_beat()</tt> method is related.
+
-
 
+
-
 
+
-
{{Note|If you wish to have more granular control over your script, you
+
-
can use the <tt>process()</tt> function directly, just as you would with
+
-
the <tt>Net::Jabber</tt> and <tt>Jabberpy</tt> libraries. Be aware,
+
-
however, that a heartbeat is maintained only in the context of the
+
-
<tt>start()</tt> method.
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== Handling registration requests ==== The first of the handlers
+
-
defined for <tt>&lt;iq/&gt;</tt> elements is the <tt>iq_register()</tt>
+
-
function. We put it first in the list, as we consider receipt of
+
-
<tt>&lt;iq/&gt;</tt> elements in the <tt>jabber:iq:register</tt>
+
-
namespace to be the most common. We want this function to deal with the
+
-
complete registration conversation. This means it must respond to IQ-get
+
-
and IQ-set requests.
+
-
 
+
-
 
+
-
<code>sub iq_register { my $node = shift; debug("[iq_register]");
+
-
 
+
-
</code>
+
-
The element to be handled is the primary piece of data that the
+
-
dispatcher passes to a callback. The element is received by the
+
-
<tt>$node</tt> variable, which is a <tt>Jabber::NodeFactory::Node</tt>
+
-
object. <tt>Jabber::NodeFactory</tt> is the ''wrapper'' around the class
+
-
that actually represents the elements (the nodes). Nodes are created
+
-
using the <tt>Jabber::NodeFactory</tt> class. The first thing we should
+
-
do is make sure it's appropriate to continue inside this function, which
+
-
is designed to handle only <tt>jabber:iq:register</tt>-qualified
+
-
queries. The namespace <tt>jabber:iq:register</tt> is represented with
+
-
the constant <tt>NS_REGISTER</tt>, imported from the <tt>Jabber::NS</tt>
+
-
module:
+
-
 
+
-
 
+
-
<code> return unless my $query = $node-&gt;getTag('', NS_REGISTER);
+
-
debug("--&gt; registration request");
+
-
 
+
-
</code>
+
-
The <tt>getTag()</tt> method can have up to two arguments. The first can
+
-
be used to specify the name of the tag you want to get, and the second
+
-
argument can contain a namespace to narrow down the request. For
+
-
example, there are two <tt>&lt;x/&gt;</tt> elements in this
+
-
<tt>&lt;message/&gt;</tt> element:
+
-
 
+
-
 
+
-
<code>&lt;message to='dj@qmacro.dyndns.org' from='piers@jabber.org'
+
-
id='2941'&gt; &lt;body&gt;Let me know when you're ready to
+
-
go&lt;/body&gt; &lt;x
+
-
xmlns='jabber:x:event'&gt;&lt;displayed/&gt;&lt;/x&gt; &lt;x
+
-
xmlns='jabber:x:delay' from='dj@qmacro.dyndns.org'
+
-
stamp='20010831T08:58:30'&gt;Offline Storage&lt;/x&gt; &lt;/message&gt;
+
-
</code>
+
-
We could distinguish one <tt>&lt;x/&gt;</tt> element from the other by
+
-
specifying either the <tt>jabber:x:event</tt> or the
+
-
<tt>jabber:x:delay</tt> namespace in the second argument to the
+
-
<tt>getTag()</tt> function.
+
-
 
+
-
Although the query tag is normally placed within an <tt>&lt;iq/&gt;</tt>
+
-
element that has the name "query," we see from Section 5.4.3.2 that it
+
-
''could'' be anything.
+
-
 
+
-
 
+
-
<code>$node-&gt;getTag('', NS_REGISTER)
+
-
</code>
+
-
This statement says "get a single child tag of the <tt>&lt;iq/&gt;</tt>
+
-
node—regardless of its name—and qualify it with the
+
-
<tt>jabber:iq:register</tt> namespace."
+
-
 
+
-
If we call the <tt>getTag()</tt> function in scalar context, and there
+
-
is more than one tag that matches, only the first one found will be
+
-
returned. If we call it in list context, all the matching tags are
+
-
returned. Assuming the call is successful, the variable <tt>$query</tt>
+
-
then contains the <tt>&lt;query/&gt;</tt> tag and all its subtags. So if
+
-
we received this in <tt>$node</tt>:
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq type='set' id='JCOM_5' to='rss.qmacro.dyndns.org'
+
-
from='dj@qmacro.dyndns.org/basement'&gt; &lt;query
+
-
xmlns='jabber:iq:register'&gt; &lt;text&gt;Slashdot&lt;/text&gt;
+
-
&lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
then <tt>$query</tt> would contain a <tt>Jabber::NodeFactory::Node</tt>
+
-
object that represented this bit:
+
-
 
+
-
 
+
-
<code>&lt;query xmlns='jabber:iq:register'&gt;
+
-
&lt;text&gt;Slashdot&lt;/text&gt; &lt;/query&gt;
+
-
</code>
+
-
If the <tt>jabber:iq:register</tt> namespace doesn't qualify any of the
+
-
child tags, <tt>iq_register()</tt> returns and the dispatcher calls the
+
-
next handler in line (<tt>iq_version()</tt>). However, let's assume that
+
-
we do have a registration IQ on our hands. The function must handle both
+
-
IQ-get and IQ-set. We first deal with a potential IQ-get:
+
-
 
+
-
 
+
-
<code> # Reg query if ($node-&gt;attr('type') eq IQ_GET) { $node =
+
-
toFrom($node); $node-&gt;attr('type', IQ_RESULT); my $instructions =
+
-
"Choose an RSS source from: ".join(", ", keys %sources);
+
-
$query-&gt;insertTag('instructions')-&gt;data($instructions);
+
-
$query-&gt;insertTag('text'); $c-&gt;send($node);
+
-
}
+
-
 
+
-
</code>
+
-
The <tt>attr()</tt> method called on a node will return the value of the
+
-
node's attribute of the name specified as the first argument. We test to
+
-
see if IQ's <tt>type</tt> attribute is <tt>get</tt> (<tt>IQ_GET</tt>).
+
-
If it is, we need to return an IQ-result as shown in Example 9-14.
+
-
 
+
-
Rather than create a new element from scratch, to return in response, we
+
-
simply "convert" the incoming element by making necessary changes to it,
+
-
turn it around and sent it back out as the response. The first thing we
+
-
do is swap the values for the <tt>from</tt> and <tt>to</tt> attributes
+
-
in the <tt>&lt;iq/&gt;</tt> tag (in <tt>$node</tt>) by calling the
+
-
<tt>toFrom()</tt> function (see Section 9.3.4.11) and setting the value
+
-
for the <tt>type</tt> attribute to <tt>result</tt> by calling a
+
-
two-argument version of the <tt>attr()</tt> function, which turns this:
+
-
 
+
-
 
+
-
<code>&lt;iq type='set' id='JCOM_5' to='rss.qmacro.dyndns.org'
+
-
from='dj@qmacro.dyndns.org/basement'&gt;
+
-
 
+
-
</code>
+
-
into this:
+
-
 
+
-
 
+
-
<code>&lt;iq type='result' id='JCOM_5' from='rss.qmacro.dyndns.org'
+
-
to='dj@qmacro.dyndns.org/basement'&gt;
+
-
 
+
-
</code>
+
-
Notice that the <tt>from</tt> attribute is retained. This is required as
+
-
the script is a component, and the response won't get stamped with one.
+
-
 
+
-
The instructions and an empty <tt>&lt;text/&gt;</tt> tag must be passed
+
-
back in the response. The names of the sources are combined into a list,
+
-
and an <tt>&lt;instructions/&gt;</tt> tag is inserted into the query
+
-
node (in <tt>$query</tt>) containing the text. This is done with two
+
-
method calls: the first to <tt>insertTag()</tt>, which returns a
+
-
<tt>Jabber::NodeFactory::Node</tt> object that represents the newly
+
-
inserted tag, and the second to <tt>data()</tt>, which inserts (or
+
-
retrieves) data into (or out of) a node. The line:
+
-
 
+
-
 
+
-
<code> $query-&gt;insertTag('instructions')-&gt;data($instructions);
+
-
 
+
-
</code>
+
-
could have been written as:
+
-
 
+
-
 
+
-
<code> my $instructions = $query-&gt;insertTag('instructions');
+
-
$instructions-&gt;data($instructions);
+
-
 
+
-
</code>
+
-
Once constructed, the response now looks like this:
+
-
 
+
-
 
+
-
<code>&lt;iq type='result' id='JCOM_5' from='rss.qmacro.dyndns.org'
+
-
to='dj@qmacro.dyndns.org/basement'&gt; &lt;query
+
-
xmlns='jabber:iq:register'&gt; &lt;instructions&gt; Choose an RSS source
+
-
from: jonudell, slashdot [...] &lt;/instructions&gt; &lt;text/&gt;
+
-
&lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
</code>
+
-
This is sent using the <tt>send()</tt> method of the connection object.
+
-
 
+
-
If the query wasn't an IQ-get, then it might be an IQ-set:
+
-
 
+
-
 
+
-
<code> # Reg request if ($node-&gt;attr('type') eq IQ_SET) {
+
-
 
+
-
# Strip JID to user@host
+
-
my $jid = stripJID($node-&gt;attr('from'));
+
-
 
+
-
$node = toFrom($node); my $source;
+
-
 
+
-
</code>
+
-
In this case, the user is requesting new items for an RSS source she's
+
-
specified in the <tt>&lt;text/&gt;</tt> field carried in the query part
+
-
of the IQ-set. The user's JID can be found in the <tt>from</tt>
+
-
attribute of the element, which is extracted with the <tt>attr()</tt>
+
-
method. There's one thing we should do before using that JID as a key in
+
-
storing that user's RSS source preferences. Look at what the JID was in
+
-
earlier examples:
+
-
 
+
-
 
+
-
<code> dj@qmacro.dyndns.org/basement</code> As you can see, this JID has a
+
-
<tt>resource</tt> attached to it. That's fine for returning a response
+
-
to an IQ request, but we need something less specific. The ''resource''
+
-
part of the JID reflects the client connection of the user at the time
+
-
the registration request was made. In the future, when we have an RSS
+
-
item to send to her, she might be connected with a different resource.
+
-
We want the RSS item to go to the right place, so we use only the
+
-
<tt>user</tt> and <tt>hostname</tt> JIDs to store preferences and
+
-
subsequently address the headline messages. The more generic form of the
+
-
JID can be obtained by calling the <tt>stripJID()</tt> function,
+
-
described later.
+
-
 
+
-
After swapping the <tt>from</tt> and <tt>to</tt> values, we then deal
+
-
with the two different types of IQ-set requests: a request to receive a
+
-
specific source or a request to cancel the registration:
+
-
 
+
-
 
+
-
<code> # Could be an unregister request if
+
-
($query-&gt;getTag('remove')) { delete $reg{$jid};
+
-
$node-&gt;attr('type', IQ_RESULT);
+
-
}
+
-
# Otherwise it's a registration request for a source
+
-
elsif ($source = $query-&gt;getTag('text')-&gt;data and
+
-
exists($sources{$source})) { my $element = $reg{$jid};
+
-
$element-&gt;{$source} = 1; $reg{$jid} = $element;
+
-
$node-&gt;attr('type', IQ_RESULT);
+
-
}
+
-
 
+
-
</code>
+
-
Sending a <tt>&lt;remove/&gt;</tt> tag in an IQ-set registration context
+
-
represents a request to ''unregister''. So we honor that by removing any
+
-
trace of the user's JID from the registration hash and simply changing
+
-
the <tt>type</tt> of the <tt>&lt;iq/&gt;</tt> element to
+
-
<tt>result</tt>. Otherwise, we interpret the IQ-set as a request to
+
-
subscribe to the RSS source that she's specified in the
+
-
<tt>&lt;text/&gt;</tt> tag. We extract that source's name into
+
-
<tt>$source</tt>, check that it's valid, and add a reference to the
+
-
user's list of sources in the registration hash <tt>%reg</tt>. Example
+
-
9-18 shows what the registration hash looks like.
+
-
 
+
-
 
+
-
''Typical contents of the registration hash''
+
-
 
+
-
<code>( 'dj@qmacro.dyndns.org' =&gt; { 'slashdot' =&gt; 1
+
-
}
+
-
'piers@jabber.org' =&gt; { 'jonudell' =&gt; 1 'slashdot' =&gt; 1
+
-
}
+
-
... )
+
-
 
+
-
</code>
+
-
In case you're wondering what's going on with the <tt>$element</tt>
+
-
variable, it's because of a current restriction with <tt>MLDBM</tt>.
+
-
Although it allows us to store complex structures via <tt>DB_File</tt>,
+
-
those structures can't be directly manipulated, so we have to do it via
+
-
a ''proxy'' variable, using <tt>$element</tt>. The value in the
+
-
registration hash <tt>%reg</tt> pointed to by the <tt>$jid</tt> key is a
+
-
structure. To manipulate that structure, we need to take a reference to
+
-
it, in <tt>$element</tt>. The structure can be manipulated through
+
-
<tt>$element</tt> and then placed back into the registration hash by
+
-
assigning <tt>$element</tt> as the value corresponding to the
+
-
<tt>$jid</tt> key.
+
-
 
+
-
Once this is done, we also mark the fact that the request was completed
+
-
by setting the IQ element's <tt>type</tt> attribute to <tt>result</tt>.
+
-
 
+
-
Finally, it's worth telling the requester that anything else sent isn't
+
-
allowed:
+
-
 
+
-
 
+
-
<code> else { $node-&gt;attr('type', IQ_ERROR); my $error =
+
-
$node-&gt;insertTag('error'); $error-&gt;attr('code', '405');
+
-
$error-&gt;data('Not Allowed');
+
-
}
+
-
 
+
-
</code>
+
-
That is, if we haven't understood what the IQ-set was—it wasn't a
+
-
<tt>&lt;remove/&gt;</tt> request nor was it a subscription to a source
+
-
we recognize—we simply return it with an <tt>&lt;error/&gt;</tt> tag
+
-
like this:
+
-
 
+
-
 
+
-
<code>RECV: &lt;iq type="set" id="jimAgentID657"
+
-
to="rss.qmacro.dyndns.org" from="dj@qmacro.dyndns.org/basement"&gt;
+
-
&lt;query xmlns="jabber:iq:register"&gt; &lt;text&gt;banana&lt;/text&gt;
+
-
&lt;/query&gt; &lt;/iq&gt;
+
-
 
+
-
SEND: &lt;iq id='jimAgentID657' type='error'
+
-
from='rss.qmacro.dyndns.org' to='dj@qmacro.dyndns.org/basement'&gt;
+
-
&lt;query xmlns='jabber:iq:register'&gt; &lt;text&gt;banana&lt;/text&gt;
+
-
&lt;/query&gt; &lt;error code='405'&gt;Not Allowed&lt;/error&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
</code>
+
-
The <tt>&lt;iq/&gt;</tt> type is set to <tt>error</tt> to draw the
+
-
client's attention to the <tt>&lt;error/&gt;</tt> tag. Sending an
+
-
element back in error is a great example of when reusing an incoming
+
-
element to build the outgoing response works very well; we don't have to
+
-
reproduce what the error was, as it's already contained in what we're
+
-
returning to the user.
+
-
 
+
-
In all of the IQ-set cases, we want to send something back:
+
-
 
+
-
 
+
-
<code> $c-&gt;send($node);
+
-
}
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
</code>
+
-
Note that we also return a special value, <tt>r_HANDLED</tt>. The fact
+
-
that we've got this far means that an IQ element was received and that
+
-
it was a registration-related element. It's been handled, so there's no
+
-
point in the other callbacks registered to handle IQ elements to get a
+
-
look in. So we tell the dispatcher to stop the invocation chain for the
+
-
element just processed.
+
-
 
+
-
 
+
-
 
+
-
==== Handling version requests ==== Now that we've seen the
+
-
<tt>iq_register()</tt> function, the function to handle
+
-
<tt>jabber:iq:version</tt> queries looks pretty straightforward:
+
-
 
+
-
 
+
-
<code>sub iq_version {
+
-
 
+
-
my $node = shift; debug("[iq_version]");
+
-
 
+
-
return unless my $query = $node-&gt;getTag('', NS_VERSION) and
+
-
$node-&gt;attr('type', IQ_GET);
+
-
 
+
-
debug("--&gt; version request");
+
-
 
+
-
$node = toFrom($node); $node-&gt;attr('code', IQ_RESULT);
+
-
$query-&gt;insertTag('name')-&gt;data($NAME);
+
-
$query-&gt;insertTag('version')-&gt;data($VERSION);
+
-
$query-&gt;insertTag('os')-&gt;data(`uname -sr`); $c-&gt;send($node);
+
-
 
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
</code>
+
-
Just as we check for whether the element is appropriate to handle in
+
-
<tt>iq_register()</tt>, we do here, too, this time looking for an IQ-get
+
-
with a query child tag qualified by the <tt>NS_VERSION</tt>
+
-
(<tt>jabber:iq:version</tt>) namespace, which we grab in
+
-
<tt>$query</tt>.
+
-
 
+
-
Setting the <tt>&lt;iq/&gt;</tt>'s type to <tt>result</tt> and flipping
+
-
the addresses, we then just have to add <tt>&lt;name/&gt;</tt>,
+
-
<tt>&lt;version/&gt;</tt>, and <tt>&lt;os/&gt;</tt> tags to the query
+
-
child with appropriate values, to end up with a response like the one
+
-
shown in Example 9-16.
+
-
 
+
-
If we do this, we deem the IQ to have been handled and return the
+
-
special <tt>r_HANDLED</tt> value, as before, to stop the dispatching
+
-
going any further for this element.
+
-
 
+
-
 
+
-
 
+
-
==== Handling browse requests ==== Next in line to handle the incoming
+
-
<tt>&lt;iq/&gt;</tt> element is the <tt>iq_browse()</tt> function. Of
+
-
course, if we've already handled the element, <tt>iq_browse()</tt> won't
+
-
even get a shot at responding. But if it did, it would proceed along
+
-
similar lines to the <tt>iq_version()</tt> function:
+
-
 
+
-
 
+
-
<code>sub iq_browse {
+
-
 
+
-
my $node = shift; debug("[iq_browse]");
+
-
 
+
-
return unless my $query = $node-&gt;getTag('', NS_BROWSE) and
+
-
$node-&gt;attr('type', IQ_GET);
+
-
 
+
-
debug("--&gt; browse request");
+
-
 
+
-
$node = toFrom($node); $node-&gt;attr('type', IQ_RESULT); my $rss =
+
-
$query-&gt;insertTag('service'); $rss-&gt;attr('type', 'rss');
+
-
$rss-&gt;attr('jid', $ID); $rss-&gt;attr('name', $NAME);
+
-
$rss-&gt;insertTag('ns')-&gt;data(NS_REGISTER); $c-&gt;send($node);
+
-
 
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
</code>
+
-
The only real difference is that we want this function to handle IQ-gets
+
-
in the <tt>jabber:iq:browse</tt> namespace and return a browse result.
+
-
Browsing will be discussed in greater detail in Section 10.3 in Chapter
+
-
10. For now, though, let's focus on returning a top-level browse result
+
-
that reflects what might be returned if a similar browse request were
+
-
made of the JSM, as described in Section 6.2.5 in Chapter 6. Example
+
-
9-19 shows what <tt>iq_browse()</tt> will return.
+
-
 
+
-
 
+
-
''The RSS news agent responds to jabber:iq:browse requests via
+
-
iq_browse()''
+
-
 
+
-
<code>RECV: &lt;iq type="get" id="browser_JCOM_2"
+
-
to="rss.qmacro.dyndns.org"&gt; &lt;query xmlns="jabber:iq:browse"/&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
SEND: &lt;iq id='browser_JCOM_2' type='result'
+
-
to='dj@qmacro.dyndns.org/winjab' from='rss.qmacro.dyndns.org'&gt;
+
-
&lt;query xmlns='jabber:iq:browse'&gt; &lt;service
+
-
jid='rss.qmacro.dyndns.org' type='rss' name='RSS News Agent'&gt;
+
-
&lt;ns&gt;jabber:iq:register&lt;/ns&gt; &lt;/service&gt; &lt;/query&gt;
+
-
&lt;/iq&gt;
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== Dealing with other requests ==== There are untold IQ elements that
+
-
could be sent to the component. While it would be ''possible'' just to
+
-
ignore them, we ought to do something and at least respond with
+
-
something like "not supported." For that, we have <tt>iq_notimpl()</tt>
+
-
as a catch-all. If the dispatcher manages to make its way to here, we
+
-
know that the <tt>&lt;iq/&gt;</tt> element is not anything we recognize
+
-
as wanting to respond to.
+
-
 
+
-
The following can be used to tell the requester that what they're asking
+
-
for hasn't been implemented:
+
-
 
+
-
 
+
-
<code>sub iq_notimpl { my $node = shift; $node = toFrom($node);
+
-
$node-&gt;attr('type', IQ_ERROR); my $error =
+
-
$node-&gt;insertTag('error'); $error-&gt;attr('code', '501');
+
-
$error-&gt;data('Not Implemented'); $c-&gt;send($node);
+
-
 
+
-
return r_HANDLED;
+
-
}
+
-
 
+
-
</code>
+
-
As you can see, all this does is set the <tt>&lt;iq/&gt;</tt> type to
+
-
<tt>error</tt>, switches the <tt>from</tt> and <tt>to</tt>, and adds an
+
-
<tt>&lt;error/&gt;</tt> tag:
+
-
 
+
-
 
+
-
<code>&lt;error code='501'&gt;Not Implemented&lt;/error&gt;
+
-
</code>
+
-
The modified element is sent back to the requester. This lets them know
+
-
that they've requested something that just isn't there or hasn't been
+
-
implemented yet.
+
-
 
+
-
 
+
-
 
+
-
==== The RSS mechanism ==== Now that we've set up the functions to
+
-
handle incoming queries, all that's left is for us to define what
+
-
happens every time the heartbeat in the <tt>Jabber::Connection</tt> loop
+
-
ticks past the 30-minute mark. We registered this <tt>rss()</tt>
+
-
function with the <tt>register_beat()</tt> method earlier in the script:
+
-
 
+
-
 
+
-
<code>sub rss {
+
-
 
+
-
debug("[rss]");
+
-
 
+
-
# Create NodeFactory
+
-
my $nf = new Jabber::NodeFactory;
+
-
 
+
-
</code>
+
-
In the IQ handlers <tt>iq-register()</tt>, <tt>iq-version()</tt>,
+
-
<tt>iq-browse()</tt>, and <tt>iq-notimpl()</tt>, we avoided the need
+
-
to build elements from scratch, by simply turning around the incoming
+
-
request elements and making them into responses, before sending them
+
-
back. Here, in the <tt>rss()</tt> function, we'll actually be building
+
-
elements from scratch—headline type &lt;message&gt; elements to be
+
-
precise. This is the reason we need an instance of the
+
-
<tt>Jabber::NodeFactory</tt>.
+
-
 
+
-
 
+
-
<code> # Go through each of the RSS sources foreach my $source (keys
+
-
%sources) {
+
-
 
+
-
# Retrieve attempt
+
-
my $data = get($sources{$source});
+
-
 
+
-
# Didn't get it? Next one
+
-
unless (defined($data)) { debug("cannot retrieve $source"); next;
+
-
}
+
-
 
+
-
# Parse the RSS
+
-
my $rss = XML::RSS-&gt;new(); eval { $rss-&gt;parse($data) };
+
-
 
+
-
if ($@) { debug("Problems parsing $source"); next;
+
-
}
+
-
 
+
-
</code>
+
-
The procedure in this function reflects what we described earlier in
+
-
Section 9.3.1.3. Each time <tt>rss()</tt> is called, it goes through
+
-
each of the sources defined in the list (<tt>%sources</tt>) and tries to
+
-
retrieve it with <tt>get()</tt>, a function from the
+
-
<tt>LWP::Simple</tt> library, and parse it with an instance of
+
-
<tt>XML::RSS</tt>. Because <tt>XML::RSS</tt> uses <tt>XML::Parser</tt>,
+
-
which dies if it encounters invalid XML, we wrap the call to the
+
-
<tt>parse()</tt> method in <tt>eval</tt>.
+
-
 
+
-
 
+
-
{{Note|Ideally we'd use just one instance of <tt>XML::RSS</tt> for the
+
-
whole <tt>rss()</tt> function, but <tt>XML::RSS</tt> requires us to
+
-
create a new instance for every source we wish to work with.
+
-
 
+
-
</code>
+
-
 
+
-
<code> my @items = @{$rss-&gt;{items</code>;
+
-
 
+
-
# Check new items
+
-
debug("$source: looking for new items"); foreach my $item (@items) {
+
-
 
+
-
# Stop checking if we get to items already seen
+
-
last if exists $cache{$source} and $cache{$source} eq
+
-
$item-&gt;{link};
+
-
 
+
-
debug("$source: new item $item-&gt;{title}");
+
-
 
+
-
</code>
+
-
Pulling the items from the RSS source into <tt>@items</tt>, we look
+
-
through them, but stop looking if we come across one that we've seen
+
-
previously (and stored in the <tt>%cache</tt>).
+
-
 
+
-
If we do have a new item to send out, we create a headline message
+
-
containing the item's details:
+
-
 
+
-
 
+
-
<code> # Create a headline message my $msg =
+
-
$nf-&gt;newNode('message');
+
-
 
+
-
$msg-&gt;attr('type', 'headline'); $msg-&gt;attr('from', join('@',
+
-
$source, $ID));
+
-
$msg-&gt;insertTag('subject')-&gt;data($item-&gt;{title});
+
-
$msg-&gt;insertTag('body')-&gt;data($item-&gt;{description});
+
-
 
+
-
my $xoob = $msg-&gt;insertTag('x', NS_XOOB);
+
-
$xoob-&gt;insertTag('url')-&gt;data($item-&gt;{link});
+
-
$xoob-&gt;insertTag('desc')-&gt;data($item-&gt;{description});
+
-
 
+
-
</code>
+
-
We use the node factory in <tt>$nf</tt> to create a new empty
+
-
<tt>&lt;message/&gt;</tt> element, with the <tt>newNode()</tt> method.
+
-
This element is built into a full-blown headline message with a
+
-
<tt>jabber:x:oob</tt>-qualified <tt>&lt;x/&gt;</tt> extension containing
+
-
the RSS item information. We can see that the call <tt>insertTag()</tt>
+
-
used here has two arguments. The second is used to specify an optional
+
-
namespace with which the new node (or tag) will be qualified. This call
+
-
creates a <tt>Jabber::NodeFactory::Node</tt> object in <tt>$xoob</tt>
+
-
that looks like this:
+
-
 
+
-
 
+
-
<code>&lt;x xmlns='jabber:x:oob'/&gt;
+
-
</code>
+
-
This is then embellished with the usual <tt>&lt;url/&gt;</tt> and
+
-
<tt>&lt;desc/&gt;</tt> tags. What's still missing is the address
+
-
information. We've specified the <tt>from</tt>; indeed taking a
+
-
departure from the values we've specified for the <tt>from</tt> in the
+
-
IQ responses, here we specify something slightly different with
+
-
<tt>join('@', $source, $ID)</tt>, which is a
+
-
<tt>[</tt>''user''<tt>]@[</tt>''hostname''<tt>]</tt>-style address. For
+
-
example, the source for Slashdot would be:
+
-
 
+
-
 
+
-
<code> slashdot@rss.qmacro.dyndns.org</code> This is mostly because it
+
-
conveys more information than just the component name
+
-
<tt>rss.qmacro.dyndns.org</tt> would. While the component's address
+
-
would not normally be seen by a client user in the context of the IQ
+
-
responses, many Jabber clients that support the headline message type
+
-
will show the message sender in the headline list display. You can see
+
-
this in Figure 9-8, where the From column in the headline list clearly
+
-
shows the RSS source from which the item originated. This has a little
+
-
bit of future for the component built in, too. If we wanted to extend
+
-
the component for more interaction with the clients, we could have the
+
-
client send a message to the <tt>[</tt>''RSS
+
-
source''<tt>]@[</tt>''componentname''<tt>]</tt> JID and, on receipt, the
+
-
component would immediately have context information on which source the
+
-
message was about, without the client user having to do anything other
+
-
than specify a JID.
+
-
 
+
-
 
+
-
{{Figure|title=Jarl's headline display
+
-
window|image=0596002025-jab_0908.png</code> Now that we've built the headline
+
-
message, which looks like the one in Example 9-9, we can fire it off to
+
-
each of the users who have registered for that RSS source:
+
-
 
+
-
 
+
-
<code> # Deliver to all that want RSS headlines foreach my $jid
+
-
(keys %reg) {
+
-
 
+
-
my $registration = $reg{$jid};
+
-
 
+
-
if (exists($registration-&gt;{$source})) { $msg-&gt;attr('to',
+
-
$jid); debug("delivering to $jid"); $c-&gt;send($msg);
+
-
}
+
-
 
+
-
}
+
-
 
+
-
</code>
+
-
The first time we encounter an RSS source, we won't have any record of a
+
-
"last seen" item in the <tt>%cache</tt>. So we avoid flooding people
+
-
with all the items of a new RSS source by jumping out of the item loop
+
-
if there's no cache info:
+
-
 
+
-
 
+
-
<code> # Prevent all items counted as new the first time around
+
-
last unless exists($cache{$source});
+
-
}
+
-
 
+
-
</code>
+
-
Finally, we make a mark in the cache for the latest item we've
+
-
encountered, to prepare the script for the next feed:
+
-
 
+
-
 
+
-
<code> # Remember the latest new item $cache{$source} =
+
-
$items[0]-&gt;{link};
+
-
 
+
-
}
+
-
 
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== The cleanup() function ==== The <tt>cleanup()</tt> function is
+
-
called if an attempt is made to shut the script down. It unties the
+
-
registration hash, ensuring no data is lost, and disconnects from the
+
-
Jabber server:
+
-
 
+
-
 
+
-
<code>sub cleanup {
+
-
 
+
-
debug("cleaning up"); untie %reg; $c-&gt;disconnect; exit;
+
-
 
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
==== Helper functions ==== Any script over a certain size is likely to
+
-
have some helper functions, and our RSS news agent is no exception. Here
+
-
we have the function to switch the <tt>from</tt> and <tt>to</tt>
+
-
attribute values of a node (<tt>toFrom()</tt>), the function to remove
+
-
the resource from a JID (<tt>stripJID()</tt>), and something not much
+
-
better than a debugging-style <tt>print</tt> statement:
+
-
 
+
-
 
+
-
<code>sub toFrom { my $node = shift; my $to = $node-&gt;attr('to');
+
-
$node-&gt;attr('to', $node-&gt;attr('from')); $node-&gt;attr('from',
+
-
$to); return $node;
+
-
}
+
-
 
+
-
 
+
-
sub stripJID { my $JID = shift; $JID =~ s|/.*$||; return $JID;
+
-
}
+
-
 
+
-
sub debug { print STDERR "debug: ", @_, "\n";
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
=== Further Ideas === There's only so much that can be included in a
+
-
demonstration script. There's plenty of room for improvement, even if
+
-
you don't count rewriting it all from scratch. For example, you could
+
-
store registrations in a SQL database or, alternatively, use the Jabber
+
-
server's own <tt>xdb</tt> component. More importantly, a static list of
+
-
RSS sources is rather restrictive. How about allowing the user to
+
-
register their own URLs? Or building an administrative mode that accepts
+
-
a special IQ from certain JIDs, with which the RSS source list can be
+
-
maintained?
+
-
 
+
-
The browsing response function would be an ideal candidate to be
+
-
expanded upon. Another level of browsing could be added that would
+
-
return browse items that reflect the specific user's RSS source
+
-
registrations. It's probably worth exploring the power of addressing the
+
-
component to include the RSS source, to extend the interactive
+
-
facilities. For example, you might want to have the script accept and
+
-
act upon messages sent to the component's JID, which includes a
+
-
''username'' portion representing the RSS source.
+
-
 
+
-
 
+
-
 
+
-
== A Simple Headline Viewer == Let's close this chapter on a lighter
+
-
note by building a complementary script that we can use to keep an eye
+
-
on the headlines coming in from the RSS news agent. We'll call this
+
-
program ''hlv'', for "HeadLine Viewer." Basically, we want to build
+
-
something that allows the user to watch RSS headlines as they scroll by
+
-
in a window that doesn't take up a lot of screen real estate.
+
-
 
+
-
In the recipes so far, we've had great success building solutions that
+
-
make use of off-the-shelf clients. But it's time to buck the trend,
+
-
indeed to make another point. While you can arrange the features of your
+
-
Jabber-based applications in the direction of standard clients, to take
+
-
advantage of the installed base of Jabber clients, if you do want to
+
-
create a client that works differently, a client that fits your needs
+
-
exactly, then go ahead—it will be surprisingly straightforward. The
+
-
mantra of "server side complex, client side simple" (with apologies to
+
-
George Orwell) is there to help us. What's more, we can put into action
+
-
an idea expressed earlier in the book (in Section 2.7):
+
-
 
+
-
<blockquote>A Jabber client is a piece of software that implements as
+
-
much of the Jabber protocol as required to get the job done.
+
-
 
+
-
</blockquote>If we're going to build a headline viewer client and know
+
-
that the information is going to be delivered to us in headline-type
+
-
<tt>&lt;message/&gt;</tt> elements, why have the viewer client
+
-
understand or deal with anything else? To implement a Jabber solution,
+
-
we pick and choose the parts of the protocol that make sense in the
+
-
context of that solution. If you want to transport RPC calls in data
+
-
payloads between two endpoints, why bother with roster management or
+
-
rich-text chat facilities? If you just want to join a conference room to
+
-
talk with the other room occupants, why bother with threaded one-on-one
+
-
conversations? If you need routing and presence capabilities to have
+
-
your oven know when you've arrived home, why engineer anything else?
+
-
 
+
-
What we're going to write here is a simple headline viewer. Nothing
+
-
more, nothing less. It will know the tiniest thing about presence—as the
+
-
headlines come in as <tt>&lt;message/&gt;</tt> elements, it will need to
+
-
announce its availability to the JSM. We're going to build a Jabber
+
-
client that will have a session context with the JSM, so we need to tell
+
-
the JSM that we're available when the client starts. Otherwise, the
+
-
headlines will be queued by the store-and-forward mechanism for delivery
+
-
the next time we're available.
+
-
 
+
-
We'll leave registration to the RSS news agent to another client that
+
-
knows about agents (services) and can interact in a
+
-
<tt>jabber:iq:register</tt> kind of way. Again, the "one size fits all,
+
-
one client for everything" philosophy doesn't always have to apply;
+
-
different features of different programs can get the job done. So while
+
-
the headline viewer will receive, understand, and display the message
+
-
headlines, we'll use WinJab or even JIM to manage the RSS source
+
-
subscriptions. Figure 9-9 illustrates the process of registration with
+
-
the RSS news agent component, using JIM.
+
-
 
+
-
 
+
-
{{Figure|title=Registering with the RSS News Agent, using
+
-
JIM|image=0596002025-jab_0909.png</code> The suggestion of JIM as a client to
+
-
complement or make up for the lack of features in the headline viewer is
+
-
deliberately ironic. The role JIM plays is to provide support for core
+
-
Jabber client features, of which headline messages are not considered to
+
-
be a part. So while JIM can interact with services and register (and
+
-
unregister—which will send the <tt>&lt;remove/&gt;</tt> tag in the
+
-
query, as described in Section 9.3.4.5), it doesn't handle headline-type
+
-
messages, which is perfectly fine. Our headline viewer won't handle chat
+
-
or normal messages—it's not ''supposed'' to.
+
-
 
+
-
It's worth pointing out that another reason the headline viewer client
+
-
can remain simple is because the RSS news agent will be doing all the
+
-
hard work. Unlike other (non-Jabber) headline viewer programs, ''hlv''
+
-
depends upon the RSS news agent. It's that component that will maintain
+
-
the list of RSS sources. It's also that component that will retrieve
+
-
those sources at regular intervals and check for new items. All we have
+
-
to do is sit back and have those new items pushed to us, at which point
+
-
the client has to make a slight effort to insert the details of those
+
-
new items into the viewer display.
+
-
 
+
-
 
+
-
 
+
-
=== What the Headline Viewer Is Going to Do === The headline viewer,
+
-
shown in Figure 9-10, has a scrollable area where headlines are
+
-
displayed. We can ''clear'' that area, or select a headline and call up
+
-
a web browser to ''fetch'' the story by passing the URL to it.
+
-
 
+
-
 
+
-
{{Figure|title=The Headline Viewer
+
-
client|image=0596002025-jab_0910.png</code> It's also nice and small,
+
-
visually and in the amount of code we're going to have to write. We
+
-
connect to the Jabber server, set up a handler for the incoming headline
+
-
messages, build the display, send our availability, and sit back and
+
-
watch the news roll in.
+
-
 
+
-
Actually, we need to say a few things about the "sitting back" bit. We
+
-
know that Jabber programming implies an event model. For this example,
+
-
we're going to use Tk, a widget library for building GUI applications,
+
-
with bindings for many languages. Tk itself has an event model, which in
+
-
many ways reflects Jabber's. Table 9-3 shows how Jabber and Tk relate to
+
-
each other in this programming model.
+
-
 
+
-
 
+
-
{|
+
-
 
+
-
|+ Jabber and Tk event model reflections -
+
-
! Jabber !! Tk
+
-
|-
+
-
| Establishing connection to server || Constructing widgets
+
-
|-
+
-
| Defining callbacks to handle incoming elements || Defining callbacks
+
-
| to handle UI events
+
-
|-
+
-
| Setting a "heartbeat" function<ref>See Section 9.3.4.3. </ref> ||
+
-
| Setting a command to execute regularly with the <tt>repeat()</tt>
+
-
| function
+
-
|-
+
-
| Launching the event loop || Starting <tt>MainLoop()</tt>
+
-
|}
+
-
Having one program governed by two independent event loops is not
+
-
what we want to try to achieve. We want Jabber's and Tk's event
+
-
models to cooperate. This is achievable by using a master/slave
+
-
relationship between the two models. Tk's <tt>repeat()</tt> method
+
-
can be used to invoke a function that calls the Jabber library's
+
-
<tt>process()</tt> method. We can hand over control to Tk with
+
-
<tt>MainLoop()</tt> and know that the Jabber event model will get a
+
-
look in because of the Tk event callback we've defined with
+
-
<tt>repeat()</tt>.
+
-
 
+
-
 
+
-
 
+
-
=== The hlv Script === The ''hlv'' script, shown in Example 9-20, uses
+
-
Perl with the <tt>Net::Jabber</tt> library.
+
-
 
+
-
 
+
-
''The hlv script, written in Perl''
+
-
 
+
-
<code>use Tk; use Net::Jabber qw(Client); use strict;
+
-
 
+
-
use constant SERVER =&gt; 'gnu.pipetree.com'; use constant PORT
+
-
=&gt; 5222; use constant USER =&gt; 'dj'; use constant PASSWORD
+
-
=&gt; 'secret'; use constant RESOURCE =&gt; 'hlv';
+
-
 
+
-
use constant BROWSER =&gt; '/usr/bin/konqueror';
+
-
 
+
-
my @headlines; my @list;
+
-
 
+
-
my $connection = Net::Jabber::Client-&gt;new();
+
-
 
+
-
$connection-&gt;Connect( hostname =&gt; SERVER, port =&gt; PORT, )
+
-
or die "Cannot connect ($!)\n";
+
-
 
+
-
my @result = $connection-&gt;AuthSend( username =&gt; USER, password
+
-
=&gt; PASSWORD, resource =&gt; RESOURCE, );
+
-
 
+
-
if ($result[0] ne "ok") { die "Ident/Auth with server failed: $result[0]
+
-
- $result[1]\n";
+
-
}
+
-
 
+
-
$connection-&gt;SetCallBacks( message =&gt; \&amp;handle_message );
+
-
 
+
-
my $main = MainWindow-&gt;new( -title =&gt; "Headline Viewer" );
+
-
$main-&gt;geometry('50x5+10+10'); $main-&gt;repeat(5000,
+
-
\&amp;check_headlines);
+
-
 
+
-
# Button frame
+
-
my $buttons = $main-&gt;Frame(); $buttons-&gt;pack(qw/-side bottom -fill
+
-
x/);
+
-
 
+
-
# Headline list
+
-
my $list = $main-&gt;Scrolled(qw/Listbox -scrollbars e -height 40
+
-
-setgrid 1/);
+
-
 
+
-
# Clear button
+
-
my $button_clear = $buttons-&gt;Button( -text =&gt; 'Clear',
+
-
-underline =&gt; '0', -command =&gt; sub { @list = ();
+
-
$list-&gt;delete(0, 'end')
+
-
},
+
-
);
+
-
 
+
-
# Fetch Button
+
-
my $button_fetch = $buttons-&gt;Button( -text =&gt; 'Fetch',
+
-
-underline =&gt; '0', -command =&gt; sub { system( join(" ", (BROWSER,
+
-
$list[$list-&gt;curselection], "&amp;")) )
+
-
},
+
-
);
+
-
 
+
-
# Exit button
+
-
my $button_exit = $buttons-&gt;Button( -text =&gt; 'exit',
+
-
-underline =&gt; '0', -command =&gt; [$main =&gt; 'destroy'], );
+
-
 
+
-
 
+
-
$button_clear-&gt;pack(qw/-side left -expand 1/);
+
-
$button_fetch-&gt;pack(qw/-side left -expand 1/);
+
-
$button_exit-&gt;pack(qw/-side left -expand 1/);
+
-
 
+
-
$list-&gt;pack(qw/-side left -expand 1 -fill both/);
+
-
 
+
-
$connection-&gt;PresenceSend();
+
-
 
+
-
MainLoop();
+
-
 
+
-
$connection-&gt;Disconnect; exit(0);
+
-
 
+
-
sub check_headlines { $connection-&gt;Process(1); while (@headlines) {
+
-
my $headline = pop @headlines; $list-&gt;insert(0,
+
-
$headline-&gt;{title}); unshift @list, $headline-&gt;{link};
+
-
}
+
-
}
+
-
 
+
-
sub handle_message { my $msg = new Net::Jabber::Message($_[1]); return
+
-
unless $msg-&gt;GetType eq 'headline';
+
-
 
+
-
my ($oob) = $msg-&gt;GetX('jabber:x:oob'); push @headlines, { link
+
-
=&gt; $oob-&gt;GetURL(), title =&gt; $msg-&gt;GetSubject(),
+
-
};
+
-
}
+
-
 
+
-
</code>
+
-
 
+
-
 
+
-
=== Reviewing the hlv Script Step by Step === The script starts with the
+
-
declarations of the libraries we're going to use, along with some
+
-
constants:
+
-
 
+
-
 
+
-
<code>use Tk; use Net::Jabber qw(Client); use strict;
+
-
 
+
-
use constant SERVER =&gt; 'gnu.pipetree.com'; use constant PORT
+
-
=&gt; 5222; use constant USER =&gt; 'dj'; use constant PASSWORD
+
-
=&gt; 'secret'; use constant RESOURCE =&gt; 'hlv';
+
-
 
+
-
use constant BROWSER =&gt; '/usr/bin/konqueror';
+
-
 
+
-
</code>
+
-
The ''hlv'' script will connect to Jabber as a client, so we need to
+
-
specify that in the <tt>use</tt> statement to have the appropriate
+
-
<tt>Net::Jabber</tt> modules loaded. We're going to be connecting to the
+
-
Jabber server at gnu.pipetree.com, although, as we said, the RSS news
+
-
agent might live somewhere else. It just so happens that in this
+
-
scenario, there's a reference to the component in gnu.pipetree.com's JSM
+
-
<tt>&lt;browse/&gt;</tt> section, so we can carry out registration
+
-
conversations with it (using JIM, for example).
+
-
 
+
-
If the Fetch button is clicked when an item in the list is selected (see
+
-
Figure 9-10), we want to jump to the story by launching a web browser.
+
-
The constant <tt>BROWSER</tt> used here refers to the browser—in this
+
-
case, Konqueror, the browser of choice for KDE users—on the local
+
-
machine.
+
-
 
+
-
 
+
-
<code>my @headlines; my @list;
+
-
 
+
-
</code>
+
-
We declare two arrays: <tt>@headlines</tt>, which we'll use to hold the
+
-
items as they arrive contained in the headline <tt>&lt;message/&gt;</tt>
+
-
elements on the XML stream, and <tt>@list</tt>, to hold the URLs that
+
-
relate to those items in <tt>@headlines</tt>.
+
-
 
+
-
After connecting to and authenticating with the Jabber server, we set up
+
-
the callback to take care of incoming <tt>&lt;message/&gt;</tt> elements
+
-
(this is very similar to the way the coffee monitor script connects and
+
-
authenticates in Section 9.2.3.5):
+
-
 
+
-
 
+
-
<code>my $connection = Net::Jabber::Client-&gt;new();
+
-
 
+
-
$connection-&gt;Connect( hostname =&gt; SERVER, port =&gt; PORT, )
+
-
or die "Cannot connect ($!)\n";
+
-
 
+
-
my @result = $connection-&gt;AuthSend( username =&gt; USER, password
+
-
=&gt; PASSWORD, resource =&gt; RESOURCE, );
+
-
 
+
-
if ($result[0] ne "ok") { die "Ident/Auth with server failed: $result[0]
+
-
- $result[1]\n";
+
-
}
+
-
 
+
-
</code>
+
-
This is the <tt>handle_message()</tt> function:
+
-
 
+
-
 
+
-
<code>$connection-&gt;SetCallBacks( message =&gt; \&amp;handle_message
+
-
);
+
-
 
+
-
</code>
+
-
Now it's time to build the GUI. We start by creating a main window,
+
-
giving it a title and geometry, and establishing the cooperation between
+
-
the two event models with the <tt>repeat()</tt> method:
+
-
 
+
-
 
+
-
<code>my $main = MainWindow-&gt;new( -title =&gt; "Headline Viewer" );
+
-
$main-&gt;geometry('50x5+10+10'); $main-&gt;repeat(5000,
+
-
\&amp;check_headlines);
+
-
 
+
-
</code>
+
-
The <tt>repeat()</tt> function will arrange Tk's main event loop to
+
-
hiccup every five seconds (the first argument is measured in
+
-
milliseconds) and call the <tt>check_headlines()</tt> function.
+
-
 
+
-
Next, we build a frame to hold three buttons—Clear, Fetch, and Exit—and
+
-
a scrollable list to contain the titles as they're received:
+
-
 
+
-
 
+
-
<code># Button frame my $buttons = $main-&gt;Frame();
+
-
$buttons-&gt;pack(qw/-side bottom -fill x/);
+
-
 
+
-
# Headline list
+
-
my $list = $main-&gt;Scrolled(qw/Listbox -scrollbars e -height 40
+
-
-setgrid 1/);
+
-
 
+
-
</code>
+
-
Defining the buttons, one at a time, brings our attention to the Tk UI
+
-
event model, in that we define the handlers using the <tt>-command</tt>
+
-
argument of the <tt>Button()</tt> method. The handlers' jobs are quite
+
-
small, so we can get away with writing them inline:
+
-
 
+
-
 
+
-
<code># Clear button my $button_clear = $buttons-&gt;Button( -text
+
-
=&gt; 'Clear', -underline =&gt; '0', -command =&gt; sub { @list = ();
+
-
$list-&gt;delete(0, 'end')
+
-
},
+
-
);
+
-
 
+
-
</code>
+
-
If called, the Clear button clears the scrollable display by calling the
+
-
<tt>delete()</tt> method on the <tt>$list</tt> object and emptying the
+
-
corresponding array of URLs.
+
-
 
+
-
The Fetch button extracts the URL from the highlighted item in the
+
-
scrollable list using the <tt>curselection()</tt> method to retrieve the
+
-
index value. The <tt>@list</tt> array is then used to look up the URL by
+
-
launching the browser (in this case, Konqueror) in the background. Many
+
-
browsers accept a URL as the first argument; if your choice of browser
+
-
doesn't, you'll need to modify this call slightly.
+
-
 
+
-
 
+
-
<code># Fetch Button my $button_fetch = $buttons-&gt;Button( -text
+
-
=&gt; 'Fetch', -underline =&gt; '0', -command =&gt; sub { system(
+
-
join(" ", (BROWSER, $list[$list-&gt;curselection], "&amp;")) )
+
-
},
+
-
);
+
-
 
+
-
</code>
+
-
If clicked, the Exit button uses a <tt>destroy()</tt> function to, well,
+
-
destroy (or close) the main window. This causes Tk's main event loop to
+
-
come to an end, passing control back to the statement in the script
+
-
following the one with which that main event loop was launched (with
+
-
<tt>MainLoop()</tt>):
+
-
 
+
-
 
+
-
<code># Exit button my $button_exit = $buttons-&gt;Button( -text
+
-
=&gt; 'Exit', -underline =&gt; '0', -command =&gt; [$main =&gt;
+
-
'destroy'], );
+
-
 
+
-
</code>
+
-
Having created all the buttons and packed everything into the window
+
-
with the <tt>pack()</tt> method:
+
-
 
+
-
 
+
-
<code>$button_clear-&gt;pack(qw/-side left -expand 1/);
+
-
$button_fetch-&gt;pack(qw/-side left -expand 1/);
+
-
$button_exit-&gt;pack(qw/-side left -expand 1/);
+
-
 
+
-
$list-&gt;pack(qw/-side left -expand 1 -fill both/);
+
-
 
+
-
</code>
+
-
we announce to the JSM that we're available:
+
-
 
+
-
 
+
-
<code>$connection-&gt;PresenceSend();
+
-
 
+
-
</code>
+
-
All that remains for us to do is start Tk's main event loop. We include
+
-
a call to the <tt>Net::Jabber</tt><tt>Disconnect()</tt> method for when
+
-
the Exit button is clicked and control returns to the script, so we can
+
-
gracefully end the Jabber connection:
+
-
 
+
-
 
+
-
<code>MainLoop();
+
-
 
+
-
$connection-&gt;Disconnect; exit(0);
+
-
 
+
-
</code>
+
-
We defined the <tt>check_headlines()</tt> function as the function to
+
-
invoke every five seconds:
+
-
 
+
-
 
+
-
<code>sub check_headlines { $connection-&gt;Process(1); while
+
-
(@headlines) { my $headline = pop @headlines; $list-&gt;insert(0,
+
-
$headline-&gt;{title}); unshift @list, $headline-&gt;{link};
+
-
}
+
-
}
+
-
 
+
-
</code>
+
-
To check for any messages that have arrived on the XML stream, we can
+
-
call the <tt>Process()</tt> method on the connection object. If there
+
-
are any messages waiting, the <tt>handle_message()</tt> function is
+
-
called to handle them:
+
-
 
+
-
 
+
-
<code>sub handle_message { my $msg = new Net::Jabber::Message($_[1]);
+
-
return unless $msg-&gt;GetType eq 'headline';
+
-
 
+
-
my ($oob) = $msg-&gt;GetX('jabber:x:oob'); push @headlines, { link
+
-
=&gt; $oob-&gt;GetURL(), title =&gt; $msg-&gt;GetSubject(),
+
-
};
+
-
}
+
-
 
+
-
</code>
+
-
We can see fairly easily what the <tt>GetX()</tt> method does, if we
+
-
remember that a headline message, complete with an <tt>&lt;x/&gt;</tt>
+
-
extension qualified by the <tt>jabber:x:oob</tt> namespace, looks like
+
-
this:
+
-
 
+
-
 
+
-
<code>&lt;message type='headline' to='dj@qmacro.dyndns.org'&gt;
+
-
&lt;subject&gt;JabberCon Update 11:45am - Aug 20&lt;/subject&gt;
+
-
&lt;body&gt;JabberCon Update - Monday Morning&lt;/body&gt; &lt;x
+
-
xmlns='jabber:x:oob'&gt;
+
-
&lt;url&gt;http://www.jabbercentral.com/news/view.php?news_id=998329970&
+
-
lt;/url&gt; &lt;desc&gt;JabberCon Update - Monday Morning&lt;/desc&gt;
+
-
&lt;/x&gt; &lt;/message&gt;
+
-
 
+
-
</code>
+
-
It returns, in list context, all the <tt>&lt;x/&gt;</tt> elements
+
-
contained in the element represented by <tt>$msg</tt> that are qualified
+
-
by the <tt>jabber:x:oob</tt> namespace. We're expecting there to be only
+
-
one, which is why we plan to throw all but the first array item away
+
-
with the <tt>($oob)</tt> construction. After the call to
+
-
<tt>GetX()</tt>, the object in <tt>$oob</tt> represents this part of the
+
-
message:
+
-
 
+
-
 
+
-
<code>&lt;x xmlns='jabber:x:oob'&gt;
+
-
&lt;url&gt;http://www.jabbercentral.com/news/view.php?news_id=998329970&
+
-
lt;/url&gt; &lt;desc&gt;JabberCon Update - Monday Morning&lt;/desc&gt;
+
-
&lt;/x&gt;
+
-
 
+
-
</code>
+
-
The item's details, including the URL and title, are pushed onto the
+
-
<tt>@headlines</tt> list, and the headline-type message-handling
+
-
function has done its job. Control passes back to the
+
-
<tt>check_headlines()</tt> script to immediately after the call to the
+
-
<tt>Process()</tt> method.
+
-
 
+
-
The <tt>handle_message()</tt> function may have been called multiple
+
-
times, depending on how many elements had arrived, so the
+
-
<tt>@headlines</tt> array might contain more than one item. We run
+
-
through the array, sending off each headline in turn, inserting the
+
-
title into the scrollable list object and the URL into the corresponding
+
-
position in the <tt>@list</tt> array:
+
-
 
+
-
 
+
-
<code>$list-&gt;insert(0, $headline-&gt;{title}); unshift @list,
+
-
$headline-&gt;{link};
+
-
 
+
-
</code>
+
-
That's really all there is to the code. The features are minimal, but
+
-
the <tt>hlv</tt> script gets the job done. There's just enough
+
-
programming to be able to receive and display RSS items.
+
-
 
+
-
The "just enough" philosophy demonstrated in this recipe can be
+
-
appropriate in non-GUI applications of Jabber as well. Taking away the
+
-
visual element of <tt>hlv</tt> to leave the mechanism for receiving and
+
-
understanding <tt>jabber:x:oob</tt> information, we're left with a
+
-
Jabber-aware "stub" that can be put to many uses; collection and
+
-
aggregation of news stories comes immediately to mind. Along the same
+
-
lines, we can see that the way we gave presence to the coffee pot in
+
-
Section 9.2 was just a melding of a stub, which understood Jabber
+
-
presence, with a loop mechanism that connected to the MINDSTORMS device.
+
-
 
+
-
It's possible to build extremely powerful solutions using Jabber's
+
-
''protocol'' and the building blocks that the protocol represents
+
-
(described in Chapter 6), without having any relation to a user
+
-
interface. We'll see such a solution in Section 10.2.
+
-
 
+
-
</content></chapter>
+

Current revision

kid volunteer work for animal shelters in nj yakima jobs lindy hop anzac day trading times personal loans leeds links bahamas five star hotels natural gas stocks gratis dating no credit age of mythology the titans no cd crack activemark crack indiana high school athletic association football rules panty job compilation wsfs online banking presidential campaign spend way too much money cigna healthcare fee schedule url phone student teacher video philadelphia eagles trade black bitchs club penguin money maker how to download it pamerla anderson videos sitemap free printable gift card caesars palace hotel deals tamil nadu tourist board home down payment loan uk bank holiday pooh bear video buying ex rental cars advance tubo merida yucatan free interracial blow jobs how to start a limo business foo fighters best of you lyrics perry mason videos cum pot obtine un credit daca sunt rau sitemap application letter healthcare album king t.i torrent caballeros del zodiaco pam and tommy lee video free index domain master clock bozeman american west airlines interior design master bedroom elmo birthday cards best deal for hotels in orlando public health job pinback videos

Programming Jabber
Preface
1 - Getting started with Jabber
2 - Inside Jabber
3 - Installing the Jabber server
4 - Server Architecture and Configuration
5 - Jabber Technology Basics
6 - Jabber Namespaces
7 - User Registration and Authorization
8 - Using Messages and Presence
9 - Groupchat, : Components, and Event Models
10 - Pointers for Further Development
Appendix A
Appendix B
Colophon

Groupchat, : Components, and Event Models


By now, you should have a

good idea of how scripts interact with Jabber and how the core
elements such as <message/> and
<presence/> can be constructed and handled.

This chapter builds upon what we've already seen in Chapter 8 and introduces new concepts. We build a nosy assistant that joins a conference room and alerts us to words and phrases that we want it to listen for. There are two popular conference protocols, as mentioned in Section 6.2.6 the presence-based Groupchat protocol, and the jabber:iq:conference-based Conference protocol. The assistant recipe, a foray into the world of 'bots, takes a look at the original presence-based one.

As we've seen, programming within Jabber's event model is fairly straightforward. But what happens when you want to meld other components with event models of their own? We look at a couple of typical scenarios where this melding needs to happen. The first is a homage to the Trojan Room Coffee Machine (http://www.cl.cam.ac.uk/coffee/coffee.html), where we give life, or at least presence, to a coffeepot, using LEGO MINDSTORMS. The second is a Tk-based RSS headline viewer. Both the coffeepot and the Tk programming library have event loops of their own. With the coffeepot, we need to have a loop that polls the coffeepot's status, independently of the polling for incoming packets from the Jabber server. The Tk programming library's event model is similar to those of the Jabber programming libraries that are used in the recipes in this book, in that handlers are set up and a loop is started that listens for UI events. In both cases, we need to get these event loops to work in harmony with the Jabber libraries' event loops.

Building Jabber solutions without event loops is a Sisyphean task. The very nature of Jabber communication is event-based, and it's important to understand how to use the event features of the Jabber programming libraries and also how to be able to mesh those features with similar features in other libraries and systems.

We also look at extending messages and build a mechanism that delivers RSS headlines to clients who register with that mechanism. These headlines are carried using an extended message type. In fact, the RSS Delivery Mechanism is a component. The three recipes in Chapter 8, were Jabber clients, in that they connected to the Jabber network via the Jabber Session Manager (JSM) service. We look at the differences between programming a client and programming a component in this chapter and build a complete component that can be queried and interacted with using the third of Jabber's building blocks the <iq/> element.

Happy coding!


Keyword Assistant


Many of the Jabber core and peripheral developers hang out in a conference room called jdev hosted by the Conferencing component on the Jabber server running at jabber.org. While a lot of useful information is to be gleaned from listening to what goes on in jdev, it isn't possible to be there all the time. Conversations in jdev are logged to web pages, which can be used to visit after the fact to try to catch up with things; however, this can be a hopeless task. One solution is to build a 'bot that looks for specific keywords and Uniform Resource Locators (URLs) in the conversations in jdev and send those on as Jabber messages.

This script, keyassist, connects to a Jabber server, enters a conference room, and listens to the conversations, looking for certain words and phrases to be uttered. The keyassist script is given a bit of "intelligence" in that it can be interacted with and told, while running, to watch for (or stop watching for), certain words and phrases.

The keyassist script introduces us to programmatic interaction with the Conferencing component. Before looking at the script, however, let's have a brief overview of Conferencing in general.



Conferencing


The Conferencing component at jabber.org is conference.jabber.org. Details of the component instance configuration for such a Conferencing component can be found in Section 4.10, where we see that the component exists as a shared object library connected with the library load component connection method. This component provides general conferencing facilities, oriented around a conference room and conference user model.

A Jabber user can enter (or join) a conference room, thereby becoming a conference user identified by a nickname that is chosen upon entering that room. Nicknames are generally used in conference rooms to provide a modicum of privacy it is assumed that by default you don't want to let the other conference room members know your real JID.

The Conferencing component supports two protocols for user and room interaction: a simple one that provides basic features and a more complex one that provides the basic features plus facilities such as password-protected rooms and room descriptions Groupchat and Conference.


{{Note|There is a third protocol, called Experimentaliq:groupchat, which came between the Groupchat and Conference protocols. This reflected an experimental move to add features to the basic Groupchat protocol using IQ elements, the contents of which were qualified by a namespace jabber:iq:groupchat. This protocol has been dropped, and support for it exists only in certain versions of WinJab and JIM.

</code>

Groupchat
The Groupchat protocol is the simpler of the two and provides basic
functions for entering and exiting conference rooms and choosing
nicknames. : This Groupchat protocol is known as the presence-based
protocol, because the protocol is based upon
<presence/> elements used for room entry, exit, and
nickname determination. The Groupchat protocol has a nominal version
number of 1.0.
Conference
The Conference protocol offers more advanced features than the
Groupchat protocol and makes use of two IQ namespaces:
jabber:iq:conference and jabber:iq:browse. It has a
nominal protocol version number of 1.4, which reflects the version of
the Jabber server with which it is delivered. Sometimes this version
number is referred to as 0.4, such as in the downloadable tarball and
in the value returned in response to a "version query" on the
component itself, as shown in Example 9-1. : The version number isn't
that important. The main thing to keep in mind is that the component
that is called conference.so (see the reference to the
shared object library in Section 4.10.4) supports both the
Groupchat protocol and the Conference protocol. If you come across a
shared object library called groupchat.so, this is the
original Conferencing component that was made available with Jabber
server Version 1.0. This library supports only the Groupchat protocol.

Querying the Conferencing component's version

SEND: <iq type='get' to='conference.gnu.mine.nu'> <query xmlns='jabber:iq:version'/> </iq>

RECV: <iq to='dj@gnu.mine.nu/jarl' from='conference.gnu.mine.nu' type='result'> <query xmlns='jabber:iq:version'> <name>conference</name> <version>0.4</version> <os>Linux 2.4.2-2</os> </query> </iq> In this recipe we'll be using the simpler Groupchat protocol. It's widely used and easy to understand. Example 9-2 shows a typical element log from Groupchat-based activity. It shows a user, with the JID qmacro@jabber.com, entering a room called "cellar," hosted on the conference component at conf.merlix.dyndns.org, a room that currently has two other occupants who go by the nicknames flash and roscoe. The elements are from qmacro's perspective, and are all explained following the example.


The Groupchat protocol in action The user qmacro tries to enter the conference room with the nickname flash and fails:


SEND: <presence to='cellar@conf.merlix.dyndns.org/flash'/>

RECV: <presence to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/flash' type='error'> <error code='409'>Conflict</error> </presence>

He tries again, this time with a different nickname, deejay, and is successful:


SEND: <presence to='cellar@conf.merlix.dyndns.org/deejay'/>

RECV: <presence to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/flash'/>

RECV: <presence to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/roscoe'/>

RECV: <presence to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/deejay'/>

RECV: <message to='qmacro@jabber.com/jarltk' type='groupchat' from='cellar@conf.merlix.dyndns.org'> <body>deejay has become available</body> </message>

roscoe says hi, and qmacro waves back:


RECV: <message to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/roscoe' type='groupchat' cnu=> <body>hi</body> </message>

SEND: <message to='cellar@conf.merlix.dyndns.org' type='groupchat'> <body>/me waves to everyone</body> </message>

flash sends a private message to qmacro:


RECV: <message to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/flash' type='chat'> <body>Is that you, qmacro?</body> <thread>jarl1998911094</thread> </message>

Feeling left out of the conversation, roscoe leaves the room:


RECV: <presence to='qmacro@jabber.com/jarltk' type='unavailable' from='cellar@conf.merlix.dyndns.org/roscoe'/>

RECV: <message to='qmacro@jabber.com/jarltk' type='groupchat' from='cellar@conf.merlix.dyndns.org'> <body>roscoe has left</body> </message> Let's take the stages in Example 9-2 one by one.


Failed attempt to enter room
qmacro makes an attempt to enter the room using the Groupchat
protocol. This is done by sending a directed
<presence/> element to a particular JID that represents
the room and the chosen nickname. This JID is constructed as follows:

[room name]@[conference component]/[nickname]

In this example, the conferencing component is identified with the
hostname conf.merlix.dyndns.org. qmacro's choice of
nickname is flash:

cellar@conf.merlix.dyndns.org/flash

Thus the following element is sent:

SEND: <presence to='cellar@conf.merlix.dyndns.org/flash'/>

The conference component determines that there is already someone
present in the room cellar@conf.merlix.dyndns.org with the
nickname flash, so qmacro is notified of this and
receives a directed presence with an <error/> tag:

RECV: <presence to='qmacro@jabber.com/jarltk' from='cellar@conf.merlix.dyndns.org/flash' type='error'> <error code='409'>Conflict</error> </presence>

Note that the <presence/> element has the type
error and comes from the artificial JID constructed in the
room entry attempt. The element is addressed to qmacro's real
JID, of courseqmacro@jabber.com/jarltk as otherwise it
wouldn't reach him. : The error code 409 and text "Conflict" tells
qmacro that the nickname conflicted with one already in the
room. This is a standard error code/text pair; Table 5-3 shows a
complete set of code/text pairs. : At this stage, qmacro is
not yet in the room.
Successful attempt to enter room
qmacro tries again, this time with a different nickname,
deejay:[1]

SEND: <presence to='cellar@conf.merlix.dyndns.org/deejay'/>

This time, there is no conflict
Personal tools