pbsupport.py 9.4 KB

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