comparison src/plugins/plugin_misc_text_commands.py @ 1963:a2bc5089c2eb

backend, frontends: message refactoring (huge commit): /!\ several features are temporarily disabled, like notifications in frontends next step in refactoring, with the following changes: - jp: updated jp message to follow changes in backend/bridge - jp: added --lang, --subject, --subject_lang, and --type options to jp message + fixed unicode handling for jid - quick_frontend (QuickApp, QuickChat): - follow backend changes - refactored chat, message are now handled in OrderedDict and uid are kept so they can be updated - Message and Occupant classes handle metadata, so frontend just have to display them - Primitivus (Chat): - follow backend/QuickFrontend changes - info & standard messages are handled in the same MessageWidget class - improved/simplified handling of messages, removed update() method - user joined/left messages are merged when next to each other - a separator is shown when message is received while widget is out of focus, so user can quickly see the new messages - affiliation/role are shown (in a basic way for now) in occupants panel - removed "/me" messages handling, as it will be done by a backend plugin - message language is displayed when available (only one language per message for now) - fixed :history and :search commands - core (constants): new constants for messages type, XML namespace, entity type - core: *Message methods renamed to follow new code sytle (e.g. sendMessageToBridge => messageSendToBridge) - core (messages handling): fixed handling of language - core (messages handling): mes_data['from'] and ['to'] are now jid.JID - core (core.xmpp): reorganised message methods, added getNick() method to client.roster - plugin text commands: fixed plugin and adapted to new messages behaviour. client is now used in arguments instead of profile - plugins: added information for cancellation reason in CancelError calls - plugin XEP-0045: various improvments, but this plugin still need work: - trigger is used to avoid message already handled by the plugin to be handled a second time - changed the way to handle history, the last message from DB is checked and we request only messages since this one, in seconds (thanks Poezio folks :)) - subject reception is waited before sending the roomJoined signal, this way we are sure that everything including history is ready - cmd_* method now follow the new convention with client instead of profile - roomUserJoined and roomUserLeft messages are removed, the events are now handled with info message with a "ROOM_USER_JOINED" info subtype - probably other forgotten stuffs :p
author Goffi <goffi@goffi.org>
date Mon, 20 Jun 2016 18:41:53 +0200
parents 633b5c21aefd
children 200cd707a46d
comparison
equal deleted inserted replaced
1962:a45235d8dc93 1963:a2bc5089c2eb
44 pass 44 pass
45 45
46 46
47 CMD_KEY = "@command" 47 CMD_KEY = "@command"
48 CMD_TYPES = ('group', 'one2one', 'all') 48 CMD_TYPES = ('group', 'one2one', 'all')
49 FEEDBACK_INFO_TYPE = "TEXT_CMD"
49 50
50 51
51 class TextCommands(object): 52 class TextCommands(object):
52 #FIXME: doc strings for commands have to be translatable 53 #FIXME: doc strings for commands have to be translatable
53 # plugins need a dynamic translation system (translation 54 # plugins need a dynamic translation system (translation
146 if cmd_name in self._commands: 147 if cmd_name in self._commands:
147 suff=2 148 suff=2
148 while (cmd_name + str(suff)) in self._commands: 149 while (cmd_name + str(suff)) in self._commands:
149 suff+=1 150 suff+=1
150 new_name = cmd_name + str(suff) 151 new_name = cmd_name + str(suff)
151 log.warning(_(u"Conflict for command [%(old_name)s], renaming it to [%(new_name)s]") % {'old_name': cmd_name, 'new_name': new_name}) 152 log.warning(_(u"Conflict for command [{old_name}], renaming it to [{new_name}]").format(old_name=cmd_name, new_name=new_name))
152 cmd_name = new_name 153 cmd_name = new_name
153 self._commands[cmd_name] = cmd_data = OrderedDict({'callback':cmd}) # We use an Ordered dict to keep documenation order 154 self._commands[cmd_name] = cmd_data = OrderedDict({'callback':cmd}) # We use an Ordered dict to keep documenation order
154 cmd_data.update(self._parseDocString(cmd, cmd_name)) 155 cmd_data.update(self._parseDocString(cmd, cmd_name))
155 log.info(_("Registered text command [%s]") % cmd_name) 156 log.info(_("Registered text command [%s]") % cmd_name)
156 157
180 an "unparsed" key is added to message, containing part of the message not yet parsed 181 an "unparsed" key is added to message, containing part of the message not yet parsed
181 commands can be deferred or not 182 commands can be deferred or not
182 @param mess_data(dict): data comming from messageSend trigger 183 @param mess_data(dict): data comming from messageSend trigger
183 @param profile: %(doc_profile)s 184 @param profile: %(doc_profile)s
184 """ 185 """
185 profile = client.profile
186 try: 186 try:
187 msg = mess_data["message"][''] 187 msg = mess_data["message"]['']
188 msg_lang = '' 188 msg_lang = ''
189 except KeyError: 189 except KeyError:
190 try: 190 try:
215 else, abord message sending 215 else, abord message sending
216 """ 216 """
217 if ret: 217 if ret:
218 return mess_data 218 return mess_data
219 else: 219 else:
220 log.debug("text commands took over") 220 log.debug(u"text command detected ({})".format(command))
221 raise failure.Failure(exceptions.CancelError()) 221 raise failure.Failure(exceptions.CancelError())
222 222
223 def genericErrback(failure): 223 def genericErrback(failure):
224 try: 224 try:
225 msg = u"with condition {}".format(failure.value.condition) 225 msg = u"with condition {}".format(failure.value.condition)
226 except AttributeError: 226 except AttributeError:
227 msg = u"with error {}".format(failure.value) 227 msg = u"with error {}".format(failure.value)
228 self.feedBack(u"Command failed {}".format(msg), mess_data, profile) 228 self.feedBack(client, u"Command failed {}".format(msg), mess_data)
229 return False 229 return False
230 230
231 mess_data["unparsed"] = msg[1 + len(command):] # part not yet parsed of the message 231 mess_data["unparsed"] = msg[1 + len(command):] # part not yet parsed of the message
232 try: 232 try:
233 cmd_data = self._commands[command] 233 cmd_data = self._commands[command]
234 except KeyError: 234 except KeyError:
235 self.feedBack(_("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data, profile) 235 self.feedBack(client, _("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data)
236 log.debug("text commands took over") 236 log.debug("text command help message")
237 raise failure.Failure(exceptions.CancelError()) 237 raise failure.Failure(exceptions.CancelError())
238 else: 238 else:
239 if not self._contextValid(mess_data, cmd_data): 239 if not self._contextValid(mess_data, cmd_data):
240 # The command is not launched in the right context, we throw a message with help instructions 240 # The command is not launched in the right context, we throw a message with help instructions
241 context_txt = _("group discussions") if cmd_data["type"] == "group" else _("one to one discussions") 241 context_txt = _("group discussions") if cmd_data["type"] == "group" else _("one to one discussions")
242 feedback = _("/{command} command only applies in {context}.").format(command=command, context=context_txt) 242 feedback = _("/{command} command only applies in {context}.").format(command=command, context=context_txt)
243 self.feedBack(u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data, profile) 243 self.feedBack(client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data)
244 log.debug("text commands took over") 244 log.debug("text command invalid message")
245 raise failure.Failure(exceptions.CancelError()) 245 raise failure.Failure(exceptions.CancelError())
246 else: 246 else:
247 d = defer.maybeDeferred(cmd_data["callback"], mess_data, profile) 247 d = defer.maybeDeferred(cmd_data["callback"], client, mess_data)
248 d.addErrback(genericErrback) 248 d.addErrback(genericErrback)
249 d.addCallback(retHandling) 249 d.addCallback(retHandling)
250 250
251 return d or mess_data # if a command is detected, we should have a deferred, else we send the message normally 251 return d or mess_data # if a command is detected, we should have a deferred, else we send the message normally
252 252
274 if arg[-1] != '@': 274 if arg[-1] != '@':
275 return jid.JID(arg) 275 return jid.JID(arg)
276 return jid.JID(arg + service_jid) 276 return jid.JID(arg + service_jid)
277 return jid.JID(u"%s@%s" % (arg, service_jid)) 277 return jid.JID(u"%s@%s" % (arg, service_jid))
278 278
279 def feedBack(self, message, mess_data, profile): 279 def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE):
280 """Give a message back to the user""" 280 """Give a message back to the user"""
281 if mess_data["type"] == 'groupchat': 281 if mess_data["type"] == 'groupchat':
282 _from = mess_data["to"].userhostJID() 282 to_ = mess_data["to"].userhostJID()
283 else: 283 else:
284 _from = self.host.getJidNStream(profile)[0] 284 to_ = client.jid
285 285
286 self.host.bridge.messageNew(unicode(mess_data["to"]), {'': message}, {}, C.MESS_TYPE_INFO, unicode(_from), {}, profile=profile) 286 # we need to invert send message back, so sender need to original destinee
287 287 mess_data["from"] = mess_data["to"]
288 def cmd_whois(self, mess_data, profile): 288 mess_data["to"] = to_
289 mess_data["type"] = C.MESS_TYPE_INFO
290 mess_data["message"] = {'': message}
291 mess_data["extra"]["info_type"] = info_type
292 self.host.messageSendToBridge(mess_data, client)
293
294 def cmd_whois(self, client, mess_data):
289 """show informations on entity 295 """show informations on entity
290 296
291 @command: [JID|ROOM_NICK] 297 @command: [JID|ROOM_NICK]
292 - JID: entity to request 298 - JID: entity to request
293 - ROOM_NICK: nick of the room to request 299 - ROOM_NICK: nick of the room to request
297 entity = mess_data["unparsed"].strip() 303 entity = mess_data["unparsed"].strip()
298 304
299 if mess_data['type'] == "groupchat": 305 if mess_data['type'] == "groupchat":
300 room = mess_data["to"].userhostJID() 306 room = mess_data["to"].userhostJID()
301 try: 307 try:
302 if self.host.plugins["XEP-0045"].isNickInRoom(room, entity, profile): 308 if self.host.plugins["XEP-0045"].isNickInRoom(room, entity, client.profile):
303 entity = u"%s/%s" % (room, entity) 309 entity = u"%s/%s" % (room, entity)
304 except KeyError: 310 except KeyError:
305 log.warning("plugin XEP-0045 is not present") 311 log.warning("plugin XEP-0045 is not present")
306 312
307 if not entity: 313 if not entity:
310 try: 316 try:
311 target_jid = jid.JID(entity) 317 target_jid = jid.JID(entity)
312 if not target_jid.user or not target_jid.host: 318 if not target_jid.user or not target_jid.host:
313 raise jid.InvalidFormat 319 raise jid.InvalidFormat
314 except (RuntimeError, jid.InvalidFormat, AttributeError): 320 except (RuntimeError, jid.InvalidFormat, AttributeError):
315 self.feedBack(_("Invalid jid, can't whois"), mess_data, profile) 321 self.feedBack(client, _("Invalid jid, can't whois"), mess_data)
316 return False 322 return False
317 323
318 if not target_jid.resource: 324 if not target_jid.resource:
319 target_jid.resource = self.host.memory.getMainResource(target_jid, profile) 325 target_jid.resource = self.host.memory.getMainResource(target_jid, client.profile)
320 326
321 whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}] 327 whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}]
322 328
323 d = defer.succeed(None) 329 d = defer.succeed(None)
324 for ignore, callback in self._whois: 330 for ignore, callback in self._whois:
325 d.addCallback(lambda ignore: callback(whois_msg, mess_data, target_jid, profile)) 331 d.addCallback(lambda ignore: callback(client, whois_msg, mess_data, target_jid))
326 332
327 def feedBack(ignore): 333 def feedBack(ignore):
328 self.feedBack(u"\n".join(whois_msg), mess_data, profile) 334 self.feedBack(client, u"\n".join(whois_msg), mess_data)
329 return False 335 return False
330 336
331 d.addCallback(feedBack) 337 d.addCallback(feedBack)
332 return d 338 return d
333 339
343 arg_name = doc_name[8:] 349 arg_name = doc_name[8:]
344 strings.append(u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help))) 350 strings.append(u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help)))
345 351
346 return strings 352 return strings
347 353
348 def cmd_help(self, mess_data, profile): 354 def cmd_help(self, client, mess_data):
349 """show help on available commands 355 """show help on available commands
350 356
351 @command: [cmd_name] 357 @command: [cmd_name]
352 - cmd_name: name of the command for detailed help 358 - cmd_name: name of the command for detailed help
353 """ 359 """
354 cmd_name = mess_data["unparsed"].strip() 360 cmd_name = mess_data["unparsed"].strip()
355 if cmd_name and cmd_name[0] == "/": 361 if cmd_name and cmd_name[0] == "/":
356 cmd_name = cmd_name[1:] 362 cmd_name = cmd_name[1:]
357 if cmd_name and cmd_name not in self._commands: 363 if cmd_name and cmd_name not in self._commands:
358 self.feedBack(_(u"Invalid command name [{}]\n".format(cmd_name)), mess_data, profile) 364 self.feedBack(client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data)
359 cmd_name = "" 365 cmd_name = ""
360 if not cmd_name: 366 if not cmd_name:
361 # we show the global help 367 # we show the global help
362 longuest = max([len(command) for command in self._commands]) 368 longuest = max([len(command) for command in self._commands])
363 help_cmds = [] 369 help_cmds = []
382 name=cmd_name, 388 name=cmd_name,
383 short_help=cmd_data['doc_short_help'], 389 short_help=cmd_data['doc_short_help'],
384 syntax=_(" "*4+"syntax: {}\n").format(syntax) if syntax else "", 390 syntax=_(" "*4+"syntax: {}\n").format(syntax) if syntax else "",
385 args_help=u'\n'.join([u" "*8+"{}".format(line) for line in self._getArgsHelp(cmd_data)])) 391 args_help=u'\n'.join([u" "*8+"{}".format(line) for line in self._getArgsHelp(cmd_data)]))
386 392
387 self.feedBack(help_mess, mess_data, profile) 393 self.feedBack(client, help_mess, mess_data)
388
389 def cmd_me(self, mess_data, profile):
390 """Display a message at third person
391
392 @command: message
393 - message: message to display at the third person
394 """
395 # We just catch the method and continue it as the frontends should manage /me display
396 return True