1# Copyright (c) Twisted Matrix Laboratories. 2# See LICENSE for details. 3 4 5""" 6L{twisted.words} support for Instance Messenger. 7""" 8 9from twisted.internet import defer 10from twisted.internet import error 11from twisted.python import log 12from twisted.python.failure import Failure 13from twisted.spread import pb 14 15from twisted.words.im.locals import ONLINE, OFFLINE, AWAY 16 17from twisted.words.im import basesupport, interfaces 18from zope.interface import implements 19 20 21class TwistedWordsPerson(basesupport.AbstractPerson): 22 """I a facade for a person you can talk to through a twisted.words service. 23 """ 24 def __init__(self, name, wordsAccount): 25 basesupport.AbstractPerson.__init__(self, name, wordsAccount) 26 self.status = OFFLINE 27 28 def isOnline(self): 29 return ((self.status == ONLINE) or 30 (self.status == AWAY)) 31 32 def getStatus(self): 33 return self.status 34 35 def sendMessage(self, text, metadata): 36 """Return a deferred... 37 """ 38 if metadata: 39 d=self.account.client.perspective.directMessage(self.name, 40 text, metadata) 41 d.addErrback(self.metadataFailed, "* "+text) 42 return d 43 else: 44 return self.account.client.perspective.callRemote('directMessage',self.name, text) 45 46 def metadataFailed(self, result, text): 47 print "result:",result,"text:",text 48 return self.account.client.perspective.directMessage(self.name, text) 49 50 def setStatus(self, status): 51 self.status = status 52 self.chat.getContactsList().setContactStatus(self) 53 54class TwistedWordsGroup(basesupport.AbstractGroup): 55 implements(interfaces.IGroup) 56 def __init__(self, name, wordsClient): 57 basesupport.AbstractGroup.__init__(self, name, wordsClient) 58 self.joined = 0 59 60 def sendGroupMessage(self, text, metadata=None): 61 """Return a deferred. 62 """ 63 #for backwards compatibility with older twisted.words servers. 64 if metadata: 65 d=self.account.client.perspective.callRemote( 66 'groupMessage', self.name, text, metadata) 67 d.addErrback(self.metadataFailed, "* "+text) 68 return d 69 else: 70 return self.account.client.perspective.callRemote('groupMessage', 71 self.name, text) 72 73 def setTopic(self, text): 74 self.account.client.perspective.callRemote( 75 'setGroupMetadata', 76 {'topic': text, 'topic_author': self.client.name}, 77 self.name) 78 79 def metadataFailed(self, result, text): 80 print "result:",result,"text:",text 81 return self.account.client.perspective.callRemote('groupMessage', 82 self.name, text) 83 84 def joining(self): 85 self.joined = 1 86 87 def leaving(self): 88 self.joined = 0 89 90 def leave(self): 91 return self.account.client.perspective.callRemote('leaveGroup', 92 self.name) 93 94 95 96class TwistedWordsClient(pb.Referenceable, basesupport.AbstractClientMixin): 97 """In some cases, this acts as an Account, since it a source of text 98 messages (multiple Words instances may be on a single PB connection) 99 """ 100 def __init__(self, acct, serviceName, perspectiveName, chatui, 101 _logonDeferred=None): 102 self.accountName = "%s (%s:%s)" % (acct.accountName, serviceName, perspectiveName) 103 self.name = perspectiveName 104 print "HELLO I AM A PB SERVICE", serviceName, perspectiveName 105 self.chat = chatui 106 self.account = acct 107 self._logonDeferred = _logonDeferred 108 109 def getPerson(self, name): 110 return self.chat.getPerson(name, self) 111 112 def getGroup(self, name): 113 return self.chat.getGroup(name, self) 114 115 def getGroupConversation(self, name): 116 return self.chat.getGroupConversation(self.getGroup(name)) 117 118 def addContact(self, name): 119 self.perspective.callRemote('addContact', name) 120 121 def remote_receiveGroupMembers(self, names, group): 122 print 'received group members:', names, group 123 self.getGroupConversation(group).setGroupMembers(names) 124 125 def remote_receiveGroupMessage(self, sender, group, message, metadata=None): 126 print 'received a group message', sender, group, message, metadata 127 self.getGroupConversation(group).showGroupMessage(sender, message, metadata) 128 129 def remote_memberJoined(self, member, group): 130 print 'member joined', member, group 131 self.getGroupConversation(group).memberJoined(member) 132 133 def remote_memberLeft(self, member, group): 134 print 'member left' 135 self.getGroupConversation(group).memberLeft(member) 136 137 def remote_notifyStatusChanged(self, name, status): 138 self.chat.getPerson(name, self).setStatus(status) 139 140 def remote_receiveDirectMessage(self, name, message, metadata=None): 141 self.chat.getConversation(self.chat.getPerson(name, self)).showMessage(message, metadata) 142 143 def remote_receiveContactList(self, clist): 144 for name, status in clist: 145 self.chat.getPerson(name, self).setStatus(status) 146 147 def remote_setGroupMetadata(self, dict_, groupName): 148 if dict_.has_key("topic"): 149 self.getGroupConversation(groupName).setTopic(dict_["topic"], dict_.get("topic_author", None)) 150 151 def joinGroup(self, name): 152 self.getGroup(name).joining() 153 return self.perspective.callRemote('joinGroup', name).addCallback(self._cbGroupJoined, name) 154 155 def leaveGroup(self, name): 156 self.getGroup(name).leaving() 157 return self.perspective.callRemote('leaveGroup', name).addCallback(self._cbGroupLeft, name) 158 159 def _cbGroupJoined(self, result, name): 160 groupConv = self.chat.getGroupConversation(self.getGroup(name)) 161 groupConv.showGroupMessage("sys", "you joined") 162 self.perspective.callRemote('getGroupMembers', name) 163 164 def _cbGroupLeft(self, result, name): 165 print 'left',name 166 groupConv = self.chat.getGroupConversation(self.getGroup(name), 1) 167 groupConv.showGroupMessage("sys", "you left") 168 169 def connected(self, perspective): 170 print 'Connected Words Client!', perspective 171 if self._logonDeferred is not None: 172 self._logonDeferred.callback(self) 173 self.perspective = perspective 174 self.chat.getContactsList() 175 176 177pbFrontEnds = { 178 "twisted.words": TwistedWordsClient, 179 "twisted.reality": None 180 } 181 182 183class PBAccount(basesupport.AbstractAccount): 184 implements(interfaces.IAccount) 185 gatewayType = "PB" 186 _groupFactory = TwistedWordsGroup 187 _personFactory = TwistedWordsPerson 188 189 def __init__(self, accountName, autoLogin, username, password, host, port, 190 services=None): 191 """ 192 @param username: The name of your PB Identity. 193 @type username: string 194 """ 195 basesupport.AbstractAccount.__init__(self, accountName, autoLogin, 196 username, password, host, port) 197 self.services = [] 198 if not services: 199 services = [('twisted.words', 'twisted.words', username)] 200 for serviceType, serviceName, perspectiveName in services: 201 self.services.append([pbFrontEnds[serviceType], serviceName, 202 perspectiveName]) 203 204 def logOn(self, chatui): 205 """ 206 @returns: this breaks with L{interfaces.IAccount} 207 @returntype: DeferredList of L{interfaces.IClient}s 208 """ 209 # Overriding basesupport's implementation on account of the 210 # fact that _startLogOn tends to return a deferredList rather 211 # than a simple Deferred, and we need to do registerAccountClient. 212 if (not self._isConnecting) and (not self._isOnline): 213 self._isConnecting = 1 214 d = self._startLogOn(chatui) 215 d.addErrback(self._loginFailed) 216 def registerMany(results): 217 for success, result in results: 218 if success: 219 chatui.registerAccountClient(result) 220 self._cb_logOn(result) 221 else: 222 log.err(result) 223 d.addCallback(registerMany) 224 return d 225 else: 226 raise error.ConnectionError("Connection in progress") 227 228 229 def _startLogOn(self, chatui): 230 print 'Connecting...', 231 d = pb.getObjectAt(self.host, self.port) 232 d.addCallbacks(self._cbConnected, self._ebConnected, 233 callbackArgs=(chatui,)) 234 return d 235 236 def _cbConnected(self, root, chatui): 237 print 'Connected!' 238 print 'Identifying...', 239 d = pb.authIdentity(root, self.username, self.password) 240 d.addCallbacks(self._cbIdent, self._ebConnected, 241 callbackArgs=(chatui,)) 242 return d 243 244 def _cbIdent(self, ident, chatui): 245 if not ident: 246 print 'falsely identified.' 247 return self._ebConnected(Failure(Exception("username or password incorrect"))) 248 print 'Identified!' 249 dl = [] 250 for handlerClass, sname, pname in self.services: 251 d = defer.Deferred() 252 dl.append(d) 253 handler = handlerClass(self, sname, pname, chatui, d) 254 ident.callRemote('attach', sname, pname, handler).addCallback(handler.connected) 255 return defer.DeferredList(dl) 256 257 def _ebConnected(self, error): 258 print 'Not connected.' 259 return error 260 261