| 1 |
#!/usr/bin/env python |
|---|
| 2 |
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 3 |
##~ License |
|---|
| 4 |
##~ |
|---|
| 5 |
##- The RuneBlade Foundation library is intended to ease some |
|---|
| 6 |
##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.) |
|---|
| 7 |
##- applications, while providing the flexibility to modularly change the |
|---|
| 8 |
##- architecture. Enjoy. |
|---|
| 9 |
##~ |
|---|
| 10 |
##~ Copyright (C) 2002 TechGame Networks, LLC. |
|---|
| 11 |
##~ |
|---|
| 12 |
##~ This library is free software; you can redistribute it and/or |
|---|
| 13 |
##~ modify it under the terms of the BSD style License as found in the |
|---|
| 14 |
##~ LICENSE file included with this distribution. |
|---|
| 15 |
##~ |
|---|
| 16 |
##~ TechGame Networks, LLC can be reached at: |
|---|
| 17 |
##~ 3578 E. Hartsel Drive #211 |
|---|
| 18 |
##~ Colorado Springs, Colorado, USA, 80920 |
|---|
| 19 |
##~ |
|---|
| 20 |
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 21 |
|
|---|
| 22 |
"""Conference implementation for Jabber conferencing |
|---|
| 23 |
|
|---|
| 24 |
The Jabber protocol can be found at http://jabber.org, |
|---|
| 25 |
and at the time of writting, specifically at |
|---|
| 26 |
http://docs.jabber.org/general/html/protocol.html |
|---|
| 27 |
|
|---|
| 28 |
Specifically, this module relates to jabber:iq:conference. |
|---|
| 29 |
""" |
|---|
| 30 |
|
|---|
| 31 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 32 |
#~ Imports |
|---|
| 33 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 34 |
|
|---|
| 35 |
import weakref |
|---|
| 36 |
import JID |
|---|
| 37 |
import JabberObserver as JObs |
|---|
| 38 |
from JabberSubject import JabberSubjectBase |
|---|
| 39 |
from SubjectObserver.Observer import Observer |
|---|
| 40 |
from SubjectObserver.AttributedSubject import AttributedSubject |
|---|
| 41 |
|
|---|
| 42 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 43 |
#~ Definitions |
|---|
| 44 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 45 |
|
|---|
| 46 |
class Conference(JabberSubjectBase): |
|---|
| 47 |
"""Sets up a Jabber conference at a specific conference JID. |
|---|
| 48 |
|
|---|
| 49 |
Note that conferencing is a Jabber module, and as such, needs to |
|---|
| 50 |
be enabled on the Jabber server itself. |
|---|
| 51 |
""" |
|---|
| 52 |
|
|---|
| 53 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 54 |
#~ Special |
|---|
| 55 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 56 |
|
|---|
| 57 |
def __init__(self, JC, ConferenceJID): |
|---|
| 58 |
JabberSubjectBase.__init__(self) |
|---|
| 59 |
# Save the conference JID |
|---|
| 60 |
self.ConferenceJID = JID.JID(ConferenceJID).nominal() |
|---|
| 61 |
|
|---|
| 62 |
# Save the Jabber Client |
|---|
| 63 |
self.JC = weakref.ref(JC) |
|---|
| 64 |
|
|---|
| 65 |
# Pretend like we are a Jabber.Client |
|---|
| 66 |
self.stream = weakref.proxy(self) |
|---|
| 67 |
# Insert our greedy little meathooks... |
|---|
| 68 |
obs = Observer(self._CallConference, self._BidConference) |
|---|
| 69 |
JC.stream.AddObserver('message', obs) |
|---|
| 70 |
JC.stream.AddObserver('presence', obs) |
|---|
| 71 |
JC.stream.AddObserver('iq', obs) |
|---|
| 72 |
|
|---|
| 73 |
# Prepare to get the conference roster |
|---|
| 74 |
self.roster = AttributedSubject() |
|---|
| 75 |
obs = JObs.JabberObserver(self._OnIQBrowseSets, BidValue=0.1) |
|---|
| 76 |
obs.AddRule(JObs.MatchAttributes(type='set', from_=self.ConferenceJID.nominal())) |
|---|
| 77 |
obs.AddRule(JObs.MatchHasChildNamespace('jabber:iq:browse')) |
|---|
| 78 |
self.stream.AddObserver('iq', obs) |
|---|
| 79 |
|
|---|
| 80 |
def __del__(self): |
|---|
| 81 |
"""Disconnects from the conference if the Jabber Client still exists""" |
|---|
| 82 |
if self.JC(): self.Presence(type='unavailable') |
|---|
| 83 |
|
|---|
| 84 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 85 |
#~ Protected Methods |
|---|
| 86 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 87 |
|
|---|
| 88 |
def _CallConference(self, subject, **UpdateDict): |
|---|
| 89 |
"""Dummy method to place in the stream Observer. Should never be called.""" |
|---|
| 90 |
assert 0 |
|---|
| 91 |
|
|---|
| 92 |
def _BidConference(self, subject, **UpdateDict): |
|---|
| 93 |
"""Selects only the messages off the jabber stream that are pertinate to the conference JID""" |
|---|
| 94 |
for each in UpdateDict.itervalues(): |
|---|
| 95 |
if each.from_ not in self.ConferenceJID: |
|---|
| 96 |
# This 'subject' is defined to be from JIDs in the conference |
|---|
| 97 |
return 0 |
|---|
| 98 |
return self.__super.Bid(subject, **UpdateDict) |
|---|
| 99 |
|
|---|
| 100 |
def _OnIQBrowseSets(self, subject, iq): |
|---|
| 101 |
users = getattr(iq, 'user', []) |
|---|
| 102 |
for conference in getattr(iq, 'conference', []): |
|---|
| 103 |
users.extend(getattr(conference, 'user', [])) |
|---|
| 104 |
|
|---|
| 105 |
for user in users: |
|---|
| 106 |
if getattr(user, 'type', '') == 'remove': |
|---|
| 107 |
del self.roster[user.jid] |
|---|
| 108 |
else: |
|---|
| 109 |
self.roster[user.jid] = getattr(user, 'name', user.jid) |
|---|
| 110 |
|
|---|
| 111 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 112 |
#~ Public Methods |
|---|
| 113 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 114 |
|
|---|
| 115 |
def Disconnect(self): |
|---|
| 116 |
"""Disconnects from the conference by sending and unavailable presence""" |
|---|
| 117 |
return self.Presence(type='unavailable') |
|---|
| 118 |
|
|---|
| 119 |
def GetInformation(self, callback): |
|---|
| 120 |
"""Requests information about the conference in question. |
|---|
| 121 |
|
|---|
| 122 |
Returns iqQuery object.""" |
|---|
| 123 |
return self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='get', callback=callback) |
|---|
| 124 |
|
|---|
| 125 |
def JoinAs(self, Nick, Secret='', Privacy=0, callback=None): |
|---|
| 126 |
"""Joins the conference using Nick(s), and Secret if need be. |
|---|
| 127 |
|
|---|
| 128 |
Returns iqQuery object.""" |
|---|
| 129 |
xml = '' |
|---|
| 130 |
if Nick: |
|---|
| 131 |
if not isinstance(Nick, (list, tuple)): Nick = (Nick,) |
|---|
| 132 |
xml = ''.join(['<nick>%s</nick>' % x for x in Nick]) |
|---|
| 133 |
if Secret: xml += '<secret>%s</secret>' % Secret |
|---|
| 134 |
if Privacy: xml += '<privacy/>' |
|---|
| 135 |
|
|---|
| 136 |
self.Presence() |
|---|
| 137 |
|
|---|
| 138 |
return self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='set', xml=xml, callback=callback) |
|---|
| 139 |
|
|---|
| 140 |
def ChangeNick(self, Nick, callback=None): |
|---|
| 141 |
"""Changes the nickname of the conference connection""" |
|---|
| 142 |
newjid = self.ConferenceJID.join(self.ConferenceJID.split(1,3) + [Nick]) |
|---|
| 143 |
return self.JC().Presence(newjid) |
|---|
| 144 |
|
|---|
| 145 |
def Invite(self, *args, **kw): |
|---|
| 146 |
"""Invites a JID to join the conference""" |
|---|
| 147 |
kw['xml'] = kw.get('xml', '') + '<x xmlns="jabber:x:conference" jid="%s"/>' % self.ConferenceJID |
|---|
| 148 |
return self.JC().Message(*args, **kw) |
|---|
| 149 |
|
|---|
| 150 |
def Message(self, body='', subject='', type='groupchat', id=None, xml=''): |
|---|
| 151 |
"""Sends a message to the conference""" |
|---|
| 152 |
return self.JC().Message(self.ConferenceJID, body=body, subject=subject, type=type, id=id, xml=xml) |
|---|
| 153 |
|
|---|
| 154 |
def Presence(self, *args, **kw): |
|---|
| 155 |
"""Sends a presence to the conference""" |
|---|
| 156 |
return self.JC().Presence(self.ConferenceJID, *args, **kw) |
|---|
| 157 |
|
|---|
| 158 |
Conference._Conference__super = super(Conference) |
|---|
| 159 |
|
|---|
| 160 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 161 |
|
|---|
| 162 |
if __name__=='__main__': |
|---|
| 163 |
print "Testing..." |
|---|
| 164 |
import Client |
|---|
| 165 |
|
|---|
| 166 |
def PrintRoster(roster, **kw): |
|---|
| 167 |
print kw.values(), 'is now in', roster.values() |
|---|
| 168 |
|
|---|
| 169 |
def PrintStuff(stream, **kw): |
|---|
| 170 |
for each in kw.itervalues(): |
|---|
| 171 |
print each._toXML() |
|---|
| 172 |
jc = Client.Client('www.runeblade.com', fileIn=open('conf.in','w'), fileOut=open('conf.out','w')) |
|---|
| 173 |
jc.Authenticate('shane.test1', 'testing', 'PyConferenceTest') |
|---|
| 174 |
conf = Conference(jc, 'shane.conf@private.www.runeblade.com') |
|---|
| 175 |
conf.roster.AddObserver(PrintRoster) |
|---|
| 176 |
#conf.AddObserver('message', PrintStuff) |
|---|
| 177 |
#conf.AddObserver('presence', PrintStuff) |
|---|
| 178 |
#conf.AddObserver('iq', PrintStuff) |
|---|
| 179 |
conf.JoinAs('RuneBlade.Development') |
|---|
| 180 |
conf.Message('Hello from RB development!!!') |
|---|
| 181 |
conf.ChangeNick('shane.test1') |
|---|
| 182 |
conf.Message('Why, isnt this fun?') |
|---|
| 183 |
#conf.Invite('shane.holloway@www.runeblade.com', subject='Please join me!', body='Please join me in shane.conf@private.www.runeblade.com') |
|---|
| 184 |
jc.ProcessPending(1.0) |
|---|
| 185 |
try: |
|---|
| 186 |
while 1: jc.Process(1.0) |
|---|
| 187 |
except KeyboardInterrupt: pass |
|---|
| 188 |
print "Test complete." |
|---|
| 189 |
|
|---|
| 190 |
|
|---|