1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
5
"""L{twisted.words} support for Instance Messenger."""
7
from __future__ import nested_scopes
9
from twisted.internet import defer
10
from twisted.internet import error
11
from twisted.python import log
12
from twisted.python.failure import Failure
13
from twisted.spread import pb
15
from twisted.words.im.locals import ONLINE, OFFLINE, AWAY
17
from twisted.words.im import basesupport, interfaces
18
from zope.interface import implements
21
class TwistedWordsPerson(basesupport.AbstractPerson):
22
"""I a facade for a person you can talk to through a twisted.words service.
24
def __init__(self, name, wordsAccount):
25
basesupport.AbstractPerson.__init__(self, name, wordsAccount)
29
return ((self.status == ONLINE) or
30
(self.status == AWAY))
35
def sendMessage(self, text, metadata):
36
"""Return a deferred...
39
d=self.account.client.perspective.directMessage(self.name,
41
d.addErrback(self.metadataFailed, "* "+text)
44
return self.account.client.perspective.callRemote('directMessage',self.name, text)
46
def metadataFailed(self, result, text):
47
print "result:",result,"text:",text
48
return self.account.client.perspective.directMessage(self.name, text)
50
def setStatus(self, status):
52
self.chat.getContactsList().setContactStatus(self)
54
class TwistedWordsGroup(basesupport.AbstractGroup):
55
implements(interfaces.IGroup)
56
def __init__(self, name, wordsClient):
57
basesupport.AbstractGroup.__init__(self, name, wordsClient)
60
def sendGroupMessage(self, text, metadata=None):
63
#for backwards compatibility with older twisted.words servers.
65
d=self.account.client.perspective.callRemote(
66
'groupMessage', self.name, text, metadata)
67
d.addErrback(self.metadataFailed, "* "+text)
70
return self.account.client.perspective.callRemote('groupMessage',
73
def setTopic(self, text):
74
self.account.client.perspective.callRemote(
76
{'topic': text, 'topic_author': self.client.name},
79
def metadataFailed(self, result, text):
80
print "result:",result,"text:",text
81
return self.account.client.perspective.callRemote('groupMessage',
91
return self.account.client.perspective.callRemote('leaveGroup',
96
class 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)
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
107
self._logonDeferred = _logonDeferred
109
def getPerson(self, name):
110
return self.chat.getPerson(name, self)
112
def getGroup(self, name):
113
return self.chat.getGroup(name, self)
115
def getGroupConversation(self, name):
116
return self.chat.getGroupConversation(self.getGroup(name))
118
def addContact(self, name):
119
self.perspective.callRemote('addContact', name)
121
def remote_receiveGroupMembers(self, names, group):
122
print 'received group members:', names, group
123
self.getGroupConversation(group).setGroupMembers(names)
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)
129
def remote_memberJoined(self, member, group):
130
print 'member joined', member, group
131
self.getGroupConversation(group).memberJoined(member)
133
def remote_memberLeft(self, member, group):
135
self.getGroupConversation(group).memberLeft(member)
137
def remote_notifyStatusChanged(self, name, status):
138
self.chat.getPerson(name, self).setStatus(status)
140
def remote_receiveDirectMessage(self, name, message, metadata=None):
141
self.chat.getConversation(self.chat.getPerson(name, self)).showMessage(message, metadata)
143
def remote_receiveContactList(self, clist):
144
for name, status in clist:
145
self.chat.getPerson(name, self).setStatus(status)
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))
151
def joinGroup(self, name):
152
self.getGroup(name).joining()
153
return self.perspective.callRemote('joinGroup', name).addCallback(self._cbGroupJoined, name)
155
def leaveGroup(self, name):
156
self.getGroup(name).leaving()
157
return self.perspective.callRemote('leaveGroup', name).addCallback(self._cbGroupLeft, name)
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)
164
def _cbGroupLeft(self, result, name):
166
groupConv = self.chat.getGroupConversation(self.getGroup(name), 1)
167
groupConv.showGroupMessage("sys", "you left")
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()
178
"twisted.words": TwistedWordsClient,
179
"twisted.reality": None
183
class PBAccount(basesupport.AbstractAccount):
184
implements(interfaces.IAccount)
186
_groupFactory = TwistedWordsGroup
187
_personFactory = TwistedWordsPerson
189
def __init__(self, accountName, autoLogin, username, password, host, port,
192
@param username: The name of your PB Identity.
193
@type username: string
195
basesupport.AbstractAccount.__init__(self, accountName, autoLogin,
196
username, password, host, port)
199
services = [('twisted.words', 'twisted.words', username)]
200
for serviceType, serviceName, perspectiveName in services:
201
self.services.append([pbFrontEnds[serviceType], serviceName,
204
def logOn(self, chatui):
206
@returns: this breaks with L{interfaces.IAccount}
207
@returntype: DeferredList of L{interfaces.IClient}s
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:
219
chatui.registerAccountClient(result)
220
self._cb_logOn(result)
223
d.addCallback(registerMany)
226
raise error.ConnectionError("Connection in progress")
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,))
236
def _cbConnected(self, root, chatui):
238
print 'Identifying...',
239
d = pb.authIdentity(root, self.username, self.password)
240
d.addCallbacks(self._cbIdent, self._ebConnected,
241
callbackArgs=(chatui,))
244
def _cbIdent(self, ident, chatui):
246
print 'falsely identified.'
247
return self._ebConnected(Failure(Exception("username or password incorrect")))
250
for handlerClass, sname, pname in self.services:
253
handler = handlerClass(self, sname, pname, chatui, d)
254
ident.callRemote('attach', sname, pname, handler).addCallback(handler.connected)
255
return defer.DeferredList(dl)
257
def _ebConnected(self, error):
258
print 'Not connected.'