basechat.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # -*- test-case-name: twisted.words.test.test_basechat -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Base classes for Instance Messenger clients.
  6. """
  7. from twisted.words.im.locals import AWAY, OFFLINE, ONLINE
  8. class ContactsList:
  9. """
  10. A GUI object that displays a contacts list.
  11. @ivar chatui: The GUI chat client associated with this contacts list.
  12. @type chatui: L{ChatUI}
  13. @ivar contacts: The contacts.
  14. @type contacts: C{dict} mapping C{str} to a L{IPerson<interfaces.IPerson>}
  15. provider
  16. @ivar onlineContacts: The contacts who are currently online (have a status
  17. that is not C{OFFLINE}).
  18. @type onlineContacts: C{dict} mapping C{str} to a
  19. L{IPerson<interfaces.IPerson>} provider
  20. @ivar clients: The signed-on clients.
  21. @type clients: C{list} of L{IClient<interfaces.IClient>} providers
  22. """
  23. def __init__(self, chatui):
  24. """
  25. @param chatui: The GUI chat client associated with this contacts list.
  26. @type chatui: L{ChatUI}
  27. """
  28. self.chatui = chatui
  29. self.contacts = {}
  30. self.onlineContacts = {}
  31. self.clients = []
  32. def setContactStatus(self, person):
  33. """
  34. Inform the user that a person's status has changed.
  35. @param person: The person whose status has changed.
  36. @type person: L{IPerson<interfaces.IPerson>} provider
  37. """
  38. if person.name not in self.contacts:
  39. self.contacts[person.name] = person
  40. if person.name not in self.onlineContacts and (
  41. person.status == ONLINE or person.status == AWAY
  42. ):
  43. self.onlineContacts[person.name] = person
  44. if person.name in self.onlineContacts and person.status == OFFLINE:
  45. del self.onlineContacts[person.name]
  46. def registerAccountClient(self, client):
  47. """
  48. Notify the user that an account client has been signed on to.
  49. @param client: The client being added to your list of account clients.
  50. @type client: L{IClient<interfaces.IClient>} provider
  51. """
  52. if client not in self.clients:
  53. self.clients.append(client)
  54. def unregisterAccountClient(self, client):
  55. """
  56. Notify the user that an account client has been signed off or
  57. disconnected from.
  58. @param client: The client being removed from the list of account
  59. clients.
  60. @type client: L{IClient<interfaces.IClient>} provider
  61. """
  62. if client in self.clients:
  63. self.clients.remove(client)
  64. def contactChangedNick(self, person, newnick):
  65. """
  66. Update your contact information to reflect a change to a contact's
  67. nickname.
  68. @param person: The person in your contacts list whose nickname is
  69. changing.
  70. @type person: L{IPerson<interfaces.IPerson>} provider
  71. @param newnick: The new nickname for this person.
  72. @type newnick: C{str}
  73. """
  74. oldname = person.name
  75. if oldname in self.contacts:
  76. del self.contacts[oldname]
  77. person.name = newnick
  78. self.contacts[newnick] = person
  79. if oldname in self.onlineContacts:
  80. del self.onlineContacts[oldname]
  81. self.onlineContacts[newnick] = person
  82. class Conversation:
  83. """
  84. A GUI window of a conversation with a specific person.
  85. @ivar person: The person who you're having this conversation with.
  86. @type person: L{IPerson<interfaces.IPerson>} provider
  87. @ivar chatui: The GUI chat client associated with this conversation.
  88. @type chatui: L{ChatUI}
  89. """
  90. def __init__(self, person, chatui):
  91. """
  92. @param person: The person who you're having this conversation with.
  93. @type person: L{IPerson<interfaces.IPerson>} provider
  94. @param chatui: The GUI chat client associated with this conversation.
  95. @type chatui: L{ChatUI}
  96. """
  97. self.chatui = chatui
  98. self.person = person
  99. def show(self):
  100. """
  101. Display the ConversationWindow.
  102. """
  103. raise NotImplementedError("Subclasses must implement this method")
  104. def hide(self):
  105. """
  106. Hide the ConversationWindow.
  107. """
  108. raise NotImplementedError("Subclasses must implement this method")
  109. def sendText(self, text):
  110. """
  111. Send text to the person with whom the user is conversing.
  112. @param text: The text to be sent.
  113. @type text: C{str}
  114. """
  115. self.person.sendMessage(text, None)
  116. def showMessage(self, text, metadata=None):
  117. """
  118. Display a message sent from the person with whom the user is conversing.
  119. @param text: The sent message.
  120. @type text: C{str}
  121. @param metadata: Metadata associated with this message.
  122. @type metadata: C{dict}
  123. """
  124. raise NotImplementedError("Subclasses must implement this method")
  125. def contactChangedNick(self, person, newnick):
  126. """
  127. Change a person's name.
  128. @param person: The person whose nickname is changing.
  129. @type person: L{IPerson<interfaces.IPerson>} provider
  130. @param newnick: The new nickname for this person.
  131. @type newnick: C{str}
  132. """
  133. self.person.name = newnick
  134. class GroupConversation:
  135. """
  136. A GUI window of a conversation with a group of people.
  137. @ivar chatui: The GUI chat client associated with this conversation.
  138. @type chatui: L{ChatUI}
  139. @ivar group: The group of people that are having this conversation.
  140. @type group: L{IGroup<interfaces.IGroup>} provider
  141. @ivar members: The names of the people in this conversation.
  142. @type members: C{list} of C{str}
  143. """
  144. def __init__(self, group, chatui):
  145. """
  146. @param chatui: The GUI chat client associated with this conversation.
  147. @type chatui: L{ChatUI}
  148. @param group: The group of people that are having this conversation.
  149. @type group: L{IGroup<interfaces.IGroup>} provider
  150. """
  151. self.chatui = chatui
  152. self.group = group
  153. self.members = []
  154. def show(self):
  155. """
  156. Display the GroupConversationWindow.
  157. """
  158. raise NotImplementedError("Subclasses must implement this method")
  159. def hide(self):
  160. """
  161. Hide the GroupConversationWindow.
  162. """
  163. raise NotImplementedError("Subclasses must implement this method")
  164. def sendText(self, text):
  165. """
  166. Send text to the group.
  167. @param text: The text to be sent.
  168. @type text: C{str}
  169. """
  170. self.group.sendGroupMessage(text, None)
  171. def showGroupMessage(self, sender, text, metadata=None):
  172. """
  173. Display to the user a message sent to this group from the given sender.
  174. @param sender: The person sending the message.
  175. @type sender: C{str}
  176. @param text: The sent message.
  177. @type text: C{str}
  178. @param metadata: Metadata associated with this message.
  179. @type metadata: C{dict}
  180. """
  181. raise NotImplementedError("Subclasses must implement this method")
  182. def setGroupMembers(self, members):
  183. """
  184. Set the list of members in the group.
  185. @param members: The names of the people that will be in this group.
  186. @type members: C{list} of C{str}
  187. """
  188. self.members = members
  189. def setTopic(self, topic, author):
  190. """
  191. Change the topic for the group conversation window and display this
  192. change to the user.
  193. @param topic: This group's topic.
  194. @type topic: C{str}
  195. @param author: The person changing the topic.
  196. @type author: C{str}
  197. """
  198. raise NotImplementedError("Subclasses must implement this method")
  199. def memberJoined(self, member):
  200. """
  201. Add the given member to the list of members in the group conversation
  202. and displays this to the user.
  203. @param member: The person joining the group conversation.
  204. @type member: C{str}
  205. """
  206. if member not in self.members:
  207. self.members.append(member)
  208. def memberChangedNick(self, oldnick, newnick):
  209. """
  210. Change the nickname for a member of the group conversation and displays
  211. this change to the user.
  212. @param oldnick: The old nickname.
  213. @type oldnick: C{str}
  214. @param newnick: The new nickname.
  215. @type newnick: C{str}
  216. """
  217. if oldnick in self.members:
  218. self.members.remove(oldnick)
  219. self.members.append(newnick)
  220. def memberLeft(self, member):
  221. """
  222. Delete the given member from the list of members in the group
  223. conversation and displays the change to the user.
  224. @param member: The person leaving the group conversation.
  225. @type member: C{str}
  226. """
  227. if member in self.members:
  228. self.members.remove(member)
  229. class ChatUI:
  230. """
  231. A GUI chat client.
  232. @type conversations: C{dict} of L{Conversation}
  233. @ivar conversations: A cache of all the direct windows.
  234. @type groupConversations: C{dict} of L{GroupConversation}
  235. @ivar groupConversations: A cache of all the group windows.
  236. @type persons: C{dict} with keys that are a C{tuple} of (C{str},
  237. L{IAccount<interfaces.IAccount>} provider) and values that are
  238. L{IPerson<interfaces.IPerson>} provider
  239. @ivar persons: A cache of all the users associated with this client.
  240. @type groups: C{dict} with keys that are a C{tuple} of (C{str},
  241. L{IAccount<interfaces.IAccount>} provider) and values that are
  242. L{IGroup<interfaces.IGroup>} provider
  243. @ivar groups: A cache of all the groups associated with this client.
  244. @type onlineClients: C{list} of L{IClient<interfaces.IClient>} providers
  245. @ivar onlineClients: A list of message sources currently online.
  246. @type contactsList: L{ContactsList}
  247. @ivar contactsList: A contacts list.
  248. """
  249. def __init__(self):
  250. self.conversations = {}
  251. self.groupConversations = {}
  252. self.persons = {}
  253. self.groups = {}
  254. self.onlineClients = []
  255. self.contactsList = ContactsList(self)
  256. def registerAccountClient(self, client):
  257. """
  258. Notify the user that an account has been signed on to.
  259. @type client: L{IClient<interfaces.IClient>} provider
  260. @param client: The client account for the person who has just signed on.
  261. @rtype: L{IClient<interfaces.IClient>} provider
  262. @return: The client, so that it may be used in a callback chain.
  263. """
  264. self.onlineClients.append(client)
  265. self.contactsList.registerAccountClient(client)
  266. return client
  267. def unregisterAccountClient(self, client):
  268. """
  269. Notify the user that an account has been signed off or disconnected.
  270. @type client: L{IClient<interfaces.IClient>} provider
  271. @param client: The client account for the person who has just signed
  272. off.
  273. """
  274. self.onlineClients.remove(client)
  275. self.contactsList.unregisterAccountClient(client)
  276. def getContactsList(self):
  277. """
  278. Get the contacts list associated with this chat window.
  279. @rtype: L{ContactsList}
  280. @return: The contacts list associated with this chat window.
  281. """
  282. return self.contactsList
  283. def getConversation(self, person, Class=Conversation, stayHidden=False):
  284. """
  285. For the given person object, return the conversation window or create
  286. and return a new conversation window if one does not exist.
  287. @type person: L{IPerson<interfaces.IPerson>} provider
  288. @param person: The person whose conversation window we want to get.
  289. @type Class: L{IConversation<interfaces.IConversation>} implementor
  290. @param Class: The kind of conversation window we want. If the conversation
  291. window for this person didn't already exist, create one of this type.
  292. @type stayHidden: C{bool}
  293. @param stayHidden: Whether or not the conversation window should stay
  294. hidden.
  295. @rtype: L{IConversation<interfaces.IConversation>} provider
  296. @return: The conversation window.
  297. """
  298. conv = self.conversations.get(person)
  299. if not conv:
  300. conv = Class(person, self)
  301. self.conversations[person] = conv
  302. if stayHidden:
  303. conv.hide()
  304. else:
  305. conv.show()
  306. return conv
  307. def getGroupConversation(self, group, Class=GroupConversation, stayHidden=False):
  308. """
  309. For the given group object, return the group conversation window or
  310. create and return a new group conversation window if it doesn't exist.
  311. @type group: L{IGroup<interfaces.IGroup>} provider
  312. @param group: The group whose conversation window we want to get.
  313. @type Class: L{IConversation<interfaces.IConversation>} implementor
  314. @param Class: The kind of conversation window we want. If the conversation
  315. window for this person didn't already exist, create one of this type.
  316. @type stayHidden: C{bool}
  317. @param stayHidden: Whether or not the conversation window should stay
  318. hidden.
  319. @rtype: L{IGroupConversation<interfaces.IGroupConversation>} provider
  320. @return: The group conversation window.
  321. """
  322. conv = self.groupConversations.get(group)
  323. if not conv:
  324. conv = Class(group, self)
  325. self.groupConversations[group] = conv
  326. if stayHidden:
  327. conv.hide()
  328. else:
  329. conv.show()
  330. return conv
  331. def getPerson(self, name, client):
  332. """
  333. For the given name and account client, return an instance of a
  334. L{IGroup<interfaces.IPerson>} provider or create and return a new
  335. instance of a L{IGroup<interfaces.IPerson>} provider.
  336. @type name: C{str}
  337. @param name: The name of the person of interest.
  338. @type client: L{IClient<interfaces.IClient>} provider
  339. @param client: The client account of interest.
  340. @rtype: L{IPerson<interfaces.IPerson>} provider
  341. @return: The person with that C{name}.
  342. """
  343. account = client.account
  344. p = self.persons.get((name, account))
  345. if not p:
  346. p = account.getPerson(name)
  347. self.persons[name, account] = p
  348. return p
  349. def getGroup(self, name, client):
  350. """
  351. For the given name and account client, return an instance of a
  352. L{IGroup<interfaces.IGroup>} provider or create and return a new instance
  353. of a L{IGroup<interfaces.IGroup>} provider.
  354. @type name: C{str}
  355. @param name: The name of the group of interest.
  356. @type client: L{IClient<interfaces.IClient>} provider
  357. @param client: The client account of interest.
  358. @rtype: L{IGroup<interfaces.IGroup>} provider
  359. @return: The group with that C{name}.
  360. """
  361. # I accept 'client' instead of 'account' in my signature for
  362. # backwards compatibility. (Groups changed to be Account-oriented
  363. # in CVS revision 1.8.)
  364. account = client.account
  365. g = self.groups.get((name, account))
  366. if not g:
  367. g = account.getGroup(name)
  368. self.groups[name, account] = g
  369. return g
  370. def contactChangedNick(self, person, newnick):
  371. """
  372. For the given C{person}, change the C{person}'s C{name} to C{newnick}
  373. and tell the contact list and any conversation windows with that
  374. C{person} to change as well.
  375. @type person: L{IPerson<interfaces.IPerson>} provider
  376. @param person: The person whose nickname will get changed.
  377. @type newnick: C{str}
  378. @param newnick: The new C{name} C{person} will take.
  379. """
  380. oldnick = person.name
  381. if (oldnick, person.account) in self.persons:
  382. conv = self.conversations.get(person)
  383. if conv:
  384. conv.contactChangedNick(person, newnick)
  385. self.contactsList.contactChangedNick(person, newnick)
  386. del self.persons[oldnick, person.account]
  387. person.name = newnick
  388. self.persons[person.name, person.account] = person