comparison sat/plugins/plugin_misc_text_commands.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents dbfedde9bc61
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SàT plugin for managing text commands 4 # SàT plugin for managing text commands
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
55 # FIXME: doc strings for commands have to be translatable 55 # FIXME: doc strings for commands have to be translatable
56 # plugins need a dynamic translation system (translation 56 # plugins need a dynamic translation system (translation
57 # should be downloadable independently) 57 # should be downloadable independently)
58 58
59 HELP_SUGGESTION = _( 59 HELP_SUGGESTION = _(
60 u"Type '/help' to get a list of the available commands. If you didn't want to " 60 "Type '/help' to get a list of the available commands. If you didn't want to "
61 u"use a command, please start your message with '//' to escape the slash." 61 "use a command, please start your message with '//' to escape the slash."
62 ) 62 )
63 63
64 def __init__(self, host): 64 def __init__(self, host):
65 log.info(_("Text commands initialization")) 65 log.info(_("Text commands initialization"))
66 self.host = host 66 self.host = host
85 - "doc_arg_[name]": the doc of [name] argument 85 - "doc_arg_[name]": the doc of [name] argument
86 """ 86 """
87 data = OrderedDict([("doc_short_help", ""), ("type", "all"), ("args", "")]) 87 data = OrderedDict([("doc_short_help", ""), ("type", "all"), ("args", "")])
88 docstring = cmd.__doc__ 88 docstring = cmd.__doc__
89 if docstring is None: 89 if docstring is None:
90 log.warning(u"No docstring found for command {}".format(cmd_name)) 90 log.warning("No docstring found for command {}".format(cmd_name))
91 docstring = "" 91 docstring = ""
92 92
93 doc_data = docstring.split("\n") 93 doc_data = docstring.split("\n")
94 data["doc_short_help"] = doc_data[0] 94 data["doc_short_help"] = doc_data[0]
95 95
135 135
136 # args 136 # args
137 data["args"] = stripped[colon_idx + 1 :].strip() 137 data["args"] = stripped[colon_idx + 1 :].strip()
138 except InvalidCommandSyntax as e: 138 except InvalidCommandSyntax as e:
139 log.warning( 139 log.warning(
140 u"Invalid command syntax for command {command}: {message}".format( 140 "Invalid command syntax for command {command}: {message}".format(
141 command=cmd_name, message=e.message 141 command=cmd_name, message=e.message
142 ) 142 )
143 ) 143 )
144 144
145 return data 145 return data
151 """ 151 """
152 for attr in dir(instance): 152 for attr in dir(instance):
153 if attr.startswith("cmd_"): 153 if attr.startswith("cmd_"):
154 cmd = getattr(instance, attr) 154 cmd = getattr(instance, attr)
155 if not callable(cmd): 155 if not callable(cmd):
156 log.warning(_(u"Skipping not callable [%s] attribute") % attr) 156 log.warning(_("Skipping not callable [%s] attribute") % attr)
157 continue 157 continue
158 cmd_name = attr[4:] 158 cmd_name = attr[4:]
159 if not cmd_name: 159 if not cmd_name:
160 log.warning(_("Skipping cmd_ method")) 160 log.warning(_("Skipping cmd_ method"))
161 if cmd_name in self._commands: 161 if cmd_name in self._commands:
163 while (cmd_name + str(suff)) in self._commands: 163 while (cmd_name + str(suff)) in self._commands:
164 suff += 1 164 suff += 1
165 new_name = cmd_name + str(suff) 165 new_name = cmd_name + str(suff)
166 log.warning( 166 log.warning(
167 _( 167 _(
168 u"Conflict for command [{old_name}], renaming it to [{new_name}]" 168 "Conflict for command [{old_name}], renaming it to [{new_name}]"
169 ).format(old_name=cmd_name, new_name=new_name) 169 ).format(old_name=cmd_name, new_name=new_name)
170 ) 170 )
171 cmd_name = new_name 171 cmd_name = new_name
172 self._commands[cmd_name] = cmd_data = OrderedDict( 172 self._commands[cmd_name] = cmd_data = OrderedDict(
173 {"callback": cmd} 173 {"callback": cmd}
214 msg = mess_data["message"][""] 214 msg = mess_data["message"][""]
215 msg_lang = "" 215 msg_lang = ""
216 except KeyError: 216 except KeyError:
217 try: 217 try:
218 # we have not default message, we try to take the first found 218 # we have not default message, we try to take the first found
219 msg_lang, msg = mess_data["message"].iteritems().next() 219 msg_lang, msg = next(iter(mess_data["message"].items()))
220 except StopIteration: 220 except StopIteration:
221 log.debug(u"No message found, skipping text commands") 221 log.debug("No message found, skipping text commands")
222 return mess_data 222 return mess_data
223 223
224 try: 224 try:
225 if msg[:2] == "//": 225 if msg[:2] == "//":
226 # we have a double '/', it's the escape sequence 226 # we have a double '/', it's the escape sequence
242 else, abord message sending 242 else, abord message sending
243 """ 243 """
244 if ret: 244 if ret:
245 return mess_data 245 return mess_data
246 else: 246 else:
247 log.debug(u"text command detected ({})".format(command)) 247 log.debug("text command detected ({})".format(command))
248 raise failure.Failure(exceptions.CancelError()) 248 raise failure.Failure(exceptions.CancelError())
249 249
250 def genericErrback(failure): 250 def genericErrback(failure):
251 try: 251 try:
252 msg = u"with condition {}".format(failure.value.condition) 252 msg = "with condition {}".format(failure.value.condition)
253 except AttributeError: 253 except AttributeError:
254 msg = u"with error {}".format(failure.value) 254 msg = "with error {}".format(failure.value)
255 self.feedBack(client, u"Command failed {}".format(msg), mess_data) 255 self.feedBack(client, "Command failed {}".format(msg), mess_data)
256 return False 256 return False
257 257
258 mess_data["unparsed"] = msg[ 258 mess_data["unparsed"] = msg[
259 1 + len(command) : 259 1 + len(command) :
260 ] # part not yet parsed of the message 260 ] # part not yet parsed of the message
278 ) 278 )
279 feedback = _("/{command} command only applies in {context}.").format( 279 feedback = _("/{command} command only applies in {context}.").format(
280 command=command, context=context_txt 280 command=command, context=context_txt
281 ) 281 )
282 self.feedBack( 282 self.feedBack(
283 client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data 283 client, "{} {}".format(feedback, self.HELP_SUGGESTION), mess_data
284 ) 284 )
285 log.debug("text command invalid message") 285 log.debug("text command invalid message")
286 raise failure.Failure(exceptions.CancelError()) 286 raise failure.Failure(exceptions.CancelError())
287 else: 287 else:
288 d = defer.maybeDeferred(cmd_data["callback"], client, mess_data) 288 d = defer.maybeDeferred(cmd_data["callback"], client, mess_data)
316 nb_arobas = arg.count("@") 316 nb_arobas = arg.count("@")
317 if nb_arobas == 1: 317 if nb_arobas == 1:
318 if arg[-1] != "@": 318 if arg[-1] != "@":
319 return jid.JID(arg) 319 return jid.JID(arg)
320 return jid.JID(arg + service_jid) 320 return jid.JID(arg + service_jid)
321 return jid.JID(u"%s@%s" % (arg, service_jid)) 321 return jid.JID("%s@%s" % (arg, service_jid))
322 322
323 def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE): 323 def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE):
324 """Give a message back to the user""" 324 """Give a message back to the user"""
325 if mess_data["type"] == "groupchat": 325 if mess_data["type"] == "groupchat":
326 to_ = mess_data["to"].userhostJID() 326 to_ = mess_data["to"].userhostJID()
348 348
349 if mess_data["type"] == "groupchat": 349 if mess_data["type"] == "groupchat":
350 room = mess_data["to"].userhostJID() 350 room = mess_data["to"].userhostJID()
351 try: 351 try:
352 if self.host.plugins["XEP-0045"].isNickInRoom(client, room, entity): 352 if self.host.plugins["XEP-0045"].isNickInRoom(client, room, entity):
353 entity = u"%s/%s" % (room, entity) 353 entity = "%s/%s" % (room, entity)
354 except KeyError: 354 except KeyError:
355 log.warning("plugin XEP-0045 is not present") 355 log.warning("plugin XEP-0045 is not present")
356 356
357 if not entity: 357 if not entity:
358 target_jid = mess_data["to"] 358 target_jid = mess_data["to"]
366 return False 366 return False
367 367
368 if not target_jid.resource: 368 if not target_jid.resource:
369 target_jid.resource = self.host.memory.getMainResource(client, target_jid) 369 target_jid.resource = self.host.memory.getMainResource(client, target_jid)
370 370
371 whois_msg = [_(u"whois for %(jid)s") % {"jid": target_jid}] 371 whois_msg = [_("whois for %(jid)s") % {"jid": target_jid}]
372 372
373 d = defer.succeed(None) 373 d = defer.succeed(None)
374 for ignore, callback in self._whois: 374 for ignore, callback in self._whois:
375 d.addCallback( 375 d.addCallback(
376 lambda ignore: callback(client, whois_msg, mess_data, target_jid) 376 lambda ignore: callback(client, whois_msg, mess_data, target_jid)
377 ) 377 )
378 378
379 def feedBack(ignore): 379 def feedBack(ignore):
380 self.feedBack(client, u"\n".join(whois_msg), mess_data) 380 self.feedBack(client, "\n".join(whois_msg), mess_data)
381 return False 381 return False
382 382
383 d.addCallback(feedBack) 383 d.addCallback(feedBack)
384 return d 384 return d
385 385
388 388
389 @param cmd_data: command data 389 @param cmd_data: command data
390 @return (list[unicode]): help strings 390 @return (list[unicode]): help strings
391 """ 391 """
392 strings = [] 392 strings = []
393 for doc_name, doc_help in cmd_data.iteritems(): 393 for doc_name, doc_help in cmd_data.items():
394 if doc_name.startswith("doc_arg_"): 394 if doc_name.startswith("doc_arg_"):
395 arg_name = doc_name[8:] 395 arg_name = doc_name[8:]
396 strings.append( 396 strings.append(
397 u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help)) 397 "- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help))
398 ) 398 )
399 399
400 return strings 400 return strings
401 401
402 def cmd_me(self, client, mess_data): 402 def cmd_me(self, client, mess_data):
422 cmd_name = mess_data["unparsed"].strip() 422 cmd_name = mess_data["unparsed"].strip()
423 if cmd_name and cmd_name[0] == "/": 423 if cmd_name and cmd_name[0] == "/":
424 cmd_name = cmd_name[1:] 424 cmd_name = cmd_name[1:]
425 if cmd_name and cmd_name not in self._commands: 425 if cmd_name and cmd_name not in self._commands:
426 self.feedBack( 426 self.feedBack(
427 client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data 427 client, _("Invalid command name [{}]\n".format(cmd_name)), mess_data
428 ) 428 )
429 cmd_name = "" 429 cmd_name = ""
430 if not cmd_name: 430 if not cmd_name:
431 # we show the global help 431 # we show the global help
432 longuest = max([len(command) for command in self._commands]) 432 longuest = max([len(command) for command in self._commands])
443 spaces=spaces, 443 spaces=spaces,
444 short_help=cmd_data["doc_short_help"], 444 short_help=cmd_data["doc_short_help"],
445 ) 445 )
446 ) 446 )
447 447
448 help_mess = _(u"Text commands available:\n%s") % (u"\n".join(help_cmds),) 448 help_mess = _("Text commands available:\n%s") % ("\n".join(help_cmds),)
449 else: 449 else:
450 # we show detailled help for a command 450 # we show detailled help for a command
451 cmd_data = self._commands[cmd_name] 451 cmd_data = self._commands[cmd_name]
452 syntax = cmd_data["args"] 452 syntax = cmd_data["args"]
453 help_mess = _(u"/{name}: {short_help}\n{syntax}{args_help}").format( 453 help_mess = _("/{name}: {short_help}\n{syntax}{args_help}").format(
454 name=cmd_name, 454 name=cmd_name,
455 short_help=cmd_data["doc_short_help"], 455 short_help=cmd_data["doc_short_help"],
456 syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "", 456 syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "",
457 args_help=u"\n".join( 457 args_help="\n".join(
458 [u" " * 8 + "{}".format(line) for line in self._getArgsHelp(cmd_data)] 458 [" " * 8 + "{}".format(line) for line in self._getArgsHelp(cmd_data)]
459 ), 459 ),
460 ) 460 )
461 461
462 self.feedBack(client, help_mess, mess_data) 462 self.feedBack(client, help_mess, mess_data)