comparison sat/plugins/plugin_misc_text_commands.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents ae5f63e5ed2c
children
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
64 64
65 def __init__(self, host): 65 def __init__(self, host):
66 log.info(_("Text commands initialization")) 66 log.info(_("Text commands initialization"))
67 self.host = host 67 self.host = host
68 # this is internal command, so we set high priority 68 # this is internal command, so we set high priority
69 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=1000000) 69 host.trigger.add("sendMessage", self.send_message_trigger, priority=1000000)
70 self._commands = {} 70 self._commands = {}
71 self._whois = [] 71 self._whois = []
72 self.registerTextCommands(self) 72 self.register_text_commands(self)
73 73
74 def _parseDocString(self, cmd, cmd_name): 74 def _parse_doc_string(self, cmd, cmd_name):
75 """Parse a docstring to get text command data 75 """Parse a docstring to get text command data
76 76
77 @param cmd: function or method callback for the command, 77 @param cmd: function or method callback for the command,
78 its docstring will be used for self documentation in the following way: 78 its docstring will be used for self documentation in the following way:
79 - first line is the command short documentation, shown with /help 79 - first line is the command short documentation, shown with /help
148 ) 148 )
149 ) 149 )
150 150
151 return data 151 return data
152 152
153 def registerTextCommands(self, instance): 153 def register_text_commands(self, instance):
154 """ Add a text command 154 """ Add a text command
155 155
156 @param instance: instance of a class containing text commands 156 @param instance: instance of a class containing text commands
157 """ 157 """
158 for attr in dir(instance): 158 for attr in dir(instance):
174 "Conflict for command [{old_name}], renaming it to [{new_name}]" 174 "Conflict for command [{old_name}], renaming it to [{new_name}]"
175 ).format(old_name=cmd_name, new_name=new_name) 175 ).format(old_name=cmd_name, new_name=new_name)
176 ) 176 )
177 cmd_name = new_name 177 cmd_name = new_name
178 self._commands[cmd_name] = cmd_data = {"callback": cmd} 178 self._commands[cmd_name] = cmd_data = {"callback": cmd}
179 cmd_data.update(self._parseDocString(cmd, cmd_name)) 179 cmd_data.update(self._parse_doc_string(cmd, cmd_name))
180 log.info(_("Registered text command [%s]") % cmd_name) 180 log.info(_("Registered text command [%s]") % cmd_name)
181 181
182 def addWhoIsCb(self, callback, priority=0): 182 def add_who_is_cb(self, callback, priority=0):
183 """Add a callback which give information to the /whois command 183 """Add a callback which give information to the /whois command
184 184
185 @param callback: a callback which will be called with the following arguments 185 @param callback: a callback which will be called with the following arguments
186 - whois_msg: list of information strings to display, callback need to append 186 - whois_msg: list of information strings to display, callback need to append
187 its own strings to it 187 its own strings to it
191 be displayed first) 191 be displayed first)
192 """ 192 """
193 self._whois.append((priority, callback)) 193 self._whois.append((priority, callback))
194 self._whois.sort(key=lambda item: item[0], reverse=True) 194 self._whois.sort(key=lambda item: item[0], reverse=True)
195 195
196 def sendMessageTrigger( 196 def send_message_trigger(
197 self, client, mess_data, pre_xml_treatments, post_xml_treatments 197 self, client, mess_data, pre_xml_treatments, post_xml_treatments
198 ): 198 ):
199 """Install SendMessage command hook """ 199 """Install SendMessage command hook """
200 pre_xml_treatments.addCallback(self._sendMessageCmdHook, client) 200 pre_xml_treatments.addCallback(self._send_message_cmd_hook, client)
201 return True 201 return True
202 202
203 def _sendMessageCmdHook(self, mess_data, client): 203 def _send_message_cmd_hook(self, mess_data, client):
204 """ Check text commands in message, and react consequently 204 """ Check text commands in message, and react consequently
205 205
206 msg starting with / are potential command. If a command is found, it is executed, 206 msg starting with / are potential command. If a command is found, it is executed,
207 else an help message is sent. 207 else an help message is sent.
208 msg starting with // are escaped: they are sent with a single / 208 msg starting with // are escaped: they are sent with a single /
237 237
238 # we have a command 238 # we have a command
239 d = None 239 d = None
240 command = msg[1:].partition(" ")[0].lower().strip() 240 command = msg[1:].partition(" ")[0].lower().strip()
241 if not command.isidentifier(): 241 if not command.isidentifier():
242 self.feedBack( 242 self.feed_back(
243 client, 243 client,
244 _("Invalid command /%s. ") % command + self.HELP_SUGGESTION, 244 _("Invalid command /%s. ") % command + self.HELP_SUGGESTION,
245 mess_data, 245 mess_data,
246 ) 246 )
247 raise failure.Failure(exceptions.CancelError()) 247 raise failure.Failure(exceptions.CancelError())
248 248
249 # looks like an actual command, we try to call the corresponding method 249 # looks like an actual command, we try to call the corresponding method
250 def retHandling(ret): 250 def ret_handling(ret):
251 """ Handle command return value: 251 """ Handle command return value:
252 if ret is True, normally send message (possibly modified by command) 252 if ret is True, normally send message (possibly modified by command)
253 else, abord message sending 253 else, abord message sending
254 """ 254 """
255 if ret: 255 if ret:
256 return mess_data 256 return mess_data
257 else: 257 else:
258 log.debug("text command detected ({})".format(command)) 258 log.debug("text command detected ({})".format(command))
259 raise failure.Failure(exceptions.CancelError()) 259 raise failure.Failure(exceptions.CancelError())
260 260
261 def genericErrback(failure): 261 def generic_errback(failure):
262 try: 262 try:
263 msg = "with condition {}".format(failure.value.condition) 263 msg = "with condition {}".format(failure.value.condition)
264 except AttributeError: 264 except AttributeError:
265 msg = "with error {}".format(failure.value) 265 msg = "with error {}".format(failure.value)
266 self.feedBack(client, "Command failed {}".format(msg), mess_data) 266 self.feed_back(client, "Command failed {}".format(msg), mess_data)
267 return False 267 return False
268 268
269 mess_data["unparsed"] = msg[ 269 mess_data["unparsed"] = msg[
270 1 + len(command) : 270 1 + len(command) :
271 ] # part not yet parsed of the message 271 ] # part not yet parsed of the message
272 try: 272 try:
273 cmd_data = self._commands[command] 273 cmd_data = self._commands[command]
274 except KeyError: 274 except KeyError:
275 self.feedBack( 275 self.feed_back(
276 client, 276 client,
277 _("Unknown command /%s. ") % command + self.HELP_SUGGESTION, 277 _("Unknown command /%s. ") % command + self.HELP_SUGGESTION,
278 mess_data, 278 mess_data,
279 ) 279 )
280 log.debug("text command help message") 280 log.debug("text command help message")
281 raise failure.Failure(exceptions.CancelError()) 281 raise failure.Failure(exceptions.CancelError())
282 else: 282 else:
283 if not self._contextValid(mess_data, cmd_data): 283 if not self._context_valid(mess_data, cmd_data):
284 # The command is not launched in the right context, we throw a message with help instructions 284 # The command is not launched in the right context, we throw a message with help instructions
285 context_txt = ( 285 context_txt = (
286 _("group discussions") 286 _("group discussions")
287 if cmd_data["type"] == "group" 287 if cmd_data["type"] == "group"
288 else _("one to one discussions") 288 else _("one to one discussions")
289 ) 289 )
290 feedback = _("/{command} command only applies in {context}.").format( 290 feedback = _("/{command} command only applies in {context}.").format(
291 command=command, context=context_txt 291 command=command, context=context_txt
292 ) 292 )
293 self.feedBack( 293 self.feed_back(
294 client, "{} {}".format(feedback, self.HELP_SUGGESTION), mess_data 294 client, "{} {}".format(feedback, self.HELP_SUGGESTION), mess_data
295 ) 295 )
296 log.debug("text command invalid message") 296 log.debug("text command invalid message")
297 raise failure.Failure(exceptions.CancelError()) 297 raise failure.Failure(exceptions.CancelError())
298 else: 298 else:
299 d = utils.asDeferred(cmd_data["callback"], client, mess_data) 299 d = utils.as_deferred(cmd_data["callback"], client, mess_data)
300 d.addErrback(genericErrback) 300 d.addErrback(generic_errback)
301 d.addCallback(retHandling) 301 d.addCallback(ret_handling)
302 302
303 return d 303 return d
304 304
305 def _contextValid(self, mess_data, cmd_data): 305 def _context_valid(self, mess_data, cmd_data):
306 """Tell if a command can be used in the given context 306 """Tell if a command can be used in the given context
307 307
308 @param mess_data(dict): message data as given in sendMessage trigger 308 @param mess_data(dict): message data as given in sendMessage trigger
309 @param cmd_data(dict): command data as returned by self._parseDocString 309 @param cmd_data(dict): command data as returned by self._parse_doc_string
310 @return (bool): True if command can be used in this context 310 @return (bool): True if command can be used in this context
311 """ 311 """
312 if (cmd_data["type"] == "group" and mess_data["type"] != "groupchat") or ( 312 if (cmd_data["type"] == "group" and mess_data["type"] != "groupchat") or (
313 cmd_data["type"] == "one2one" and mess_data["type"] == "groupchat" 313 cmd_data["type"] == "one2one" and mess_data["type"] == "groupchat"
314 ): 314 ):
315 return False 315 return False
316 return True 316 return True
317 317
318 def getRoomJID(self, arg, service_jid): 318 def get_room_jid(self, arg, service_jid):
319 """Return a room jid with a shortcut 319 """Return a room jid with a shortcut
320 320
321 @param arg: argument: can be a full room jid (e.g.: sat@chat.jabberfr.org) 321 @param arg: argument: can be a full room jid (e.g.: sat@chat.jabberfr.org)
322 or a shortcut (e.g.: sat or sat@ for sat on current service) 322 or a shortcut (e.g.: sat or sat@ for sat on current service)
323 @param service_jid: jid of the current service (e.g.: chat.jabberfr.org) 323 @param service_jid: jid of the current service (e.g.: chat.jabberfr.org)
327 if arg[-1] != "@": 327 if arg[-1] != "@":
328 return jid.JID(arg) 328 return jid.JID(arg)
329 return jid.JID(arg + service_jid) 329 return jid.JID(arg + service_jid)
330 return jid.JID(f"{arg}@{service_jid}") 330 return jid.JID(f"{arg}@{service_jid}")
331 331
332 def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE): 332 def feed_back(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE):
333 """Give a message back to the user""" 333 """Give a message back to the user"""
334 if mess_data["type"] == "groupchat": 334 if mess_data["type"] == "groupchat":
335 to_ = mess_data["to"].userhostJID() 335 to_ = mess_data["to"].userhostJID()
336 else: 336 else:
337 to_ = client.jid 337 to_ = client.jid
340 mess_data["from"] = mess_data["to"] 340 mess_data["from"] = mess_data["to"]
341 mess_data["to"] = to_ 341 mess_data["to"] = to_
342 mess_data["type"] = C.MESS_TYPE_INFO 342 mess_data["type"] = C.MESS_TYPE_INFO
343 mess_data["message"] = {"": message} 343 mess_data["message"] = {"": message}
344 mess_data["extra"]["info_type"] = info_type 344 mess_data["extra"]["info_type"] = info_type
345 client.messageSendToBridge(mess_data) 345 client.message_send_to_bridge(mess_data)
346 346
347 def cmd_whois(self, client, mess_data): 347 def cmd_whois(self, client, mess_data):
348 """show informations on entity 348 """show informations on entity
349 349
350 @command: [JID|ROOM_NICK] 350 @command: [JID|ROOM_NICK]
356 entity = mess_data["unparsed"].strip() 356 entity = mess_data["unparsed"].strip()
357 357
358 if mess_data["type"] == "groupchat": 358 if mess_data["type"] == "groupchat":
359 room = mess_data["to"].userhostJID() 359 room = mess_data["to"].userhostJID()
360 try: 360 try:
361 if self.host.plugins["XEP-0045"].isNickInRoom(client, room, entity): 361 if self.host.plugins["XEP-0045"].is_nick_in_room(client, room, entity):
362 entity = "%s/%s" % (room, entity) 362 entity = "%s/%s" % (room, entity)
363 except KeyError: 363 except KeyError:
364 log.warning("plugin XEP-0045 is not present") 364 log.warning("plugin XEP-0045 is not present")
365 365
366 if not entity: 366 if not entity:
369 try: 369 try:
370 target_jid = jid.JID(entity) 370 target_jid = jid.JID(entity)
371 if not target_jid.user or not target_jid.host: 371 if not target_jid.user or not target_jid.host:
372 raise jid.InvalidFormat 372 raise jid.InvalidFormat
373 except (RuntimeError, jid.InvalidFormat, AttributeError): 373 except (RuntimeError, jid.InvalidFormat, AttributeError):
374 self.feedBack(client, _("Invalid jid, can't whois"), mess_data) 374 self.feed_back(client, _("Invalid jid, can't whois"), mess_data)
375 return False 375 return False
376 376
377 if not target_jid.resource: 377 if not target_jid.resource:
378 target_jid.resource = self.host.memory.getMainResource(client, target_jid) 378 target_jid.resource = self.host.memory.main_resource_get(client, target_jid)
379 379
380 whois_msg = [_("whois for %(jid)s") % {"jid": target_jid}] 380 whois_msg = [_("whois for %(jid)s") % {"jid": target_jid}]
381 381
382 d = defer.succeed(None) 382 d = defer.succeed(None)
383 for __, callback in self._whois: 383 for __, callback in self._whois:
384 d.addCallback( 384 d.addCallback(
385 lambda __: callback(client, whois_msg, mess_data, target_jid) 385 lambda __: callback(client, whois_msg, mess_data, target_jid)
386 ) 386 )
387 387
388 def feedBack(__): 388 def feed_back(__):
389 self.feedBack(client, "\n".join(whois_msg), mess_data) 389 self.feed_back(client, "\n".join(whois_msg), mess_data)
390 return False 390 return False
391 391
392 d.addCallback(feedBack) 392 d.addCallback(feed_back)
393 return d 393 return d
394 394
395 def _getArgsHelp(self, cmd_data): 395 def _get_args_help(self, cmd_data):
396 """Return help string for args of cmd_name, according to docstring data 396 """Return help string for args of cmd_name, according to docstring data
397 397
398 @param cmd_data: command data 398 @param cmd_data: command data
399 @return (list[unicode]): help strings 399 @return (list[unicode]): help strings
400 """ 400 """
418 # We just ignore the command as the match is done on receiption by clients 418 # We just ignore the command as the match is done on receiption by clients
419 return True 419 return True
420 420
421 def cmd_whoami(self, client, mess_data): 421 def cmd_whoami(self, client, mess_data):
422 """give your own jid""" 422 """give your own jid"""
423 self.feedBack(client, client.jid.full(), mess_data) 423 self.feed_back(client, client.jid.full(), mess_data)
424 424
425 def cmd_help(self, client, mess_data): 425 def cmd_help(self, client, mess_data):
426 """show help on available commands 426 """show help on available commands
427 427
428 @command: [cmd_name] 428 @command: [cmd_name]
430 """ 430 """
431 cmd_name = mess_data["unparsed"].strip() 431 cmd_name = mess_data["unparsed"].strip()
432 if cmd_name and cmd_name[0] == "/": 432 if cmd_name and cmd_name[0] == "/":
433 cmd_name = cmd_name[1:] 433 cmd_name = cmd_name[1:]
434 if cmd_name and cmd_name not in self._commands: 434 if cmd_name and cmd_name not in self._commands:
435 self.feedBack( 435 self.feed_back(
436 client, _("Invalid command name [{}]\n".format(cmd_name)), mess_data 436 client, _("Invalid command name [{}]\n".format(cmd_name)), mess_data
437 ) 437 )
438 cmd_name = "" 438 cmd_name = ""
439 if not cmd_name: 439 if not cmd_name:
440 # we show the global help 440 # we show the global help
441 longuest = max([len(command) for command in self._commands]) 441 longuest = max([len(command) for command in self._commands])
442 help_cmds = [] 442 help_cmds = []
443 443
444 for command in sorted(self._commands): 444 for command in sorted(self._commands):
445 cmd_data = self._commands[command] 445 cmd_data = self._commands[command]
446 if not self._contextValid(mess_data, cmd_data): 446 if not self._context_valid(mess_data, cmd_data):
447 continue 447 continue
448 spaces = (longuest - len(command)) * " " 448 spaces = (longuest - len(command)) * " "
449 help_cmds.append( 449 help_cmds.append(
450 " /{command}: {spaces} {short_help}".format( 450 " /{command}: {spaces} {short_help}".format(
451 command=command, 451 command=command,
462 help_mess = _("/{name}: {short_help}\n{syntax}{args_help}").format( 462 help_mess = _("/{name}: {short_help}\n{syntax}{args_help}").format(
463 name=cmd_name, 463 name=cmd_name,
464 short_help=cmd_data["doc_short_help"], 464 short_help=cmd_data["doc_short_help"],
465 syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "", 465 syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "",
466 args_help="\n".join( 466 args_help="\n".join(
467 [" " * 8 + "{}".format(line) for line in self._getArgsHelp(cmd_data)] 467 [" " * 8 + "{}".format(line) for line in self._get_args_help(cmd_data)]
468 ), 468 ),
469 ) 469 )
470 470
471 self.feedBack(client, help_mess, mess_data) 471 self.feed_back(client, help_mess, mess_data)