Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0085.py @ 659:b6c22d9f593a
plugin xep-0085: bug fix + improvement
author | souliane <souliane@mailoo.org> |
---|---|
date | Mon, 07 Oct 2013 13:09:57 +0200 |
parents | 5c5cf5bca240 |
children | 69a8bfd266a5 |
comparison
equal
deleted
inserted
replaced
658:e26134122ed7 | 659:b6c22d9f593a |
---|---|
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core import exceptions | 20 from sat.core import exceptions |
21 from logging import debug, info, error | 21 from logging import info |
22 from wokkel import disco, iwokkel | 22 from wokkel import disco, iwokkel |
23 from zope.interface import implements | 23 from zope.interface import implements |
24 from twisted.words.protocols.jabber.jid import JID | 24 from twisted.words.protocols.jabber.jid import JID |
25 try: | 25 try: |
26 from twisted.words.protocols.xmlstream import XMPPHandler | 26 from twisted.words.protocols.xmlstream import XMPPHandler |
87 'param_name': PARAM_NAME, | 87 'param_name': PARAM_NAME, |
88 'param_label': _('Enable chat state notifications') | 88 'param_label': _('Enable chat state notifications') |
89 } | 89 } |
90 | 90 |
91 def __init__(self, host): | 91 def __init__(self, host): |
92 info(_("CSN plugin initialization")) | 92 info(_("Chat State Notifications plugin initialization")) |
93 self.host = host | 93 self.host = host |
94 | 94 |
95 # parameter value is retrieved before each use | 95 # parameter value is retrieved before each use |
96 host.memory.importParams(self.params) | 96 host.memory.importParams(self.params) |
97 | 97 |
98 # triggers from core | 98 # triggers from core |
99 host.trigger.add("MessageReceived", self.messageReceivedTrigger) | 99 host.trigger.add("MessageReceived", self.messageReceivedTrigger) |
100 host.trigger.add("sendMessageXml", self.sendMessageXmlTrigger) | 100 host.trigger.add("sendMessageXml", self.sendMessageXmlTrigger) |
101 host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger) | 101 host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger) |
102 # TODO: handle profile disconnexion (free memory in entity data) | 102 # TODO: handle profile disconnection (free memory in entity data) |
103 | 103 |
104 # args: to_s (jid as string), profile | 104 # args: to_s (jid as string), profile |
105 host.bridge.addMethod("chatStateComposing", ".plugin", in_sign='ss', | 105 host.bridge.addMethod("chatStateComposing", ".plugin", in_sign='ss', |
106 out_sign='', method=self.chatStateComposing) | 106 out_sign='', method=self.chatStateComposing) |
107 | 107 |
111 def getHandler(self, profile): | 111 def getHandler(self, profile): |
112 return XEP_0085_handler(self, profile) | 112 return XEP_0085_handler(self, profile) |
113 | 113 |
114 def updateEntityData(self, entity_jid, value, profile): | 114 def updateEntityData(self, entity_jid, value, profile): |
115 """ | 115 """ |
116 Update the entity data and reset the chat state display | 116 Update the entity data of the given profile for one or all contacts. |
117 if the notification has been disabled. Parameter "entity_jid" | 117 Reset the chat state(s) display if the notification has been disabled. |
118 could be @ALL@ to update all entities. | 118 @param entity_jid: contact's JID, or '@ALL@' to update all contacts. |
119 @param value: True, False or '@NONE@' to delete the entity data | |
120 @param profile: current profile | |
119 """ | 121 """ |
120 self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile) | 122 self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile) |
121 if not value or value == "@NONE@": | 123 if not value or value == "@NONE@": |
122 # disable chat state for this or these contact(s) | 124 # disable chat state for this or these contact(s) |
123 self.host.bridge.chatStateReceived(unicode(entity_jid), "", profile) | 125 self.host.bridge.chatStateReceived(unicode(entity_jid), "", profile) |
124 | 126 |
125 def paramUpdateTrigger(self, name, value, category, type, profile): | 127 def paramUpdateTrigger(self, name, value, category, type, profile): |
126 """ | 128 """ |
127 Reset all the existing chat state entity data associated | 129 Reset all the existing chat state entity data associated with this profile after a parameter modification. |
128 with this profile after a parameter modification (value | 130 @param name: parameter name |
129 different then "true" would delete the entity data). | 131 @param value: "true" to activate the notifications, or any other value to delete it |
132 @param category: parameter category | |
130 """ | 133 """ |
131 if (category, name) == (PARAM_KEY, PARAM_NAME): | 134 if (category, name) == (PARAM_KEY, PARAM_NAME): |
132 self.updateEntityData("@ALL@", True if value == "true" else "@NONE@", profile) | 135 self.updateEntityData("@ALL@", True if value == "true" else "@NONE@", profile) |
133 | 136 |
134 def messageReceivedTrigger(self, message, profile): | 137 def messageReceivedTrigger(self, message, profile): |
149 # init to send following "composing" state | 152 # init to send following "composing" state |
150 self.__chatStateInit(from_jid, message.getAttribute("type"), profile) | 153 self.__chatStateInit(from_jid, message.getAttribute("type"), profile) |
151 except StopIteration: | 154 except StopIteration: |
152 # contact didn't enable Chat State Notifications | 155 # contact didn't enable Chat State Notifications |
153 self.updateEntityData(from_jid, False, profile) | 156 self.updateEntityData(from_jid, False, profile) |
157 return True | |
154 except StopIteration: | 158 except StopIteration: |
155 pass | 159 pass |
156 | 160 |
157 state_list = [child.name for child in message.children if | 161 state_list = [child.name for child in message.children if |
158 message.getAttribute("type") in MESSAGE_TYPES | 162 message.getAttribute("type") in MESSAGE_TYPES |
159 and child.name in CHAT_STATES | 163 and child.name in CHAT_STATES |
160 and child.defaultUri == NS_CHAT_STATES] | 164 and child.defaultUri == NS_CHAT_STATES] |
161 for state in state_list: | 165 for state in state_list: |
162 # there must be only one state according to the XEP | 166 # there must be only one state according to the XEP |
163 self.host.bridge.chatStateReceived(message.getAttribute("from"), | 167 self.host.bridge.chatStateReceived(message.getAttribute("from"), state, profile) |
164 state, profile) | |
165 break | 168 break |
166 return True | 169 return True |
167 | 170 |
168 def sendMessageXmlTrigger(self, message, mess_data, profile): | 171 def sendMessageXmlTrigger(self, message, mess_data, profile): |
169 """ | 172 """ |
170 Eventually add the chat state to the message and initiate | 173 Eventually add the chat state to the message and initiate |
171 the state machine when sending an "active" state. | 174 the state machine when sending an "active" state. |
172 """ | 175 """ |
173 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile): | |
174 return True | |
175 | |
176 # check if notifications should be sent to this contact | |
177 contact_enabled = True | |
178 to_jid = JID(message.getAttribute("to")) | 176 to_jid = JID(message.getAttribute("to")) |
179 try: | 177 if not self.__checkActivation(to_jid, forceEntityData=True, profile=profile): |
180 contact_enabled = self.host.memory.getEntityData( | |
181 to_jid, [ENTITY_KEY], profile)[ENTITY_KEY] | |
182 except (exceptions.UnknownEntityError, KeyError): | |
183 # enable it for the first time | |
184 self.updateEntityData(to_jid, True, profile) | |
185 if not contact_enabled: | |
186 return True | 178 return True |
187 try: | 179 try: |
188 # message with a body always mean active state | 180 # message with a body always mean active state |
189 generateElementsNamed(message.children, name="body").next() | 181 generateElementsNamed(message.children, name="body").next() |
190 message.addElement('active', NS_CHAT_STATES) | 182 message.addElement('active', NS_CHAT_STATES) |
195 state = mess_data["options"]["chat_state"] | 187 state = mess_data["options"]["chat_state"] |
196 assert(state in CHAT_STATES) | 188 assert(state in CHAT_STATES) |
197 message.addElement(state, NS_CHAT_STATES) | 189 message.addElement(state, NS_CHAT_STATES) |
198 return True | 190 return True |
199 | 191 |
192 def __checkActivation(self, to_jid, forceEntityData, profile): | |
193 """ | |
194 @param to_joid: the contact's JID | |
195 @param forceEntityData: if set to True, a non-existing | |
196 entity data will be considered to be True (and initialized) | |
197 @param: current profile | |
198 @return: True if the notifications should be sent to this JID. | |
199 """ | |
200 # check if the parameter is active | |
201 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile): | |
202 return False | |
203 # check if notifications should be sent to this contact | |
204 try: | |
205 return self.host.memory.getEntityData(to_jid, [ENTITY_KEY], profile)[ENTITY_KEY] | |
206 except (exceptions.UnknownEntityError, KeyError): | |
207 if forceEntityData: | |
208 # enable it for the first time | |
209 self.updateEntityData(to_jid, True, profile) | |
210 return True | |
211 # wait for the first message before sending states | |
212 return False | |
213 | |
200 def __chatStateInit(self, to_jid, mess_type, profile): | 214 def __chatStateInit(self, to_jid, mess_type, profile): |
201 """ | 215 """ |
202 Data initialization for the chat state machine. | 216 Data initialization for the chat state machine. |
203 """ | 217 """ |
204 # TODO: use also the resource in map key | 218 # TODO: use also the resource in map key |
220 # TODO: use also the JID resource in the map key | 234 # TODO: use also the JID resource in the map key |
221 to_jid = to_jid.userhostJID() | 235 to_jid = to_jid.userhostJID() |
222 profile = self.host.memory.getProfileName(profile_key) | 236 profile = self.host.memory.getProfileName(profile_key) |
223 if profile is None: | 237 if profile is None: |
224 raise exceptions.ProfileUnknownError | 238 raise exceptions.ProfileUnknownError |
239 return | |
225 self.__chatStateInit(to_jid, mess_type, profile) | 240 self.__chatStateInit(to_jid, mess_type, profile) |
226 self.map[profile][to_jid]._onEvent("active") | 241 self.map[profile][to_jid]._onEvent("active") |
227 | 242 |
228 def chatStateComposing(self, to_jid_s, profile_key): | 243 def chatStateComposing(self, to_jid_s, profile_key): |
229 """ | 244 """ |
231 from the front-end, it needs to check the values of the | 246 from the front-end, it needs to check the values of the |
232 parameter "Send chat state notifications" and the entity | 247 parameter "Send chat state notifications" and the entity |
233 data associated to the target JID. | 248 data associated to the target JID. |
234 TODO: try to optimize this method which is called often | 249 TODO: try to optimize this method which is called often |
235 """ | 250 """ |
236 # check if the parameter is active | |
237 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile_key): | |
238 return | |
239 # TODO: use also the JID resource in the map key | 251 # TODO: use also the JID resource in the map key |
240 to_jid = JID(to_jid_s).userhostJID() | 252 to_jid = JID(to_jid_s).userhostJID() |
241 profile = self.host.memory.getProfileName(profile_key) | 253 profile = self.host.memory.getProfileName(profile_key) |
242 if profile is None: | 254 if profile is None: |
243 raise exceptions.ProfileUnknownError | 255 raise exceptions.ProfileUnknownError |
244 # check if notifications should be sent to this contact | 256 return |
245 contact_enabled = True | 257 if not self.__checkActivation(to_jid, forceEntityData=False, profile=profile): |
258 return | |
246 try: | 259 try: |
247 contact_enabled = self.host.memory.getEntityData( | 260 self.map[profile][to_jid]._onEvent("composing") |
248 to_jid, [ENTITY_KEY], profile)[ENTITY_KEY] | 261 except AttributeError: |
249 except (exceptions.UnknownEntityError, KeyError): | 262 # no message has been sent/received since the notifications |
250 # wait for the first message before sending states | 263 # have been enabled, it's better to wait for a first one |
251 pass | 264 pass |
252 if not contact_enabled: | |
253 return True | |
254 # now we are sure that the state should be sent | |
255 self.map[profile][to_jid]._onEvent("composing") | |
256 | 265 |
257 | 266 |
258 class ChatStateMachine: | 267 class ChatStateMachine: |
259 """ | 268 """ |
260 This class represents a chat state, between one profile and | 269 This class represents a chat state, between one profile and |