From 1e334f20f1bd4a801ffba915f610b10c5cf9912a Mon Sep 17 00:00:00 2001 From: "kujiu (@uberwald)" Date: Sun, 3 Sep 2023 18:26:58 +0200 Subject: [PATCH] Poezio theme --- README.rst | 1 + common/.config/nvim/init.lua | 2 + common/.config/tmux/tmux.conf | 2 +- .../.config/poezio/poezio.cfg | 24 +- desktop/.local/share/poezio/plugins/README | 1 - .../.local/share/poezio/plugins/__init__.py | 0 desktop/.local/share/poezio/plugins/admin.py | 139 --- desktop/.local/share/poezio/plugins/alias.py | 207 ---- desktop/.local/share/poezio/plugins/amsg.py | 35 - .../share/poezio/plugins/autocorrect.py | 97 -- desktop/.local/share/poezio/plugins/b64.py | 70 -- desktop/.local/share/poezio/plugins/bob.py | 83 -- .../.local/share/poezio/plugins/capslock.py | 15 - .../share/poezio/plugins/change_title.py | 21 - .../.local/share/poezio/plugins/close_all.py | 44 - desktop/.local/share/poezio/plugins/code.py | 52 - .../.local/share/poezio/plugins/contact.py | 60 - desktop/.local/share/poezio/plugins/csi.py | 51 - desktop/.local/share/poezio/plugins/cyber.py | 42 - .../.local/share/poezio/plugins/day_change.py | 37 - desktop/.local/share/poezio/plugins/dice.py | 122 -- desktop/.local/share/poezio/plugins/disco.py | 106 -- .../poezio/plugins/display_corrections.py | 84 -- desktop/.local/share/poezio/plugins/double.py | 14 - desktop/.local/share/poezio/plugins/embed.py | 50 - .../share/poezio/plugins/emoji_ascii.py | 60 - desktop/.local/share/poezio/plugins/exec.py | 98 -- desktop/.local/share/poezio/plugins/figlet.py | 48 - .../.local/share/poezio/plugins/iq_show.py | 19 - desktop/.local/share/poezio/plugins/irc.py | 323 ----- .../.local/share/poezio/plugins/lastlog.py | 61 - desktop/.local/share/poezio/plugins/link.py | 178 --- .../.local/share/poezio/plugins/marquee.py | 93 -- desktop/.local/share/poezio/plugins/mirror.py | 33 - .../.local/share/poezio/plugins/mpd_client.py | 98 -- desktop/.local/share/poezio/plugins/otr.py | 1068 ----------------- .../.local/share/poezio/plugins/pacokick.py | 42 - desktop/.local/share/poezio/plugins/ping.py | 172 --- .../.local/share/poezio/plugins/pipe_cmd.py | 68 -- .../.local/share/poezio/plugins/pointpoint.py | 49 - desktop/.local/share/poezio/plugins/qr.py | 184 --- desktop/.local/share/poezio/plugins/quote.py | 124 -- .../.local/share/poezio/plugins/rainbow.py | 47 - .../share/poezio/plugins/random_nick.py | 41 - .../share/poezio/plugins/regex_admin.py | 75 -- .../.local/share/poezio/plugins/reminder.py | 168 --- .../poezio/plugins/remove_get_trackers.py | 24 - .../.local/share/poezio/plugins/reorder.py | 203 ---- .../.local/share/poezio/plugins/replace.py | 113 -- .../share/poezio/plugins/replace_word.py | 37 - desktop/.local/share/poezio/plugins/revstr.py | 16 - desktop/.local/share/poezio/plugins/rstrip.py | 15 - .../share/poezio/plugins/screen_detach.py | 119 -- .../share/poezio/plugins/send_delayed.py | 80 -- .../share/poezio/plugins/server_part.py | 67 -- .../.local/share/poezio/plugins/shuffle.py | 17 - .../share/poezio/plugins/simple_notify.py | 165 --- desktop/.local/share/poezio/plugins/spaces.py | 16 - desktop/.local/share/poezio/plugins/spam.py | 25 - desktop/.local/share/poezio/plugins/status.py | 43 - .../.local/share/poezio/plugins/sticker.py | 97 -- desktop/.local/share/poezio/plugins/stoi.py | 60 - desktop/.local/share/poezio/plugins/tell.py | 117 -- desktop/.local/share/poezio/plugins/test.py | 26 - .../share/poezio/plugins/time_marker.py | 77 -- .../.local/share/poezio/plugins/untrackme.py | 140 --- desktop/.local/share/poezio/plugins/upload.py | 98 -- desktop/.local/share/poezio/plugins/uptime.py | 49 - .../share/poezio/plugins/user_extras.py | 634 ---------- desktop/.local/share/poezio/plugins/vcard.py | 323 ----- desktop/.local/share/poezio/plugins/white.py | 29 - .../.local/share/poezio/themes/Nightfox.py | 263 ++++ update-home.fish | 17 +- update-private-example.fish | 6 +- 74 files changed, 301 insertions(+), 6983 deletions(-) rename {templates => desktop}/.config/poezio/poezio.cfg (97%) delete mode 100644 desktop/.local/share/poezio/plugins/README delete mode 100644 desktop/.local/share/poezio/plugins/__init__.py delete mode 100644 desktop/.local/share/poezio/plugins/admin.py delete mode 100644 desktop/.local/share/poezio/plugins/alias.py delete mode 100644 desktop/.local/share/poezio/plugins/amsg.py delete mode 100644 desktop/.local/share/poezio/plugins/autocorrect.py delete mode 100644 desktop/.local/share/poezio/plugins/b64.py delete mode 100644 desktop/.local/share/poezio/plugins/bob.py delete mode 100644 desktop/.local/share/poezio/plugins/capslock.py delete mode 100644 desktop/.local/share/poezio/plugins/change_title.py delete mode 100644 desktop/.local/share/poezio/plugins/close_all.py delete mode 100644 desktop/.local/share/poezio/plugins/code.py delete mode 100644 desktop/.local/share/poezio/plugins/contact.py delete mode 100644 desktop/.local/share/poezio/plugins/csi.py delete mode 100644 desktop/.local/share/poezio/plugins/cyber.py delete mode 100644 desktop/.local/share/poezio/plugins/day_change.py delete mode 100644 desktop/.local/share/poezio/plugins/dice.py delete mode 100644 desktop/.local/share/poezio/plugins/disco.py delete mode 100644 desktop/.local/share/poezio/plugins/display_corrections.py delete mode 100644 desktop/.local/share/poezio/plugins/double.py delete mode 100644 desktop/.local/share/poezio/plugins/embed.py delete mode 100644 desktop/.local/share/poezio/plugins/emoji_ascii.py delete mode 100644 desktop/.local/share/poezio/plugins/exec.py delete mode 100644 desktop/.local/share/poezio/plugins/figlet.py delete mode 100644 desktop/.local/share/poezio/plugins/iq_show.py delete mode 100644 desktop/.local/share/poezio/plugins/irc.py delete mode 100644 desktop/.local/share/poezio/plugins/lastlog.py delete mode 100644 desktop/.local/share/poezio/plugins/link.py delete mode 100644 desktop/.local/share/poezio/plugins/marquee.py delete mode 100644 desktop/.local/share/poezio/plugins/mirror.py delete mode 100644 desktop/.local/share/poezio/plugins/mpd_client.py delete mode 100644 desktop/.local/share/poezio/plugins/otr.py delete mode 100644 desktop/.local/share/poezio/plugins/pacokick.py delete mode 100644 desktop/.local/share/poezio/plugins/ping.py delete mode 100644 desktop/.local/share/poezio/plugins/pipe_cmd.py delete mode 100644 desktop/.local/share/poezio/plugins/pointpoint.py delete mode 100755 desktop/.local/share/poezio/plugins/qr.py delete mode 100644 desktop/.local/share/poezio/plugins/quote.py delete mode 100644 desktop/.local/share/poezio/plugins/rainbow.py delete mode 100644 desktop/.local/share/poezio/plugins/random_nick.py delete mode 100644 desktop/.local/share/poezio/plugins/regex_admin.py delete mode 100644 desktop/.local/share/poezio/plugins/reminder.py delete mode 100644 desktop/.local/share/poezio/plugins/remove_get_trackers.py delete mode 100644 desktop/.local/share/poezio/plugins/reorder.py delete mode 100644 desktop/.local/share/poezio/plugins/replace.py delete mode 100644 desktop/.local/share/poezio/plugins/replace_word.py delete mode 100644 desktop/.local/share/poezio/plugins/revstr.py delete mode 100644 desktop/.local/share/poezio/plugins/rstrip.py delete mode 100644 desktop/.local/share/poezio/plugins/screen_detach.py delete mode 100644 desktop/.local/share/poezio/plugins/send_delayed.py delete mode 100644 desktop/.local/share/poezio/plugins/server_part.py delete mode 100644 desktop/.local/share/poezio/plugins/shuffle.py delete mode 100644 desktop/.local/share/poezio/plugins/simple_notify.py delete mode 100644 desktop/.local/share/poezio/plugins/spaces.py delete mode 100644 desktop/.local/share/poezio/plugins/spam.py delete mode 100644 desktop/.local/share/poezio/plugins/status.py delete mode 100644 desktop/.local/share/poezio/plugins/sticker.py delete mode 100644 desktop/.local/share/poezio/plugins/stoi.py delete mode 100644 desktop/.local/share/poezio/plugins/tell.py delete mode 100644 desktop/.local/share/poezio/plugins/test.py delete mode 100644 desktop/.local/share/poezio/plugins/time_marker.py delete mode 100644 desktop/.local/share/poezio/plugins/untrackme.py delete mode 100644 desktop/.local/share/poezio/plugins/upload.py delete mode 100644 desktop/.local/share/poezio/plugins/uptime.py delete mode 100644 desktop/.local/share/poezio/plugins/user_extras.py delete mode 100644 desktop/.local/share/poezio/plugins/vcard.py delete mode 100644 desktop/.local/share/poezio/plugins/white.py create mode 100644 desktop/.local/share/poezio/themes/Nightfox.py diff --git a/README.rst b/README.rst index 05e930a..2c56087 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,7 @@ You need to install: - offlineimap3 - pandoc - poezio +- pyinotify - tmux - toot - unoconv diff --git a/common/.config/nvim/init.lua b/common/.config/nvim/init.lua index 1811d8a..8a725d8 100644 --- a/common/.config/nvim/init.lua +++ b/common/.config/nvim/init.lua @@ -63,3 +63,5 @@ vim.keymap.set('v', 'gL', ":'<,'>!lou_translate --backward en-us-brf.dis,fr-bfu- vim.cmd 'colorscheme nightfox' vim.transparent_window = true + +vim.cmd 'highlight Normal ctermbg=none guibg=none' diff --git a/common/.config/tmux/tmux.conf b/common/.config/tmux/tmux.conf index 7b881cc..5532a82 100644 --- a/common/.config/tmux/tmux.conf +++ b/common/.config/tmux/tmux.conf @@ -7,7 +7,7 @@ bind-key -T copy-mode-vi C-v send -X rectangle-toggle bind-key -T copy-mode-vi y send -X copy-pipe-and-cancel set -g lock-command vlock -set -g lock-after-time 0 +set -g lock-after-time 900 bind l lock-client bind L lock-session diff --git a/templates/.config/poezio/poezio.cfg b/desktop/.config/poezio/poezio.cfg similarity index 97% rename from templates/.config/poezio/poezio.cfg rename to desktop/.config/poezio/poezio.cfg index ac3942d..a5cc837 100644 --- a/templates/.config/poezio/poezio.cfg +++ b/desktop/.config/poezio/poezio.cfg @@ -12,7 +12,7 @@ # like room administration, nickname registration. # The 'server' option will be ignored if you specify a JID (Jabber identifier) # It should be in the form nickname@server.tld or nickname@server.tld/resource -jid = template@example.com +jid = REPLACE_JID # A password is needed only if you specified a jid. It will be ignored otherwise # If you leave this empty, the password will be asked at each startup @@ -20,11 +20,11 @@ jid = template@example.com # A command that will be executed if "password" is not set, e.g. a session password # manager like secret-tool on gnome, or anything you want -eval_password = keyring get "xmpp:template@example.com" "template@example.com" +eval_password = keyring get "KEYRING_ID" "KEYRING_USER" # This identifies this client over time with the server, to let it optimise # offline storage and various other features. -device_id = REPLACEDEVICEHOSTNAME +device_id = REPLACE_DEVICE_HOSTNAME # Path to a PEM certificate file to use for certificate authentication # through SASL External. If set, keyfile MUST be provided as well in @@ -50,7 +50,7 @@ rooms = highlight_on = # Colon-separated list of plugins to load on startup -plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder:vcard:upload:screen_detach:qr:close_all:contact:disco:display_corrections:irc:link:mpd_client +plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder:vcard:upload:screen_detach:qr:close_all:contact:disco:display_corrections:irc:link:mpd_client:change_title # The server used for anonymous connection. # Make sure the server you're using accepts anonymous authentication @@ -58,7 +58,7 @@ plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder: # TLS Certificate fingerprint # Do not touch this if you don’t know what you are doing -certificate = F5:9A:6D:C9:C5:1A:B5:05:22:F4:EB:4E:23:32:7C:97:6A:24:FF:F8:BA:8D:7F:C1:D1:E8:6F:37:EE:A3:FE:43 +certificate = REPLACE_FINGERPRINT # List of ciphers allowed when connecting to the server, # this list prioritizes forward secrecy and forbids anything @@ -194,9 +194,9 @@ enable_xhtml_im = true # - status changes won't be displayed unless # the user talked in the last 2 minutes -#hide_exit_join = -1 +hide_exit_join = 600 -#hide_status_change = 120 +hide_status_change = 120 # Some informational messages (error, a contact getting connected, etc) @@ -298,6 +298,11 @@ hide_user_list = true # - false or anything else: no highlighting #show_composing_tabs = direct +# Use deterministic coloration for tab names or tab numbers in the activity bar. +# Consistent Color Generation (XEP-0392). + +autocolor_tab_names = false + # Ignore private messages received in chatrooms #ignore_private = false @@ -381,7 +386,7 @@ roster_show_offline = true # in the theme_dir directory. # If the file is not found (or no filename is specified) the default # theme will be used instead -#theme = default +theme = Nightfox # Whether to create gaps when moving or closing a tab # (a gap means that the number of your tabs does not depend of the previous tabs @@ -533,9 +538,8 @@ M-t = _exc_toggle show_timestamps # You should not edit this section, it is just used by poezio # to save various data across restarts folded_roster_groups = -info_win_height = 2 +info_win_height = 0 [muc_colors] # Set color for a nick, under the form # nick = color -[reorder] diff --git a/desktop/.local/share/poezio/plugins/README b/desktop/.local/share/poezio/plugins/README deleted file mode 100644 index 9411d58..0000000 --- a/desktop/.local/share/poezio/plugins/README +++ /dev/null @@ -1 +0,0 @@ -Plugins from poezio directory diff --git a/desktop/.local/share/poezio/plugins/__init__.py b/desktop/.local/share/poezio/plugins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/desktop/.local/share/poezio/plugins/admin.py b/desktop/.local/share/poezio/plugins/admin.py deleted file mode 100644 index c290184..0000000 --- a/desktop/.local/share/poezio/plugins/admin.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -This plugin adds several convenient aliases, to shorten -roles/affiliation management. - -Aliases defined ---------------- - -All those commands take a nick or a JID as a parameter. - -For roles -~~~~~~~~~ - -.. glossary:: - :sorted: - - /visitor - /mute - Set the role to ``visitor`` - - /participant - Set the role to ``participant`` - - /moderator - /op - Set the role to ``moderator`` - - -For affiliations -~~~~~~~~~~~~~~~~ - -.. glossary:: - :sorted: - - /admin - Set the affiliation to ``admin`` - - /member - /voice - Set the affiliation to ``member`` - - /noaffiliation - Set the affiliation to ``none`` - - /owner - Set the affiliation to ``owner`` - - - - - -""" - -from poezio.plugin import BasePlugin -from poezio.tabs import MucTab -from poezio.core.structs import Completion - - -class Plugin(BasePlugin): - """ - Adds several convenient aliases to /affiliation and /role: - /visitor - /participant - /moderator == /op - /member == /voice - /owner - /admin - /noaffiliation - """ - - def init(self): - for role in ('visitor', 'participant', 'moderator'): - self.api.add_tab_command( - MucTab, - role, - self.role(role), - help='Set the role of a nick to %s' % role, - usage='', - short='Set the role to %s' % role, - completion=self.complete_nick) - - for aff in ('member', 'owner', 'admin'): - self.api.add_tab_command( - MucTab, - aff, - self.affiliation(aff), - usage='', - help='Set the affiliation of a nick to %s' % aff, - short='Set the affiliation to %s' % aff, - completion=self.complete_nick) - - self.api.add_tab_command( - MucTab, - 'noaffiliation', - self.affiliation('none'), - usage='', - help='Set the affiliation of a nick to none.', - short='Set the affiliation to none.', - completion=self.complete_nick) - self.api.add_tab_command( - MucTab, - 'voice', - self.affiliation('member'), - usage='', - help='Set the affiliation of a nick to member.', - short='Set the affiliation to member.', - completion=self.complete_nick) - self.api.add_tab_command( - MucTab, - 'op', - self.role('moderator'), - usage='', - help='Set the role of a nick to moderator.', - short='Set the role to moderator.', - completion=self.complete_nick) - self.api.add_tab_command( - MucTab, - 'mute', - self.role('visitor'), - usage='', - help='Set the role of a nick to visitor.', - short='Set the role to visitor.', - completion=self.complete_nick) - - def role(self, role): - async def inner(args): - await self.api.current_tab().command_role(args + ' ' + role) - return inner - - def affiliation(self, affiliation): - async def inner(args): - await self.api.current_tab().command_affiliation(args + ' ' + affiliation) - return inner - - def complete_nick(self, the_input): - tab = self.api.current_tab() - compare_users = lambda x: x.last_talked - word_list = [user.nick for user in sorted(tab.users, key=compare_users, reverse=True)\ - if user.nick != tab.own_nick] - return Completion(the_input.auto_completion, word_list, '') diff --git a/desktop/.local/share/poezio/plugins/alias.py b/desktop/.local/share/poezio/plugins/alias.py deleted file mode 100644 index 459ce02..0000000 --- a/desktop/.local/share/poezio/plugins/alias.py +++ /dev/null @@ -1,207 +0,0 @@ -""" -Usage ------ - -This plugin defines two new global commands: :term:`/alias` and :term:`/unalias`. - -.. glossary:: - - /alias - **Usage:** ``/alias [args]`` - - This command will create a new command, named ```` (and callable - with ``/name``), that runs ``/command``, with ``[args]`` as fixed - args for the command. - When you run the alias, you can also pass parameters to it, that will be - given to the original command. - - Example: :: - - /alias toto say koin - - Will bind ``/say koin`` to ``/toto``, so this alias will work in any - Chat tab. If someone calls it with :: - - /toto koin - - Poezio will then execute ``/say koin koin``. - - Also, you can rebind arguments arbitrarily, with the ``{}`` placeholder. - For example, :: - - /alias toto say {} le {} - /toto loulou coucou - - Will execute ``/say loulou le coucou``, because the ``{}`` are - replaced with the command args, in the order they are given. - - Extra args are still added at the end of the command if provided - (args used for the formatting are only used for the formatting). - - /unalias - **Usage:** ``/unalias `` - - This command removes a defined alias. - - -Config ------- - -The aliases are stored inside the configuration file for the plugin. -You can either use the above commands or write it manually, and it -will be read when the plugin is loaded. - - -Example of the syntax: - -.. code-block:: ini - - [alias] - toto = say {} le {} - j = join {}@conference.jabber.org/nick - jp = say je proteste - - -""" - -from poezio.plugin import BasePlugin -from poezio.common import shell_split -from poezio.core.structs import Completion - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'alias', - self.command_alias, - usage=' [args]', - short='Create an alias command', - help='Create an alias for with [args].') - self.api.add_command( - 'unalias', - self.command_unalias, - usage='', - help='Remove a previously created alias', - short='Remove an alias', - completion=self.completion_unalias) - self.commands = {} - self.load_conf() - - def load_conf(self): - """ - load stored aliases on startup - """ - for alias in self.config.options(): - full = self.config.get(alias, '') - if full: - self.command_alias(alias + ' ' + full, silent=True) - - def command_alias(self, line, silent=False): - """ - /alias [args] - """ - arg = split_args(line) - if not arg: - if not silent: - self.api.information('Alias: Not enough parameters', 'Error') - return - alias, command, args = arg - - if alias in self.commands: - update = True - elif alias in self.core.commands: - if not silent: - self.api.information('Alias: command already exists', 'Error') - return - else: - update = False - - self.config.set(alias, command + ' ' + args) - self.commands[alias] = command_wrapper( - generic_command, lambda: self.get_command(command), args) - self.api.del_command(alias) - self.api.add_command( - alias, self.commands[alias], - 'This command is an alias for /%s %s' % (alias, command)) - - if not silent: - if update: - self.api.information('Alias /%s updated' % alias, 'Info') - else: - self.api.information('Alias /%s successfully created' % alias, - 'Info') - - def command_unalias(self, alias): - """ - /unalias - """ - if alias in self.commands: - del self.commands[alias] - self.api.del_command(alias) - self.config.remove(alias) - self.api.information('Alias /%s successfully deleted' % alias, - 'Info') - - def completion_unalias(self, the_input): - "Completion for /unalias" - aliases = [alias for alias in self.commands] - aliases.sort() - return Completion( - the_input.auto_completion, aliases, '', quotify=False) - - def get_command(self, name): - """Returns the function associated with a command""" - - def dummy(args): - """Dummy function called if the command doesn’t exist""" - pass - - if name in self.commands: - return dummy - elif name in self.core.commands: - return self.core.commands[name].func - elif name in self.api.current_tab().commands: - return self.api.current_tab().commands[name].func - return dummy - - -def split_args(line): - """ - Extract the relevant vars from the command line - """ - arg = line.split() - if len(arg) < 2: - return None - alias_pos = line.find(' ') - alias = line[:alias_pos] - end = line[alias_pos + 1:] - args_pos = end.find(' ') - if args_pos == -1: - command = end - args = '' - else: - command = end[:args_pos] - args = end[args_pos + 1:] - return (alias, command, args) - - -def generic_command(command, extra_args, args): - """ - Function that will execute the command and set the relevant - parameters (format string, etc). - """ - args = shell_split(args) - new_extra_args = extra_args.format(*args) - count = extra_args.count('{}') - args = args[count:] - new_extra_args += ' '.join(args) - return command()(new_extra_args) - - -def command_wrapper(func, command, extra_args): - "set the predefined arguments" - - def wrapper(*args, **kwargs): - return func(command, extra_args, *args, **kwargs) - - return wrapper diff --git a/desktop/.local/share/poezio/plugins/amsg.py b/desktop/.local/share/poezio/plugins/amsg.py deleted file mode 100644 index 3b81085..0000000 --- a/desktop/.local/share/poezio/plugins/amsg.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -This plugin broadcasts a message to all your joined rooms. - -.. note:: With great power comes great responsibility. - Use with moderation. - -Command -------- - -.. glossary:: - - /amsg - **Usage:** ``/amsg `` - - Broadcast a message. - - -""" -from poezio.plugin import BasePlugin -from poezio.tabs import MucTab - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'amsg', - self.command_amsg, - usage='', - short='Broadcast a message', - help='Broadcast the message to all the joined rooms.') - - async def command_amsg(self, args): - for room in self.core.tabs: - if isinstance(room, MucTab) and room.joined: - await room.command_say(args) diff --git a/desktop/.local/share/poezio/plugins/autocorrect.py b/desktop/.local/share/poezio/plugins/autocorrect.py deleted file mode 100644 index 6e83436..0000000 --- a/desktop/.local/share/poezio/plugins/autocorrect.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -This plugin lets you perform simple replacements on the last message. - -Usage ------ - -.. note:: the ``/``, ``#``, ``!``, ``:`` and ``;`` chars can be used as separators, - even if the examples only use ``/`` - - -Regex replacement -~~~~~~~~~~~~~~~~~ - -Once the plugin is loaded, any message matching the following regex:: - - ^s/(.+?)/(.*?)(/|/g)?$ - -will be interpreted as a regex replacement, and the substitution will be -applied to the last sent message. - -For example, if you sent the message:: - - This tab lists all public rooms on a MUC service. It is currently very limited but will be improved in the future. There currently is no way to search a room. - -And you now want to replace “MUC” with “multi-user chat”, you input:: - - s/MUC/multi-user chat - -And poezio will correct the message for you. - - -Raw string replacement -~~~~~~~~~~~~~~~~~~~~~~ - -Once the plugin is loaded, any message matching the following regex:: - - ^r/(.+?)/(.*?)(/|/g)?$ - -will be interpreted as a replacement, and the substitution will be applied -to the last send message. - -This variant is useful if you don’t want to care about regular expressions -(and you do not want to have to escape stuff like space or backslashes). - - -""" - -from poezio.plugin import BasePlugin -import re - -allowed_separators = '/#!:;' -sed_re = re.compile( - '^([sr])(?P[%s])(.+?)(?P=sep)(.*?)((?P=sep)|(?P=sep)g)?$' % - allowed_separators) - - -class Plugin(BasePlugin): - def init(self): - self.api.add_event_handler('muc_say', self.sed_fix) - self.api.add_event_handler('conversation_say', self.sed_fix) - self.api.add_event_handler('private_say', self.sed_fix) - - def sed_fix(self, msg, tab): - if not tab.last_sent_message: - return - if 'correct' not in tab.commands: - return - body = tab.last_sent_message['body'] - match = sed_re.match(msg['body']) - if not match: - return - typ, sep, remove, put, matchall = match.groups() - - replace_all = False - if matchall == sep + 'g': - replace_all = True - - if typ == 's': - try: - if replace_all: - new_body = re.sub(remove, put, body) - else: - new_body = re.sub(remove, put, body, count=1) - except Exception as e: - self.api.information( - 'Invalid regex for the autocorrect ' - 'plugin: %s' % e, 'Error') - return - elif typ == 'r': - if replace_all: - new_body = body.replace(remove, put) - else: - new_body = body.replace(remove, put, 1) - - if body != new_body: - msg['body'] = new_body - msg['replace']['id'] = tab.last_sent_message['id'] diff --git a/desktop/.local/share/poezio/plugins/b64.py b/desktop/.local/share/poezio/plugins/b64.py deleted file mode 100644 index 82300a0..0000000 --- a/desktop/.local/share/poezio/plugins/b64.py +++ /dev/null @@ -1,70 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 -# -# Copyright © 2019 Maxime “pep” Buquet -# -# Distributed under terms of the GPL-3.0+ license. - -""" -Usage ------ - -Base64 encryption plugin. - -This plugin also respects security guidelines listed in XEP-0419. - -.. glossary:: - /b64 - **Usage:** ``/b64`` - - This command enables encryption of outgoing messages for the current - tab. -""" - -from base64 import b64decode, b64encode -from typing import List, Optional -from slixmpp import Message, JID - -from poezio.plugin_e2ee import E2EEPlugin -from poezio.tabs import ( - ChatTab, - MucTab, - PrivateTab, - DynamicConversationTab, - StaticConversationTab, -) - - -class Plugin(E2EEPlugin): - """Base64 Plugin""" - - encryption_name = 'base64' - encryption_short_name = 'b64' - eme_ns = 'urn:xmpps:base64:0' - - # This encryption mechanism is using as a container - replace_body_with_eme = False - - # In what tab is it ok to use this plugin. Here we want all of them - supported_tab_types = ( - MucTab, - PrivateTab, - DynamicConversationTab, - StaticConversationTab, - ) - - async def decrypt(self, message: Message, jid: Optional[JID], _tab: Optional[ChatTab]) -> None: - """ - Decrypt base64 - """ - body = message['body'] - message['body'] = b64decode(body.encode()).decode() - - async def encrypt(self, message: Message, _jid: Optional[List[JID]], _tab: ChatTab) -> None: - """ - Encrypt to base64 - """ - # TODO: Stop using for this. Put the encoded payload in another element. - body = message['body'] - message['body'] = b64encode(body.encode()).decode() diff --git a/desktop/.local/share/poezio/plugins/bob.py b/desktop/.local/share/poezio/plugins/bob.py deleted file mode 100644 index 98c6290..0000000 --- a/desktop/.local/share/poezio/plugins/bob.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -This plugin sends a small image to the recipient of your choice, using XHTML-IM and Bits of Binary. - -Usage ------ - -/bob some/image.png - -Configuration options ---------------------- - -.. glossary:: - - max_size - **Default:** ``2048`` - - The maximum acceptable size of a file, over which you will get an error instead. - - max_age - **Default:** ``86400`` - - The time during which the file should stay in cache on the receiving side. -""" - -from poezio.core.structs import Completion -from poezio.plugin import BasePlugin -from poezio import tabs - -from pathlib import Path -from glob import glob -from os.path import expanduser -from mimetypes import guess_type - - -class Plugin(BasePlugin): - - default_config = {'bob': {'max_size': 2048, 'max_age': 86400}} - - def init(self): - for tab in tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab, tabs.MucTab: - self.api.add_tab_command( - tab, - 'bob', - self.command_bob, - usage='', - help='Send image to the current discussion', - short='Send a short image', - completion=self.completion_bob) - - async def command_bob(self, filename): - path = Path(expanduser(filename)) - try: - size = path.stat().st_size - except OSError as exc: - self.api.information('Error sending “%s”: %s' % (path.name, exc), - 'Error') - return - mime_type = guess_type(path.as_posix())[0] - if mime_type is None or not mime_type.startswith('image/'): - self.api.information( - 'Error sending “%s”, not an image file.' % path.name, 'Error') - return - if size > self.config.get('max_size'): - self.api.information( - 'Error sending “%s”, file too big.' % path.name, 'Error') - return - with open(path.as_posix(), 'rb') as file: - data = file.read() - max_age = self.config.get('max_age') - cid = await self.core.xmpp.plugin['xep_0231'].set_bob( - data, mime_type, max_age=max_age) - self.api.run_command( - '/xhtml %s' % (cid, path.name)) - - @staticmethod - def completion_bob(the_input): - txt = expanduser(the_input.get_text()[5:]) - images = [] - for filename in glob(txt + '*'): - mime_type = guess_type(filename)[0] - if mime_type is not None and mime_type.startswith('image/'): - images.append(filename) - return Completion(the_input.auto_completion, images, quotify=False) diff --git a/desktop/.local/share/poezio/plugins/capslock.py b/desktop/.local/share/poezio/plugins/capslock.py deleted file mode 100644 index 7f96000..0000000 --- a/desktop/.local/share/poezio/plugins/capslock.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Once loaded, everything you will send will be IN CAPITAL LETTERS. -""" -from poezio.plugin import BasePlugin -from poezio import xhtml - - -class Plugin(BasePlugin): - def init(self): - self.api.add_event_handler('muc_say', self.caps) - self.api.add_event_handler('conversation_say', self.caps) - self.api.add_event_handler('private_say', self.caps) - - def caps(self, msg, tab): - msg['body'] = xhtml.clean_text(msg['body']).upper() diff --git a/desktop/.local/share/poezio/plugins/change_title.py b/desktop/.local/share/poezio/plugins/change_title.py deleted file mode 100644 index e5ed96a..0000000 --- a/desktop/.local/share/poezio/plugins/change_title.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -This plugin will set the title of your terminal to the name of the current tab. - -""" -from poezio.plugin import BasePlugin -import sys - - -class Plugin(BasePlugin): - def init(self): - self.on_tab_change(None, new_tab=self.core.tabs.current_tab) - self.api.add_event_handler('tab_change', self.on_tab_change) - - def cleanup(self): - "Re-set the terminal title to 'poezio'" - sys.stdout.write("\x1b]0;poezio\x07") - sys.stdout.flush() - - def on_tab_change(self, old_tab, new_tab): - sys.stdout.write("\x1b]0;{}\x07".format(new_tab.name)) - sys.stdout.flush() diff --git a/desktop/.local/share/poezio/plugins/close_all.py b/desktop/.local/share/poezio/plugins/close_all.py deleted file mode 100644 index c1d1ec8..0000000 --- a/desktop/.local/share/poezio/plugins/close_all.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -``close_all`` plugin: close all tabs except chatrooms and the contact list. - -Commands --------- - -.. glossary:: - - /closeall - **Usage:** ``/closeall`` - - Close all tabs except the roster and chatroom tabs. -""" -from poezio.plugin import BasePlugin -from poezio import tabs -from poezio.decorators import command_args_parser - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'closeall', - self.command_closeall, - help='Close all non-chatroom tabs.') - - @command_args_parser.ignored - def command_closeall(self): - """ - /closeall - """ - current = self.api.current_tab() - if not isinstance(current, (tabs.RosterInfoTab, tabs.MucTab)): - self.core.go_to_roster() - current = self.api.current_tab() - - def filter_func(x): - return not isinstance(x, (tabs.RosterInfoTab, tabs.MucTab)) - - matching_tabs = list(filter(filter_func, self.core.tabs.get_tabs())) - length = len(matching_tabs) - for tab in matching_tabs: - self.core.close_tab(tab) - self.api.information('%s tabs closed.' % length, 'Info') - self.core.refresh_window() diff --git a/desktop/.local/share/poezio/plugins/code.py b/desktop/.local/share/poezio/plugins/code.py deleted file mode 100644 index 8d9c57a..0000000 --- a/desktop/.local/share/poezio/plugins/code.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -This plugin adds a :term:`/code` command, to send syntax highlighted snippets -of code using pygments and XHTML-IM (XEP-0071). - -Install -------- - -Either use your distribution tools to install python3-pygments or equivalent, -or run: - -.. code-block:: shell - - pip install --user pygments - -Usage ------ - -.. glossary:: - - /code - - Run this command to send the of code, syntax highlighted - using pygments’s lexer. -""" - -from poezio.plugin import BasePlugin - -from pygments import highlight -from pygments.lexers import get_lexer_by_name -from pygments.formatters import HtmlFormatter #pylint: disable=no-name-in-module -FORMATTER = HtmlFormatter(nowrap=True, noclasses=True) - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'code', - self.command_code, - usage=' ', - short='Sends syntax-highlighted code', - help='Sends syntax-highlighted code in the current tab') - - def command_code(self, args): - split = args.split(None, 1) - if len(split) != 2: - self.api.information('Usage: /code ', 'Error') - return None - language, code = split - lexer = get_lexer_by_name(language) - tab = self.api.current_tab() - code = highlight(code, lexer, FORMATTER) - tab.command_xhtml('
%s
' % (language, code.rstrip('\n'))) diff --git a/desktop/.local/share/poezio/plugins/contact.py b/desktop/.local/share/poezio/plugins/contact.py deleted file mode 100644 index 13dcc42..0000000 --- a/desktop/.local/share/poezio/plugins/contact.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Do a disco#info query on a JID, display the XEP-0157 Contact Addresses - -Usage ------ - -.. glossary:: - - /contact - **Usage:** ``/contact `` - - This command queries a JID for its Contact Addresses. -""" - -from poezio.plugin import BasePlugin -from slixmpp.exceptions import IqError, IqTimeout -from slixmpp.jid import InvalidJID - -CONTACT_TYPES = ['abuse', 'admin', 'feedback', 'sales', 'security', 'support'] - -class Plugin(BasePlugin): - def init(self): - self.api.add_command('contact', self.command_disco, - usage='', - short='Get the Contact Addresses of a JID', - help='Get the Contact Addresses of a JID') - - def on_disco(self, iq): - info = iq['disco_info'] - contacts = [] - # iterate all data forms, in case there are multiple - for form in iq['disco_info']: - values = form.get_values() - if values['FORM_TYPE'][0] == 'http://jabber.org/network/serverinfo': - for var in values: - if not var.endswith('-addresses'): - continue - title = var[:-10] # strip '-addresses' - sep = '\n ' + len(title) * ' ' - field_value = values[var] - if field_value: - value = sep.join(field_value) if isinstance(field_value, list) else field_value - contacts.append(f'{title}: {value}') - if contacts: - self.api.information('\n'.join(contacts), 'Contact Info') - else: - self.api.information(f'No Contact Addresses for {iq["from"]}', 'Error') - - async def command_disco(self, jid): - try: - iq = await self.core.xmpp.plugin['xep_0030'].get_info(jid=jid, cached=False) - self.on_disco(iq) - except InvalidJID as exn: - self.api.information(f'Invalid JID “{jid}”: {exn}', 'Error') - except (IqError, IqTimeout,) as exn: - ifrom = exn.iq['from'] - condition = exn.iq['error']['condition'] - text = exn.iq['error']['text'] - message = f'Error getting Contact Addresses from {ifrom}: {condition}: {text}' - self.api.information(message, 'Error') diff --git a/desktop/.local/share/poezio/plugins/csi.py b/desktop/.local/share/poezio/plugins/csi.py deleted file mode 100644 index 1bd6a92..0000000 --- a/desktop/.local/share/poezio/plugins/csi.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -This plugin lets you set the CSI_ state manually, when the autoaway plugin -is not sufficient for your usage. - -Commands --------- - -.. glossary:: - - /csi_active - **Usage:** ``/csi_active`` - - Set CSI state to ``active``. - - /csi_inactive - **Usage:** ``/csi_inactive`` - - Set CSI state to ``inactive``. - -.. _CSI: https://xmpp.org/extensions/xep-0352.html -""" - -from poezio.plugin import BasePlugin - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'csi_active', - self.command_active, - help='Set the client state indication to “active”', - short='Manual set active') - self.api.add_command( - 'csi_inactive', - self.command_inactive, - help='Set the client state indication to “inactive”', - short='Manual set inactive') - - def command_active(self, args): - if not self.core.xmpp.plugin['xep_0352'].enabled: - self.api.information('CSI is not enabled in this server', - 'Warning') - else: - self.core.xmpp.plugin['xep_0352'].send_active() - - def command_inactive(self, args): - if not self.core.xmpp.plugin['xep_0352'].enabled: - self.api.information('CSI is not enabled in this server', - 'Warning') - else: - self.core.xmpp.plugin['xep_0352'].send_inactive() diff --git a/desktop/.local/share/poezio/plugins/cyber.py b/desktop/.local/share/poezio/plugins/cyber.py deleted file mode 100644 index c7d4aba..0000000 --- a/desktop/.local/share/poezio/plugins/cyber.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -This plugin adds a "cyber" prefix to a random word in your chatroom messages. - -Usage ------ - -Say something in a MUC tab. - -Configuration options ---------------------- - -.. glossary:: - - frequency - **Default:** ``10`` - - The percentage of the time the plugin will activate (randomly). 100 for every message, <= 0 for never. -""" - -from poezio.plugin import BasePlugin -from random import choice, randint -import re - -DEFAULT_CONFIG = {'cyber': {'frequency': 10}} - - -class Plugin(BasePlugin): - - default_config = DEFAULT_CONFIG - - def init(self): - self.api.add_event_handler('muc_say', self.cyberize) - - def cyberize(self, msg, tab): - if randint(1, 100) > self.config.get('frequency'): - return - words = [ - word for word in re.split('\W+', msg['body']) if len(word) > 3 - ] - if words: - word = choice(words) - msg['body'] = msg['body'].replace(word, 'cyber' + word) diff --git a/desktop/.local/share/poezio/plugins/day_change.py b/desktop/.local/share/poezio/plugins/day_change.py deleted file mode 100644 index 5d3ab37..0000000 --- a/desktop/.local/share/poezio/plugins/day_change.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -This plugin adds a message at 00:00 in each of your chat tabs saying that the -date has changed. - -""" - -import datetime -from gettext import gettext as _ - -from poezio import timed_events, tabs -from poezio.plugin import BasePlugin -from poezio.ui.types import InfoMessage - - -class Plugin(BasePlugin): - def init(self): - self.schedule_event() - - def cleanup(self): - self.api.remove_timed_event(self.next_event) - - def schedule_event(self): - day_change = datetime.datetime.combine(datetime.date.today(), - datetime.time()) - day_change += datetime.timedelta(1) - self.next_event = timed_events.TimedEvent(day_change, self.day_change) - self.api.add_timed_event(self.next_event) - - def day_change(self): - msg = _("Day changed to %s") % (datetime.date.today().isoformat()) - - for tab in self.core.tabs: - if isinstance(tab, tabs.ChatTab): - tab.add_message(InfoMessage(msg)) - - self.core.refresh_window() - self.schedule_event() diff --git a/desktop/.local/share/poezio/plugins/dice.py b/desktop/.local/share/poezio/plugins/dice.py deleted file mode 100644 index 3b540cb..0000000 --- a/desktop/.local/share/poezio/plugins/dice.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Dice plugin: roll some dice - -Usage of this plugin is not recommended. - -Commands --------- - -.. glossary:: - - /roll [number of dice] [duration of the roll] - Roll one or several unicode dice - -Configuration -------------- - -.. glossary:: - :sorted: - - refresh - **Default:** ``0.5`` - - Interval in seconds between each correction (the closest to 0 is the fastest) - - default_duration - **Default:** ``5`` - - Total duration of the animation. -""" - -import random -from typing import Optional - -from poezio import tabs -from poezio.decorators import command_args_parser -from poezio.plugin import BasePlugin - -DICE = '\u2680\u2681\u2682\u2683\u2684\u2685' - - -class DiceRoll: - __slots__ = [ - 'duration', 'total_duration', 'dice_number', 'msgtype', 'jid', - 'msgid', 'increments' - ] - - def __init__(self, total_duration, dice_number, msgtype, jid, msgid, increments): - self.duration = 0 - self.total_duration = total_duration - self.dice_number = dice_number - self.msgtype = msgtype - self.jid = jid - self.msgid = msgid - self.increments = increments - - def reroll(self): - self.duration += self.increments - - def is_finished(self): - return self.duration >= self.total_duration - - -def roll_dice(num_dice: int) -> str: - return ''.join(random.choice(DICE) for _ in range(num_dice)) - -class Plugin(BasePlugin): - default_config = {"dice": {"refresh": 0.75, "default_duration": 7.5}} - - def init(self): - for tab_t in [tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab]: - self.api.add_tab_command( - tab_t, - 'roll', - self.command_dice, - help='Roll a die', - usage='[number] [duration]') - - @command_args_parser.quoted(0, 2, ['', ''], True) - def command_dice(self, args): - tab = self.api.current_tab() - duration = self.config.get('default_duration') - num_dice = 1 - try: - if args[0]: - num_dice = int(args[0]) - if args[1]: - duration = float(args[1]) - except ValueError: - self.core.command.help("roll") - return - else: - if num_dice <= 0 or duration < 0: - self.core.command.help("roll") - return - - msgtype = 'groupchat' if isinstance(tab, tabs.MucTab) else 'chat' - - message = self.core.xmpp.make_message(tab.jid) - message['type'] = msgtype - message['body'] = roll_dice(num_dice) - message.send() - - increment = self.config.get('refresh') - msgid = message['id'] - - roll = DiceRoll(duration, num_dice, msgtype, tab.jid, msgid, increment) - event = self.api.create_delayed_event(increment, self.delayed_event, - roll) - self.api.add_timed_event(event) - - def delayed_event(self, roll): - if roll.is_finished(): - return - roll.reroll() - message = self.core.xmpp.make_message(roll.jid) - message["type"] = roll.msgtype - message["body"] = roll_dice(roll.dice_number) - message["replace"]["id"] = roll.msgid - message.send() - event = self.api.create_delayed_event(roll.increments, - self.delayed_event, roll) - self.api.add_timed_event(event) diff --git a/desktop/.local/share/poezio/plugins/disco.py b/desktop/.local/share/poezio/plugins/disco.py deleted file mode 100644 index d15235f..0000000 --- a/desktop/.local/share/poezio/plugins/disco.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Do a disco#info query on a JID - -Usage ------ - -.. glossary:: - - /disco - **Usage:** ``/disco `` - - This command queries a JID for its disco#info. - - There is no cache, as this is generally used for debug more than - anything user-related. -""" - -from poezio.plugin import BasePlugin -from poezio.decorators import command_args_parser -from slixmpp.jid import InvalidJID -from slixmpp.exceptions import IqError, IqTimeout - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'disco', - self.command_disco, - usage=' [node] [info|items]', - short='Get the disco#info of a JID', - help='Get the disco#info of a JID') - - def on_info(self, iq): - if iq['type'] == 'error': - self.api.information(iq['error']['text'] or iq['error']['condition'], 'Error') - return - - info = iq['disco_info'] - identities = (str(identity) for identity in info['identities']) - self.api.information('\n'.join(identities), 'Identities') - features = sorted(str(feature) for feature in info['features']) - self.api.information('\n'.join(features), 'Features') - title = 'Server Info' - server_info = [] - for field in info['form']: - var = field['var'] - if field['type'] == 'hidden' and var == 'FORM_TYPE': - title = field['value'][0] - continue - sep = '\n ' + len(var) * ' ' - field_value = field.get_value(convert=False) - value = sep.join(field_value) if isinstance(field_value, - list) else field_value - server_info.append('%s: %s' % (var, value)) - if server_info: - self.api.information('\n'.join(server_info), title) - - def on_items(self, iq): - if iq['type'] == 'error': - self.api.information(iq['error']['text'] or iq['error']['condition'], 'Error') - return - - def describe(item): - text = item[0] - node = item[1] - name = item[2] - if node is not None: - text += ', node=' + node - if name is not None: - text += ', name=' + name - return text - - items = iq['disco_items'] - self.api.information('\n'.join(describe(item) for item in items['items']), 'Items') - - @command_args_parser.quoted(1, 3) - async def command_disco(self, args): - if args is None: - self.core.command.help('disco') - return - if len(args) == 1: - jid, = args - node = None - type_ = 'info' - elif len(args) == 2: - jid, node = args - type_ = 'info' - else: - jid, node, type_ = args - try: - if type_ == 'info': - iq = await self.core.xmpp.plugin['xep_0030'].get_info( - jid=jid, node=node, cached=False - ) - self.on_info(iq) - elif type_ == 'items': - iq = await self.core.xmpp.plugin['xep_0030'].get_items( - jid=jid, node=node - ) - self.on_items(iq) - except InvalidJID as e: - self.api.information('Invalid JID “%s”: %s' % (jid, e), 'Error') - except IqError as e: - self.api.information('Received iq error while querying “%s”: %s' % (jid, e), 'Error') - except IqTimeout: - self.api.information('Received no reply querying “%s”…' % jid, 'Error') diff --git a/desktop/.local/share/poezio/plugins/display_corrections.py b/desktop/.local/share/poezio/plugins/display_corrections.py deleted file mode 100644 index cf8107c..0000000 --- a/desktop/.local/share/poezio/plugins/display_corrections.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Lists old versions of a corrected message. - -Usage ------ - -.. glossary:: - - /display_corrections - **Usage:** ``/display_corrections [number]`` - - This command lists the old versions of a message. - - Without argument, it will list the last corrected message if there - is any. If you give an integer as an argument, ``/display_corrections`` - will go back gradually in the buffer to find the message matching - that number (starting from 1, for the last corrected message). - - If you are scrolling in the buffer, Poezio will list the corrected messages - starting from the first you can see. (although there are some problems with - multiline messages). - - -""" -from poezio.plugin import BasePlugin -from poezio.common import shell_split -from poezio import tabs -from poezio.ui.types import Message -from poezio.theming import get_theme - - -class Plugin(BasePlugin): - def init(self): - for tab_type in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab): - self.api.add_tab_command( - tab_type, - 'display_corrections', - handler=self.command_display_corrections, - usage='', - help= - 'Display all the corrections of the number-th last corrected message.', - short='Display the corrections of a message') - - def find_corrected(self, nb): - messages = self.api.get_conversation_messages() - if not messages: - return None - for message in reversed(messages): - if not isinstance(message, Message): - continue - if message.old_message: - if nb == 1: - return message - else: - nb -= 1 - return None - - def command_display_corrections(self, args): - theme = get_theme() - args = shell_split(args) - if len(args) == 1: - try: - nb = int(args[0]) - except: - return self.api.run_command('/help display_corrections') - else: - nb = 1 - message = self.find_corrected(nb) - if message: - display = [] - while message: - str_time = message.time.strftime(theme.SHORT_TIME_FORMAT) - display.append('%s %s%s%s %s' % - (str_time, '* ' - if message.me else '', message.nickname, '' - if message.me else '>', message.txt)) - message = message.old_message - self.api.information( - 'Older versions:\n' + '\n'.join(display[::-1]), 'Info') - else: - self.api.information('No corrected message found.', 'Warning') - - def cleanup(self): - del self.config diff --git a/desktop/.local/share/poezio/plugins/double.py b/desktop/.local/share/poezio/plugins/double.py deleted file mode 100644 index 88b7f4c..0000000 --- a/desktop/.local/share/poezio/plugins/double.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Double the first word of any message you send in a :ref:`muctab`, making you appear retarded. -""" -from poezio.plugin import BasePlugin - - -class Plugin(BasePlugin): - def init(self): - self.api.add_event_handler('muc_say', self.double) - - def double(self, msg, tab): - split = msg['body'].split() - if split: - msg['body'] = split[0] + ' ' + msg['body'] diff --git a/desktop/.local/share/poezio/plugins/embed.py b/desktop/.local/share/poezio/plugins/embed.py deleted file mode 100644 index 4a68f03..0000000 --- a/desktop/.local/share/poezio/plugins/embed.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Display an image URL as an embedded image in some clients like Conversations. -Uses: https://xmpp.org/extensions/xep-0066.html#x-oob - -Usage ------ - -.. glossary:: - - /embed - - Run this command to send the as an - embedded image in your contact's client. -""" - -from poezio import tabs -from poezio.plugin import BasePlugin -from poezio.theming import get_theme -from poezio.ui.types import Message - - -class Plugin(BasePlugin): - def init(self): - for tab_t in [tabs.MucTab, tabs.StaticConversationTab, tabs.DynamicConversationTab, tabs.PrivateTab]: - self.api.add_tab_command( - tab_t, - 'embed', - self.embed_image_url, - help='Embed an image url into the contact\'s client', - usage='') - - def embed_image_url(self, url, tab=None): - tab = tab or self.api.current_tab() - message = self.core.xmpp.make_message(tab.jid) - message['body'] = url - message['oob']['url'] = url - message['type'] = 'groupchat' - if not isinstance(tab, tabs.MucTab): - message['type'] = 'chat' - tab.add_message( - Message( - message['body'], - nickname=tab.core.own_nick, - nick_color=get_theme().COLOR_OWN_NICK, - identifier=message['id'], - jid=tab.core.xmpp.boundjid, - ), - ) - message.send() - self.core.refresh_window() diff --git a/desktop/.local/share/poezio/plugins/emoji_ascii.py b/desktop/.local/share/poezio/plugins/emoji_ascii.py deleted file mode 100644 index 4beec3b..0000000 --- a/desktop/.local/share/poezio/plugins/emoji_ascii.py +++ /dev/null @@ -1,60 +0,0 @@ -# poezio emoji_ascii plugin -# -# Will translate received Emoji to :emoji: for better display on text terminals, -# and outgoing :emoji: into Emoji on the wire. -# -# Requires emojis.json.gz (MIT licensed) from: -# -# git clone https://github.com/vdurmont/emoji-java -# gzip -9 < ./src/main/resources/emojis.json > poezio/plugins/emojis.json.gz - -# TODOs: -# 1. it messes up your log files (doesn't log original message, logs mutilated :emoji: instead) -# 2. Doesn't work on outgoing direct messages -# 3. Doesn't detect pastes, corrupts jabber:x:foobar -# 4. no auto-completion of emoji aliases -# 5. coloring of converted Emojis to be able to differentiate them from incoming ASCII - -import gzip -import json -import os -import re - -from poezio.plugin import BasePlugin -from typing import Dict - - -class Plugin(BasePlugin): - emoji_to_ascii: Dict[str, str] = {} - ascii_to_emoji: Dict[str, str] = {} - emoji_pattern = None - alias_pattern = None - - def init(self): - emoji_map_file_name = os.path.abspath(os.path.dirname(__file__) + '/emojis.json.gz') - emoji_map_data = gzip.open(emoji_map_file_name, 'r').read().decode('utf-8') - emoji_map = json.loads(emoji_map_data) - for e in emoji_map: - self.emoji_to_ascii[e['emoji']] = ':%s:' % e['aliases'][0] - for alias in e['aliases']: - # work around :iq: and similar country code misdetection - flag = re.match('^[a-z][a-z]$', alias) and "flag" in e["tags"] - if not flag: - self.ascii_to_emoji[':%s:' % alias] = e['emoji'] - self.emoji_pattern = re.compile('|'.join(self.emoji_to_ascii.keys()).replace('*', '\*')) - self.alias_pattern = re.compile('|'.join(self.ascii_to_emoji.keys()).replace('+', '\+')) - - self.api.add_event_handler('muc_msg', self.emoji2alias) - self.api.add_event_handler('conversation_msg', self.emoji2alias) - self.api.add_event_handler('private_msg', self.emoji2alias) - - self.api.add_event_handler('muc_say', self.alias2emoji) - self.api.add_event_handler('private_say', self.alias2emoji) - self.api.add_event_handler('conversation_say', self.alias2emoji) - - - def emoji2alias(self, msg, tab): - msg['body'] = self.emoji_pattern.sub(lambda m: self.emoji_to_ascii[m.group()], msg['body']) - - def alias2emoji(self, msg, tab): - msg['body'] = self.alias_pattern.sub(lambda m: self.ascii_to_emoji[m.group()], msg['body']) diff --git a/desktop/.local/share/poezio/plugins/exec.py b/desktop/.local/share/poezio/plugins/exec.py deleted file mode 100644 index 68f2448..0000000 --- a/desktop/.local/share/poezio/plugins/exec.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -This plugin lets you execute a system command through poezio. - -Usage ------ - -.. warning:: Running commands that start a daemon or an interface is not a good - idea. - -.. glossary:: - - /exec - **Usage:** ``/exec [-o|-O] `` - - Execute a system command. - - :: - - /exec command - - Will give you the result in the information buffer. - - :: - - /exec -o command - - Will send the result of the command into the current tab, if possible. - - :: - - /exec -O command - - Will send the result of the command and the command summary into the current - tab, if possible. - -""" - -from poezio.plugin import BasePlugin -from poezio import common -import asyncio -import subprocess - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'exec', - self.command_exec, - usage='[-o|-O] ', - help= - 'Execute a shell command and prints the result in the information buffer. The command should be ONE argument, that means it should be between \"\". The first argument (before the command) can be -o or -O. If -o is specified, it sends the result in the current conversation. If -O is specified, it sends the command and its result in the current conversation.\nExample: /exec -O \"uptime\" will send “uptime\n20:36:19 up 3:47, 4 users, load average: 0.09, 0.13, 0.09” in the current conversation.', - short='Execute a command') - - async def async_exec(self, command, arg): - create = asyncio.create_subprocess_exec( - 'sh', - '-c', - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - process = await create - except OSError as e: - self.api.information('Failed to execute command: %s' % (e, ), - 'Error') - return - stdout, stderr = await process.communicate() - result = stdout.decode('utf-8') - stderr = stderr.decode('utf-8') - if arg == '-o': - if not self.api.send_message('%s' % (result, )): - self.api.information( - 'Cannot send result (%s), this is not a conversation tab' % - result, 'Error') - elif arg == '-O': - if not self.api.send_message('%s:\n%s' % (command, result)): - self.api.information( - 'Cannot send result (%s), this is not a conversation tab' % - result, 'Error') - else: - self.api.information('%s:\n%s' % (command, result), 'Info') - if stderr: - self.api.information('stderr for %s:\n%s' % (command, stderr), - 'Info') - await process.wait() - - def command_exec(self, args): - args = common.shell_split(args) - if len(args) == 1: - command = args[0] - arg = None - elif len(args) == 2: - command = args[1] - arg = args[0] - else: - self.api.run_command('/help exec') - return - asyncio.create_task(self.async_exec(command, arg)) diff --git a/desktop/.local/share/poezio/plugins/figlet.py b/desktop/.local/share/poezio/plugins/figlet.py deleted file mode 100644 index 4d4c757..0000000 --- a/desktop/.local/share/poezio/plugins/figlet.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -This plugin uses figlet to transform every message into a big ascii-art -message. - - -Usage ------ - -Say something in a Chat tab. - -.. note:: Can create fun things when used with :ref:`The rainbow plugin `. - -""" - -import subprocess -from poezio.plugin import BasePlugin - - -def is_figlet() -> bool: - """Ensure figlet exists""" - process = subprocess.Popen( - ['which', 'figlet'], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return process.wait() == 0 - - -class Plugin(BasePlugin): - def init(self): - if not is_figlet(): - self.api.information( - 'Couldn\'t find the figlet program. ' - 'Please install it and reload the plugin.', - 'Error', - ) - return None - - self.api.add_event_handler('muc_say', self.figletize) - self.api.add_event_handler('conversation_say', self.figletize) - self.api.add_event_handler('private_say', self.figletize) - return None - - def figletize(self, msg, tab): - process = subprocess.Popen( - ['figlet', '--', msg['body']], stdout=subprocess.PIPE) - result = process.communicate()[0].decode('utf-8') - msg['body'] = result diff --git a/desktop/.local/share/poezio/plugins/iq_show.py b/desktop/.local/share/poezio/plugins/iq_show.py deleted file mode 100644 index ad8f9e7..0000000 --- a/desktop/.local/share/poezio/plugins/iq_show.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Show the exchanged IQs (useful for debugging). - -""" -from poezio.plugin import BasePlugin -from slixmpp.xmlstream.matcher import StanzaPath -from slixmpp.xmlstream.handler import Callback - - -class Plugin(BasePlugin): - def init(self): - self.core.xmpp.register_handler( - Callback('Iq_show', StanzaPath('iq'), self.handle_iq)) - - def handle_iq(self, iq): - self.api.information('%s' % iq, 'Iq') - - def cleanup(self): - self.core.xmpp.remove_handler('Iq_show') diff --git a/desktop/.local/share/poezio/plugins/irc.py b/desktop/.local/share/poezio/plugins/irc.py deleted file mode 100644 index f3aa7b6..0000000 --- a/desktop/.local/share/poezio/plugins/irc.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -Plugin destined to be used together with the Biboumi IRC gateway. - -For more information about Biboumi, please see the `official website`_. - -This plugin is here as a non-default extension of the poezio configuration -made to work with IRC rooms and logins. It also defines commands aimed at -reducing the amount of effort needed to navigate smoothly between IRC and -XMPP rooms. - -Configuration -------------- - -Every feature of this plugin is centered around its :ref:`configuration file `, -so you have to make sure it is filled properly. - -Global configuration -~~~~~~~~~~~~~~~~~~~~ -.. glossary:: - :sorted: - - gateway - **Default:** ``irc.jabberfr.org`` - - The JID of the IRC gateway to use. If empty, irc.jabberfr.org will be - used. Please try to run your own, though, it’s painless to setup. - - initial_connect - **Default:** ``true`` - - Set to ``true`` if you want to join all the rooms and try to - authenticate with nickserv when the plugin gets loaded. If it set to - ``false``, you will have to use the :term:`/irc_login` command to - authenticate, and the :term:`/irc_join` command to join - preconfigured rooms. - -.. note:: There is no nickname option because the default from poezio will be used. - -Server-specific configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Write a configuration section for each server, with the server address as the -section name, and the following options: - - -.. glossary:: - :sorted: - - nickname - **Default:** ``[empty]`` - - Your nickname on this server. If empty, the default configuration will be used. - - rooms [IRC plugin] - **Default:** ``[empty]`` - - The list of rooms to join on this server (e.g. ``#room1:#room2``). - -.. note:: If no login_command or login_nick is set, the authentication phase - won’t take place and you will join the rooms without authentication - with nickserv or whatever. - -Commands -~~~~~~~~ - -.. glossary:: - :sorted: - - /irc_join - **Usage:** ``/irc_join `` - - Join the specified room on the same server as the current tab (can - be a private conversation or a chatroom). If a server that appears - in the conversation is specified instead of a room, the plugin - will try to join all the rooms configured with autojoin on that - server. - - /irc_query - **Usage:** ``/irc_query [message]`` - - Open a private conversation with the given nickname, on the same IRC - server as the current tab (can be a private conversation or a - chatroom). Doing `/irc_query foo "hello there"` when the current - tab is #foo%irc.example.com@biboumi.example.com is equivalent to - ``/message foo%irc.example.com@biboumi.example.com "hello there"`` - -Example configuration -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: ini - - [irc] - gateway = irc.jabberfr.org - - [irc.libera.chat] - nickname = mynick - login_nick = nickserv - login_command = identify mypassword - rooms = #testroom1:#testroom2 - - [irc.geeknode.org] - nickname = anothernick - login_nick = C - login_command = nick identify mypassword - rooms = #testvroum - - - -.. _official website: http://biboumi.louiz.org/ - -""" - -import asyncio - -from typing import Optional, Tuple, List, Any -from slixmpp.jid import JID, InvalidJID - -from poezio.plugin import BasePlugin -from poezio.decorators import command_args_parser -from poezio.core.structs import Completion -from poezio import tabs - - -class Plugin(BasePlugin): - default_config = { - 'irc': { - "initial_connect": True, - "gateway": "irc.jabberfr.org", - } - } - - def init(self) -> None: - if self.config.getbool('initial_connect'): - asyncio.create_task( - self.initial_connect() - ) - self.api.add_command( - 'irc_join', - self.command_irc_join, - usage='', - help=('Join in the same server as the ' - 'current tab (if it is an IRC tab). Or ' - 'join all the preconfigured rooms in ' - ' '), - short='Join irc rooms more easily', - completion=self.completion_irc_join) - - self.api.add_command( - 'irc_query', - self.command_irc_query, - usage=' [message]', - help=('Open a private conversation with the ' - 'given , on the current IRC ' - 'server. Optionally immediately send ' - 'the given message. For example, if the ' - 'current tab is #foo%irc.example.com@' - 'biboumi.example.com, doing `/irc_query ' - 'nick "hi there"` is equivalent to ' - '`/message nick%irc.example.com@biboumi.' - 'example.com "hi there"`'), - short='Open a private conversation with an IRC user') - - async def join(self, gateway: str, server: JID) -> None: - "Join irc rooms on a server" - nick: str = self.config.get_by_tabname( - 'nickname', server, default='') or self.core.own_nick - rooms: List[str] = self.config.get_by_tabname( - 'rooms', server, default='').split(':') - joins = [] - for room in rooms: - room = '{}%{}@{}/{}'.format(room, server, gateway, nick) - joins.append(self.core.command.join(room)) - - await asyncio.gather(*joins) - - async def initial_connect(self) -> None: - gateway: str = self.config.getstr('gateway') - sections: List[str] = self.config.sections() - - sections_jid = [] - for sect in sections: - if sect == 'irc': - continue - try: - sect_jid = JID(sect) - if sect_jid != sect_jid.server: - self.api.information(f'Invalid server: {sect}', 'Warning') - continue - except InvalidJID: - self.api.information(f'Invalid server: {sect}', 'Warning') - continue - sections_jid.append(sect_jid) - - for section in sections_jid: - room_suffix = '%{}@{}'.format(section, gateway) - - already_opened = False - for tab in self.core.tabs: - if tab.name.endswith(room_suffix) and tab.joined: - already_opened = True - break - - if not already_opened: - await self.join(gateway, section) - - @command_args_parser.quoted(1, 1) - async def command_irc_join(self, args: Optional[List[str]]) -> None: - """ - /irc_join - """ - if not args: - self.core.command.help('irc_join') - return - sections: List[str] = self.config.sections() - if 'irc' in sections: - sections.remove('irc') - if args[0] in sections: - try: - section_jid = JID(args[0]) - except InvalidJID: - self.api.information(f'Invalid address: {args[0]}', 'Error') - return - #self.config.get_by_tabname('rooms', section_jid) - await self.join_server_rooms(section_jid) - else: - await self.join_room(args[0]) - - @command_args_parser.quoted(1, 1) - def command_irc_query(self, args: Optional[List[str]]) -> None: - """ - Open a private conversation with the given nickname, on the current IRC - server. - """ - if args is None: - self.core.command.help('irc_query') - return - current_tab_info = self.get_current_tab_irc_info() - if not current_tab_info: - return - server, gateway = current_tab_info - nickname = args[0] - message = None - if len(args) == 2: - message = args[1] - jid = '{}%{}@{}'.format(nickname, server, gateway) - if message: - self.core.command.message('{} "{}"'.format(jid, message)) - else: - self.core.command.message('{}'.format(jid)) - - async def join_server_rooms(self, section: JID) -> None: - """ - Join all the rooms configured for a section - (section = irc server) - """ - gateway: str = self.config.getstr('gateway') - rooms: List[str] = self.config.get_by_tabname('rooms', section).split(':') - nick: str = self.config.get_by_tabname('nickname', section) - if nick: - nick = '/' + nick - else: - nick = '' - suffix = '%{}@{}{}'.format(section, gateway, nick) - - for room in rooms: - await self.core.command.join(room + suffix) - - async def join_room(self, name: str) -> None: - """ - Join a room with only its name and the current tab - """ - current_tab_info = self.get_current_tab_irc_info() - if not current_tab_info: - return - server, gateway = current_tab_info - try: - server_jid = JID(server) - except InvalidJID: - return - - room = '{}%{}@{}'.format(name, server, gateway) - if self.config.get_by_tabname('nickname', server_jid.bare): - room += '/' + self.config.get_by_tabname('nickname', server_jid.bare) - - await self.core.command.join(room) - - def get_current_tab_irc_info(self) -> Optional[Tuple[str, str]]: - """ - Return a tuple with the irc server and the gateway hostnames of the - current tab. If the current tab is not an IRC channel or private - conversation, a warning is displayed and None is returned - """ - gateway: str = self.config.getstr('gateway') - current = self.api.current_tab() - current_jid = current.jid - if not current_jid.server == gateway: - self.api.information( - 'The current tab does not appear to be an IRC one', 'Warning') - return None - if isinstance(current, tabs.OneToOneTab): - if '%' not in current_jid.node: - server = current_jid.node - else: - ignored, server = current_jid.node.rsplit('%', 1) - elif isinstance(current, tabs.MucTab): - if '%' not in current_jid.node: - server = current_jid.node - else: - ignored, server = current_jid.node.rsplit('%', 1) - else: - self.api.information( - 'The current tab does not appear to be an IRC one', 'Warning') - return None - return server, gateway - - def completion_irc_join(self, the_input: Any) -> Completion: - """ - completion for /irc_join - """ - sections: List[str] = self.config.sections() - if 'irc' in sections: - sections.remove('irc') - return Completion(the_input.new_completion, sections, 1) diff --git a/desktop/.local/share/poezio/plugins/lastlog.py b/desktop/.local/share/poezio/plugins/lastlog.py deleted file mode 100644 index 1c48fa0..0000000 --- a/desktop/.local/share/poezio/plugins/lastlog.py +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 -# -# Copyright © 2018 Maxime “pep” Buquet -# Copyright © 2019 Madhur Garg -# -# Distributed under terms of the GPL-3.0+ license. See the COPYING file. - -""" - Search provided string in the buffer and return all results on the screen -""" - -import re -from typing import Optional -from datetime import datetime - -from poezio.plugin import BasePlugin -from poezio import tabs -from poezio.text_buffer import TextBuffer -from poezio.ui.types import Message as PMessage, InfoMessage - - -def add_line( - text_buffer: TextBuffer, - text: str, - datetime: Optional[datetime] = None, - ) -> None: - """Adds a textual entry in the TextBuffer""" - text_buffer.add_message(InfoMessage(text, time=datetime)) - - -class Plugin(BasePlugin): - """Lastlog Plugin""" - - def init(self): - for tab in tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab, tabs.MucTab: - self.api.add_tab_command( - tab, - 'lastlog', - self.command_lastlog, - usage='', - help='Search in the buffer and returns results' - 'on the screen') - - def command_lastlog(self, input_): - """Define lastlog command""" - - text_buffer = self.api.current_tab()._text_buffer - search_re = re.compile(input_, re.I) - - res = [] - add_line(text_buffer, "Lastlog:") - for message in text_buffer.messages: - if isinstance(message, PMessage) and \ - search_re.search(message.txt) is not None: - res.append(message) - add_line(text_buffer, "%s> %s" % (message.nickname, message.txt), message.time) - add_line(text_buffer, "End of Lastlog") - self.api.current_tab().text_win.pos = 0 - self.api.current_tab().core.refresh_window() diff --git a/desktop/.local/share/poezio/plugins/link.py b/desktop/.local/share/poezio/plugins/link.py deleted file mode 100644 index 699215e..0000000 --- a/desktop/.local/share/poezio/plugins/link.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Opens links in a browser. - -Installation ------------- - -First use case: local use -~~~~~~~~~~~~~~~~~~~~~~~~~ -If you use poezio on your workstation, this is for you. -You only have to load the plugin: :: - - /load link - -Second use case: remote use -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you use poezio through SSH, this is for you. - -.. note:: Small explanation: Poezio will create a `Unix FIFO`_ and send the commands in, - and you will have to run a dæmon locally with ssh, to get those commands. - -First, set the :term:`exec_remote` option in the config file to ``true``. Then select -the directory you want to put the fifo in (default is the current -directory, :file:`./`), the :file:`poezio.fifo` file will be created there. - -After that, load the plugin: :: - - /load link - -And open a link with :term:`/link` (as described below), this will create the FIFO. - -You need to grab poezio’s sources on your client computer, or at least the `daemon.py`_ -file. - -Finally, on your client computer, run the ssh command: - -.. code-block:: bash - - ssh toto@example.org "cat ~/poezio/poezio.fifo" | python3 daemon.py - -Usage ------ - -.. glossary:: - - /link - **Usage:** ``/link [range] [command]`` - - This plugin adds a :term:`/link` command that will open the links in - ``firefox``. If you want to use another browser, or any other - command, you can use the :term:`/set` command to change the - :term:`browser` option. You can also specify the command to execute - directly in the arguments. For example `/link "mpv %s"` will open - the first link found using mpv, instead of the configured browser. - - - :term:`/link` without argument will open the last link found - in the current tab, if any is found. An optional range - argument can be given, to select one or more links to - open. Examples: - ``/link 1`` is equivalent to ``/link`` - ``/link 3`` will open the third link found in the current tab, - starting from the bottom. - ``/link 1:5`` will open the last five links in the current tab - ``/link :2`` will open the last two links - -Options -------- - -:term:`exec_remote` - - To execute the command on your client - -.. glossary:: - - browser - Set the default browser started by the plugin - -.. _Unix FIFO: https://en.wikipedia.org/wiki/Named_pipe -.. _daemon.py: https://lab.louiz.org/poezio/poezio/raw/main/poezio/daemon.py - -""" -import platform -import re - -from poezio.plugin import BasePlugin -from poezio.xhtml import clean_text -from poezio import common -from poezio import tabs - -url_pattern = re.compile( - r'\b' - '(?:http[s]?://(?:\S+))|' - '(?:magnet:\?(?:\S+))|' - '(?:aesgcm://(?:\S+))|' - '(?:gopher://(?:\S+))|' - '(?:gemini://(?:\S+))' - '\b', - re.I | re.U -) - -app_mapping = { - 'Linux': 'xdg-open', - 'Darwin': 'open', -} - - -class Plugin(BasePlugin): - def init(self): - for _class in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab): - self.api.add_tab_command( - _class, - 'link', - self.command_link, - usage='[num] [command]', - help= - 'Opens the last link from the conversation into a browser.\n\ - If [num] is given, then it will\open the num-th link displayed. \ - Use a [command] argument to override the configured browser value.', - short='Open links into a browser') - - def find_link(self, nb): - messages = self.api.get_conversation_messages() - if not messages: - return None - for message in messages[::-1]: - matches = url_pattern.findall(clean_text(message.txt)) - if matches: - for url in matches[::-1]: - if nb == 1: - return url - else: - nb -= 1 - return None - - def command_link(self, args): - args = common.shell_split(args) - start = 1 - end = 1 - # With two arguments, the first is the range, the second is the command - # With only one argument, it is a range if it starts with a number - # or :, otherwise it is a command - if len(args) == 2 or\ - len(args) == 1 and (args[0][0].isnumeric() or args[0][0] == ":"): - if args[0].find(':') == -1: - try: - start = int(args[0]) - end = start - except ValueError: - return self.api.run_command('/help link') - else: - start, end = args[0].split(':', 1) - if start == '': - start = 1 - try: - start = int(start) - end = int(end) - except ValueError: - return self.api.information( - 'Invalid range: %s' % (args[0]), 'Error') - command = None - if len(args) == 2: - command = args[1] - if len(args) == 1 and (not args[0][0].isnumeric() - and args[0][0] != ":"): - command = args[0] - for nb in range(start, end + 1): - link = self.find_link(nb) - if not link: - return self.api.information('No URL found.', 'Warning') - default = app_mapping.get(platform.system(), 'firefox') - if command is None: - self.core.exec_command( - [self.config.get('browser', default), link]) - else: - self.core.exec_command([command, link]) - - def cleanup(self): - del self.config diff --git a/desktop/.local/share/poezio/plugins/marquee.py b/desktop/.local/share/poezio/plugins/marquee.py deleted file mode 100644 index 66ec8b7..0000000 --- a/desktop/.local/share/poezio/plugins/marquee.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Marquee plugin: replicate the html tag with message corrections. - -Usage of this plugin is not recommended. - -Commands --------- - -.. glossary:: - - /marquee - Send the following text with behavior - -Configuration -------------- - -.. glossary:: - :sorted: - - refresh - **Default:** ``1`` - - Interval between each correction (the closest to 0 is the fastest) - - total_duration - **Default:** ``30`` - - Total duration of the animation. - - padding - **Default:** ``20`` - - Padding to use to move the text. - - -""" -import asyncio -from poezio.plugin import BasePlugin -from poezio import tabs -from poezio import xhtml -from poezio.decorators import command_args_parser - - -def move(text, step, spacing): - new_text = text + ("\u00A0" * spacing) - return new_text[-(step % len(new_text)):] + new_text[:-( - step % len(new_text))] - - -class Plugin(BasePlugin): - default_config = { - "marquee": { - "refresh": 1.0, - "total_duration": 30, - "padding": 20 - } - } - - def init(self): - for tab_t in [tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab]: - self.add_tab_command( - tab_t, 'marquee', self.command_marquee, - 'Replicate the behavior in a message') - - @command_args_parser.raw - async def command_marquee(self, args): - if not args: - return None - tab = self.api.current_tab() - args = xhtml.clean_text(xhtml.convert_simple_to_full_colors(args)) - await tab.command_say(args) - is_muctab = isinstance(tab, tabs.MucTab) - msg_id = tab.last_sent_message["id"] - jid = tab.jid - - event = self.api.create_delayed_event( - self.config.get("refresh"), self.delayed_event, jid, args, msg_id, - 1, 0, is_muctab) - self.api.add_timed_event(event) - - def delayed_event(self, jid, body, msg_id, step, duration, is_muctab): - if duration >= self.config.get("total_duration"): - return - message = self.core.xmpp.make_message(jid) - message["type"] = "groupchat" if is_muctab else "chat" - message["body"] = move(body, step, self.config.get("padding")) - message["replace"]["id"] = msg_id - message.send() - event = self.api.create_delayed_event( - self.config.get("refresh"), self.delayed_event, jid, body, - msg_id, step + 1, duration + self.config.get("refresh"), - is_muctab) - self.api.add_timed_event(event) diff --git a/desktop/.local/share/poezio/plugins/mirror.py b/desktop/.local/share/poezio/plugins/mirror.py deleted file mode 100644 index 55c429a..0000000 --- a/desktop/.local/share/poezio/plugins/mirror.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Repeats the last message in the conversation. - -Command -------- - -.. glossary:: - - /mirror - **Usage:** ``/mirror`` - -""" -from poezio.plugin import BasePlugin -from poezio import tabs - - -class Plugin(BasePlugin): - def init(self): - for tab_type in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab): - self.api.add_tab_command( - tab_type, - 'mirror', - handler=self.mirror, - help='Repeat the last message from the conversation.', - short='Repeat the last message from the conversation.') - - def mirror(self, args): - messages = self.api.get_conversation_messages() - if not messages: - # Do nothing if the conversation doesn’t contain any message - return - last_message = messages[-1] - self.api.send_message(last_message.txt) diff --git a/desktop/.local/share/poezio/plugins/mpd_client.py b/desktop/.local/share/poezio/plugins/mpd_client.py deleted file mode 100644 index f1eea90..0000000 --- a/desktop/.local/share/poezio/plugins/mpd_client.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -This plugin is here to send what you are listening to in a chat tab. - -Installation ------------- - -You need `python-mpd`_, in its python3 version. - -Then you can load the plugin. - -.. code-block:: none - - /load mpd_client - - -Configuration -------------- - -You have to put the following into :file:`mpd_client.cfg`, as explained in -the :ref:`plugin-configuration` section. - -.. note:: If you do not put anything, the plugin will try to connect to - :file:`localhost:6600` with no password. - -.. code-block:: ini - - [mpd_client] - host = the_mpd_host - port = 6600 - password = password if necessary - - -Usage ------ - -.. glossary:: - - /mpd - **Usage:** ``/mpd [full]`` - - The bare command will show the current song, artist, and album - - ``/mpd full`` will show the current song, artist, and album, - plus a nice progress bar in color. - -.. _python-mpd: https://github.com/Mic92/python-mpd2 - -""" - -from poezio.plugin import BasePlugin -from poezio.common import shell_split -from poezio.core.structs import Completion -from os.path import basename as base -from poezio import tabs -import mpd - - -class Plugin(BasePlugin): - def init(self): - for _class in (tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.MucTab, tabs.PrivateTab): - self.api.add_tab_command( - _class, - 'mpd', - self.command_mpd, - usage='[full]', - help= - 'Sends a message showing the current song of an MPD instance. If full is provided, the message is more verbose.', - short='Send the MPD status', - completion=self.completion_mpd) - - def command_mpd(self, args): - args = shell_split(args) - c = mpd.MPDClient() - c.connect( - host=self.config.get('host', 'localhost'), - port=self.config.get('port', '6600')) - password = self.config.get('password', '') - if password: - c.password(password) #pylint: disable=no-member - current = c.currentsong() #pylint: disable=no-member - artist = current.get('artist', 'Unknown artist') - album = current.get('album', 'Unknown album') - title = current.get('title', base( - current.get('file', 'Unknown title'))) - - s = '%s - %s (%s)' % (artist, title, album) - if 'full' in args: - if 'elapsed' in current and 'time' in current: - current_time = float(c.status()['elapsed']) #pylint: disable=no-member - percents = int(current_time / float(current['time']) * 10) - s += ' \x192}[\x191}' + '-' * ( - percents - 1) + '\x193}+' + '\x191}' + '-' * ( - 10 - percents - 1) + '\x192}]\x19o' - if not self.api.send_message('%s' % (s, )): - self.api.information('Cannot send result (%s)' % s, 'Error') - - def completion_mpd(self, the_input): - return Completion(the_input.auto_completion, ['full'], quotify=False) diff --git a/desktop/.local/share/poezio/plugins/otr.py b/desktop/.local/share/poezio/plugins/otr.py deleted file mode 100644 index 6c15f3d..0000000 --- a/desktop/.local/share/poezio/plugins/otr.py +++ /dev/null @@ -1,1068 +0,0 @@ -""" - -This plugin implements `Off The Record messaging`_. - -This is a plugin used to encrypt a one-to-one conversation using the OTR -encryption method. You can use it if you want good privacy, deniability, -authentication, and strong secrecy. Without this encryption, your messages -are encrypted **at least** from your client (poezio) to your server. The -message is decrypted by your server and you cannot control the encryption -method of your messages from your server to your contact’s server (unless -you are your own server’s administrator), nor from your contact’s server -to your contact’s client. - -This plugin does end-to-end encryption. This means that **only** your contact can -decrypt your messages, and it is fully encrypted during **all** its travel -through the internet. - -Note that if you are having an encrypted conversation with a contact, you can -**not** send XHTML-IM messages to them (or correct messages, or anything more than -raw text). All formatting will be removed and be replaced by plain text messages. - -This is a limitation of the OTR protocol, and it will never be fixed. Some clients -like Pidgin-OTR try do do magic stuff with html unescaping inside the OTR body, and -it is not pretty. - -Installation ------------- - -To use the OTR plugin, you must first install pure-python-otr and pycrypto -(for python3). - -You have to install it from the git because a few issues were -found with the python3 compatibility while writing this plugin, -and the fixes did not make it into a stable release yet. - -Install the python module: - -.. code-block:: bash - - git clone https://github.com/afflux/pure-python-otr.git - cd pure-python-otr - python3 setup.py install --user - -You can also use pip in a virtualenv (built-in as pyvenv_ with python since 3.3) -with the requirements.txt at the root of the poezio directory. - -Important details ------------------ - -The OTR session is considered for a full JID (e.g. toto@example/**client1**), -but the trust is set with a bare JID (e.g. toto@example). This is important -in the case of Private Chats (in a chatroom), since you cannot always get the -real JID of your contact (or check if the same nick is used by different people). - -.. note:: - - This also means that you cannot have an OTR session in the "common" - conversation tab, which is not locked to a specific JID. After activating - the plugin, you need to open a session with a full JID to be able to use - OTR. - -Usage ------ - -Command added to Static Conversation Tabs (opened with ``/message foo@bar/baz`` or -by expanding a contact in the roster) and Private Tabs: - -.. glossary:: - - /otr - **Usage:** ``/otr [start|refresh|end|fpr|ourfpr|trust|untrust]`` - - This command is used to manage an OTR private session. - - - The ``start`` (or ``refresh``) command starts or refreshs a private OTR session - - The ``end`` command ends a private OTR session - - The ``fpr`` command gives you the fingerprint of the key of the remote entity - - The ``ourfpr`` command gives you the fingerprint of your own key - - The ``trust`` command marks the current remote key as trusted for the current remote JID - - The ``untrust`` command removes that trust - - Finally, the ``drop`` command is used if you want to delete your private key (not recoverable). - - .. warning:: - - With ``drop``, the private key is only removed from the filesystem, - *NOT* with multiple rewrites in a secure manner, you should do that - yourself if you want to be sure. - - /otrsmp - **Usage:** ``/otrsmp [question] [secret]`` - - Verify the identify of your contact by using a pre-defined secret. - - - The ``abort`` command aborts an ongoing verification - - The ``ask`` command start a verification, with a question or not - - The ``answer`` command sends back the answer and finishes the verification - -Managing trust --------------- - -An OTR conversation can be started with a simple ``/otr start`` and the -conversation will be encrypted. However it is very often useful to check -that your are talking to the right person. - -To this end, two actions are available, and a message explaining both -will be prompted each time an **untrusted** conversation is started: - -- Checking the knowledge of a shared secret through the use of :term:`/otrsmp` -- Exchanging fingerprints (``/otr fpr`` and ``/otr ourfpr``) out of band (in a secure channel) to check that both match, - then use ``/otr trust`` to add then to the list of trusted fingerprints for this JID. - -Files ------ - -This plugin creates trust files complatible with libotr and the files produced by gajim. - - -The files are located in :file:`$XDG_DATA_HOME/poezio/otr/` by default (so -:file:`~/.local/share/poezio/otr` in most cases). - -Two files are created: - -- An account_jid.key3 (:file:`example@example.com.key3`) file, which contains the private key -- An account_jid.fpr (:file:`example@example.com.fpr`) file, which contains the list of trusted - (or untrusted) JIDs and keys. - -Configuration -------------- - -.. glossary:: - :sorted: - - decode_xhtml - **Default:** ``true`` - - Decode embedded XHTML. - - decode_entities - **Default:** ``true`` - - Decode XML and HTML entities (like ``&``) even when the - document isn't valid (if it is valid, it will be decoded even - without this option). - - decode_newlines - **Default:** ``true`` - - Decode ``
`` and ``
`` tags even when the document - isn't valid (if it is valid, it will be decoded even - without this option for ``
``, and ``
`` will make - the document invalid anyway). - - keys_dir - **Default:** ``$XDG_DATA_HOME/poezio/otr`` - - The directory in which you want keys and fpr to be stored. - - require_encryption - **Default:** ``false`` - - If ``true``, prevents you from sending unencrypted messages, and tries - to establish OTR sessions when receiving unencrypted messages. - - timeout - **Default:** ``3`` - - The number of seconds poezio will wait until notifying you - that the OTR session was not established. A negative or null - value will disable this notification. - - log - **Default:** ``false`` - - Log conversations (OTR start/end marker, and messages). - -The :term:`require_encryption`, :term:`decode_xhtml`, :term:`decode_entities` -and :term:`log` configuration parameters are tab-specific. - -.. _Off The Record messaging: http://wiki.xmpp.org/web/OTR -.. _pyvenv: https://docs.python.org/3/using/scripts.html#pyvenv-creating-virtual-environments - -""" - -from gettext import gettext as _ -import logging - -import os -import html -import curses -from pathlib import Path - -import potr -from potr.context import NotEncryptedError, UnencryptedMessage, ErrorReceived, NotOTRMessage,\ - STATE_ENCRYPTED, STATE_PLAINTEXT, STATE_FINISHED, Context, Account, crypt - -from slixmpp import JID, InvalidJID - -from poezio import common -from poezio import xdg -from poezio import xhtml -from poezio.config import config -from poezio.plugin import BasePlugin -from poezio.roster import roster -from poezio.tabs import StaticConversationTab, PrivateTab -from poezio.theming import get_theme, dump_tuple -from poezio.decorators import command_args_parser -from poezio.core.structs import Completion -from poezio.ui.types import InfoMessage, Message - -log = logging.getLogger(__name__) - -POLICY_FLAGS = { - 'ALLOW_V1': False, - 'ALLOW_V2': True, - 'REQUIRE_ENCRYPTION': False, - 'SEND_TAG': True, - 'WHITESPACE_START_AKE': True, - 'ERROR_START_AKE': True -} - -log = logging.getLogger(__name__) - -OTR_TUTORIAL = _("""%(info)sThis contact has not yet been verified. -You have several methods of authentication available: - -1) Verify each other's fingerprints using a secure (and different) channel: -Your fingerprint: %(normal)s%(our_fpr)s%(info)s -%(jid_c)s%(jid)s%(info)s's fingerprint: %(normal)s%(remote_fpr)s%(info)s -Then use the command: /otr trust - -2) SMP pre-shared secret you both know: -/otrsmp ask - -3) SMP pre-shared secret you both know with a question: -/otrsmp ask -""") - -OTR_NOT_ENABLED = _('%(jid_c)s%(jid)s%(info)s did not enable ' - 'OTR after %(secs)s seconds.') - -MESSAGE_NOT_SENT = _('%(info)sYour message to %(jid_c)s%(jid)s%(info)s was' - ' not sent because your configuration requires an ' - 'encrypted session.\nWait until it is established or ' - 'change your configuration.') - -INCOMPATIBLE_TAB = _('%(info)sYour message to %(jid_c)s%(jid)s%(info)s was' - ' not sent because your configuration requires an ' - 'encrypted session and the current tab is a bare-jid ' - 'one, with which you cannot open or use an OTR session.' - ' You need to open a fulljid tab with /message if you ' - 'want to use OTR.%(help)s') - -TAB_HELP_RESOURCE = _('\nChoose the relevant one among the following:%s') - -OTR_REQUEST = _('%(info)sOTR request to %(jid_c)s%(jid)s%(info)s sent.') - -OTR_OWN_FPR = _('%(info)sYour OTR key fingerprint is ' - '%(normal)s%(fpr)s%(info)s.') - -OTR_REMOTE_FPR = _('%(info)sThe key fingerprint for %(jid_c)s' - '%(jid)s%(info)s is %(normal)s%(fpr)s%(info)s.') - -OTR_NO_FPR = _('%(jid_c)s%(jid)s%(info)s has no' ' key currently in use.') - -OTR_START_TRUSTED = _('%(info)sStarted a \x19btrusted\x19o%(info)s ' - 'OTR conversation with %(jid_c)s%(jid)s') - -OTR_REFRESH_TRUSTED = _('%(info)sRefreshed \x19btrusted\x19o%(info)s' - ' OTR conversation with %(jid_c)s%(jid)s') - -OTR_START_UNTRUSTED = _('%(info)sStarted an \x19buntrusted\x19o%(info)s' - ' OTR conversation with %(jid_c)s%(jid)s') - -OTR_REFRESH_UNTRUSTED = _('%(info)sRefreshed \x19buntrusted\x19o%(info)s' - ' OTR conversation with %(jid_c)s%(jid)s') - -OTR_END = _('%(info)sEnded OTR conversation with %(jid_c)s%(jid)s') - -SMP_REQUESTED = _('%(jid_c)s%(jid)s%(info)s has requested SMP verification' - '%(q)s%(info)s.\nAnswer with: /otrsmp answer ') - -SMP_INITIATED = _('%(info)sInitiated SMP request with ' - '%(jid_c)s%(jid)s%(info)s.') - -SMP_PROGRESS = _('%(info)sSMP progressing.') - -SMP_RECIPROCATE = _('%(info)sYou may want to authenticate your peer by asking' - ' your own question: /otrsmp ask [question] ') - -SMP_SUCCESS = _('%(info)sSMP Verification \x19bsucceeded\x19o%(info)s.') - -SMP_FAIL = _('%(info)sSMP Verification \x19bfailed\x19o%(info)s.') - -SMP_ABORTED_PEER = _('%(info)sSMP aborted by peer.') - -SMP_ABORTED = _('%(info)sSMP aborted.') - -MESSAGE_UNENCRYPTED = _('%(info)sThe following message from %(jid_c)s%(jid)s' - '%(info)s was \x19bnot\x19o%(info)s encrypted:\x19o\n' - '%(msg)s') - -MESSAGE_UNREADABLE = _('%(info)sAn encrypted message from %(jid_c)s%(jid)s' - '%(info)s was received but is unreadable, as you are' - ' not currently communicating privately.') - -MESSAGE_INVALID = _('%(info)sThe message from %(jid_c)s%(jid)s%(info)s' - ' could not be decrypted.') - -OTR_ERROR = _('%(info)sReceived the following error from ' - '%(jid_c)s%(jid)s%(info)s:\x19o %(err)s') - -POTR_ERROR = _('%(info)sAn unspecified error in the OTR plugin occurred:\n' - '%(exc)s') - -TRUST_ADDED = _('%(info)sYou added %(jid_c)s%(bare_jid)s%(info)s with key ' - '\x19o%(key)s%(info)s to your trusted list.') - -TRUST_REMOVED = _('%(info)sYou removed %(jid_c)s%(bare_jid)s%(info)s with ' - 'key \x19o%(key)s%(info)s from your trusted list.') - -KEY_DROPPED = _('%(info)sPrivate key dropped.') - - -def hl(tab): - """ - Make a tab beep and change its status. - """ - if tab.state != 'current': - tab.state = 'private' - - conv_jid = tab.jid - if 'private' in config.get('beep_on', 'highlight private').split(): - if not config.get_by_tabname( - 'disable_beep', conv_jid.bare, default=False): - curses.beep() - - -class PoezioContext(Context): - """ - OTR context, specific to a conversation with a contact - - Overrides methods from potr.context.Context - """ - - def __init__(self, account, peer, xmpp, core): - super(PoezioContext, self).__init__(account, peer) - self.xmpp = xmpp - self.core = core - self.flags = {} - self.trustName = JID(peer).bare - self.in_smp = False - self.smp_own = False - self.log = 0 - - def getPolicy(self, key): - if key in self.flags: - return self.flags[key] - else: - return False - - def reset_smp(self): - self.in_smp = False - self.smp_own = False - - def inject(self, msg, appdata=None): - message = self.xmpp.make_message( - mto=self.peer, mbody=msg.decode('ascii'), mtype='chat') - message['eme']['namespace'] = 'urn:xmpp:otr:0' - message.enable('carbon_private') - message.enable('no-copy') - message.enable('no-permanent-store') - message.send() - - def setState(self, newstate): - format_dict = { - 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), - 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'normal': '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT), - 'jid': self.peer, - 'bare_jid': JID(self.peer).bare - } - - tab = self.core.tabs.by_name(self.peer) - if not tab: - tab = None - if self.state == STATE_ENCRYPTED: - if newstate == STATE_ENCRYPTED and tab: - log.debug('OTR conversation with %s refreshed', self.peer) - if self.getCurrentTrust(): - msg = OTR_REFRESH_TRUSTED % format_dict - tab.add_message(InfoMessage(msg)) - else: - msg = OTR_REFRESH_UNTRUSTED % format_dict - tab.add_message(InfoMessage(msg)) - hl(tab) - elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: - log.debug('OTR conversation with %s finished', self.peer) - if tab: - tab.add_message(InfoMessage(OTR_END % format_dict)) - hl(tab) - elif newstate == STATE_ENCRYPTED and tab: - if self.getCurrentTrust(): - tab.add_message(InfoMessage(OTR_START_TRUSTED % format_dict)) - else: - format_dict['our_fpr'] = self.user.getPrivkey() - format_dict['remote_fpr'] = self.getCurrentKey() - tab.add_message( - InfoMessage(OTR_TUTORIAL % format_dict), - ) - tab.add_message( - InfoMessage(OTR_START_UNTRUSTED % format_dict), - ) - hl(tab) - - log.debug('Set encryption state of %s to %s', self.peer, - states[newstate]) - super(PoezioContext, self).setState(newstate) - if tab: - self.core.refresh_window() - self.core.doupdate() - - -class PoezioAccount(Account): - """ - OTR Account, keeps track of a specific account (ours) - - Redefines the load/save methods from potr.context.Account - """ - - def __init__(self, jid, key_dir): - super(PoezioAccount, self).__init__(jid, 'xmpp', 0) - self.key_dir = os.path.join(key_dir, jid) - - def load_privkey(self): - try: - with open(self.key_dir + '.key3', 'rb') as keyfile: - return potr.crypt.PK.parsePrivateKey(keyfile.read())[0] - except: - log.error('Error in load_privkey', exc_info=True) - - def drop_privkey(self): - try: - os.remove(self.key_dir + '.key3') - except: - log.exception('Error in drop_privkey (removing %s)', - self.key_dir + '.key3') - self.privkey = None - - def save_privkey(self): - try: - with open(self.key_dir + '.key3', 'xb') as keyfile: - keyfile.write(self.getPrivkey().serializePrivateKey()) - except: - log.error('Error in save_privkey', exc_info=True) - - def load_trusts(self): - try: - with open(self.key_dir + '.fpr', 'r') as fpr_fd: - for line in fpr_fd: - ctx, acc, proto, fpr, trust = line[:-1].split('\t') - - if acc != self.name or proto != 'xmpp': - continue - try: - jid = JID(ctx).bare - except InvalidJID: - continue - self.setTrust(jid, fpr, trust) - except: - log.error('Error in load_trusts', exc_info=True) - - def save_trusts(self): - try: - with open(self.key_dir + '.fpr', 'w') as fpr_fd: - for uid, trusts in self.trusts.items(): - for fpr, trustVal in trusts.items(): - fpr_fd.write('\t'.join((uid, self.name, 'xmpp', fpr, - trustVal))) - fpr_fd.write('\n') - except: - log.exception('Error in save_trusts', exc_info=True) - - saveTrusts = save_trusts - loadTrusts = load_trusts - loadPrivkey = load_privkey - savePrivkey = save_privkey - - -states = { - STATE_PLAINTEXT: 'plaintext', - STATE_ENCRYPTED: 'encrypted', - STATE_FINISHED: 'finished', -} - - -class Plugin(BasePlugin): - def init(self): - # set the default values from the config - keys_dir = self.config.get('keys_dir', '') - otr_dir = Path( - keys_dir).expanduser() if keys_dir else xdg.DATA_HOME / 'otr' - try: - otr_dir.mkdir(parents=True, exist_ok=True) - except OSError as e: - self.api.information( - 'The OTR-specific folder could not ' - 'be created: %s. Poezio will be unable ' - 'to save keys and trusts' % e, 'OTR') - - except Exception as e: - self.api.information( - 'The OTR-specific folder could not ' - 'be created. Poezio will be unable ' - 'to save keys and trusts', 'OTR') - - self.api.add_event_handler('conversation_msg', - self.on_conversation_msg) - self.api.add_event_handler('private_msg', self.on_conversation_msg) - self.api.add_event_handler('conversation_say_after', - self.on_conversation_say) - self.api.add_event_handler('private_say_after', - self.on_conversation_say) - - StaticConversationTab.add_information_element( - 'otr', self.display_encryption_status) - PrivateTab.add_information_element('otr', - self.display_encryption_status) - - self.core.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:otr:0') - - self.account = PoezioAccount(self.core.xmpp.boundjid.bare, otr_dir) - self.account.load_trusts() - self.contexts = {} - usage = '' - shortdesc = 'Manage an OTR conversation' - desc = ('Manage an OTR conversation.\n' - 'start/refresh: Start or refresh a conversation\n' - 'end: End a conversation\n' - 'fpr: Show the fingerprint of the key of the remote user\n' - 'ourfpr: Show the fingerprint of your own key\n' - 'drop: Remove the current key (FOREVER)\n' - 'trust: Set this key for this contact as trusted\n' - 'untrust: Remove the trust for the key of this contact\n') - smp_usage = ' [question] [answer]' - smp_short = 'Identify a contact' - smp_desc = ( - 'Verify the identify of your contact by using a pre-defined secret.\n' - 'abort: Abort an ongoing verification\n' - 'ask: Start a verification, with a question or not\n' - 'answer: Finish a verification\n') - - self.api.add_tab_command( - StaticConversationTab, - 'otrsmp', - self.command_smp, - help=smp_desc, - usage=smp_usage, - short=smp_short, - completion=self.completion_smp) - self.api.add_tab_command( - PrivateTab, - 'otrsmp', - self.command_smp, - help=smp_desc, - usage=smp_usage, - short=smp_short, - completion=self.completion_smp) - - self.api.add_tab_command( - StaticConversationTab, - 'otr', - self.command_otr, - help=desc, - usage=usage, - short=shortdesc, - completion=self.completion_otr) - self.api.add_tab_command( - PrivateTab, - 'otr', - self.command_otr, - help=desc, - usage=usage, - short=shortdesc, - completion=self.completion_otr) - - def cleanup(self): - for context in self.contexts.values(): - context.disconnect() - - self.core.xmpp.plugin['xep_0030'].del_feature(feature='urn:xmpp:otr:0') - - StaticConversationTab.remove_information_element('otr') - PrivateTab.remove_information_element('otr') - - def get_context(self, jid): - """ - Retrieve or create an OTR context - """ - jid = JID(jid) - if jid.full not in self.contexts: - flags = POLICY_FLAGS.copy() - require = self.config.get_by_tabname( - 'require_encryption', jid.bare, default=False) - flags['REQUIRE_ENCRYPTION'] = require - logging_policy = self.config.get_by_tabname( - 'log', jid.bare, default=False) - self.contexts[jid.full] = PoezioContext(self.account, jid.full, - self.core.xmpp, self.core) - self.contexts[jid.full].log = 1 if logging_policy else 0 - self.contexts[jid.full].flags = flags - return self.contexts[jid.full] - - def on_conversation_msg(self, msg, tab): - """ - Message received - """ - if msg['from'].bare == self.core.xmpp.boundjid.bare: - return - format_dict = { - 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), - 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'jid': msg['from'] - } - try: - ctx = self.get_context(msg['from']) - txt, tlvs = ctx.receiveMessage(msg["body"].encode('utf-8')) - - # SMP - if tlvs: - self.handle_tlvs(tlvs, ctx, tab, format_dict) - except UnencryptedMessage as err: - # received an unencrypted message inside an OTR session - self.unencrypted_message_received(err, ctx, msg, tab, format_dict) - self.otr_start(tab, tab.name, format_dict) - return - except NotOTRMessage as err: - # ignore non-otr messages - # if we expected an OTR message, we would have - # got an UnencryptedMesssage - # but do an additional check because of a bug with potr and py3k - if ctx.state != STATE_PLAINTEXT or ctx.getPolicy( - 'REQUIRE_ENCRYPTION'): - self.unencrypted_message_received(err, ctx, msg, tab, - format_dict) - self.otr_start(tab, tab.name, format_dict) - return - except ErrorReceived as err: - # Received an OTR error - proto_error = err.args[0].error # pylint: disable=no-member - format_dict['err'] = proto_error.decode('utf-8', errors='replace') - tab.add_message(InfoMessage(OTR_ERROR % format_dict)) - del msg['body'] - del msg['html'] - hl(tab) - self.core.refresh_window() - return - except NotEncryptedError as err: - # Encrypted message received, but unreadable as we do not have - # an OTR session in place. - text = MESSAGE_UNREADABLE % format_dict - tab.add_message(InfoMessage(text)) - hl(tab) - del msg['body'] - del msg['html'] - self.core.refresh_window() - return - except crypt.InvalidParameterError: - # Malformed OTR payload and stuff - text = MESSAGE_INVALID % format_dict - tab.add_message(InfoMessage(text)) - hl(tab) - del msg['body'] - del msg['html'] - self.core.refresh_window() - return - except Exception: - # Unexpected error - import traceback - exc = traceback.format_exc() - format_dict['exc'] = exc - tab.add_message(InfoMessage(POTR_ERROR % format_dict)) - log.error('Unspecified error in the OTR plugin', exc_info=True) - return - # No error, proceed with the message - self.encrypted_message_received(msg, ctx, tab, txt) - - def handle_tlvs(self, tlvs, ctx, tab, format_dict): - """ - If the message had a TLV, it means we received part of an SMP - exchange. - """ - smp1q = get_tlv(tlvs, potr.proto.SMP1QTLV) - smp1 = get_tlv(tlvs, potr.proto.SMP1TLV) - smp2 = get_tlv(tlvs, potr.proto.SMP2TLV) - smp3 = get_tlv(tlvs, potr.proto.SMP3TLV) - smp4 = get_tlv(tlvs, potr.proto.SMP4TLV) - abort = get_tlv(tlvs, potr.proto.SMPABORTTLV) - if abort: - ctx.reset_smp() - tab.add_message(InfoMessage(SMP_ABORTED_PEER % format_dict)) - elif ctx.in_smp and not ctx.smpIsValid(): - ctx.reset_smp() - tab.add_message(InfoMessage(SMP_ABORTED % format_dict)) - elif smp1 or smp1q: - # Received an SMP request (with a question or not) - if smp1q: - try: - question = ' with question: \x19o' + smp1q.msg.decode( - 'utf-8') - except UnicodeDecodeError: - self.api.information( - 'The peer sent a question but it had a wrong encoding', - 'Error') - question = '' - else: - question = '' - ctx.in_smp = True - # we did not initiate it - ctx.smp_own = False - format_dict['q'] = question - tab.add_message(InfoMessage(SMP_REQUESTED % format_dict)) - elif smp2: - # SMP reply received - if not ctx.in_smp: - ctx.reset_smp() - else: - tab.add_message(InfoMessage(SMP_PROGRESS % format_dict)) - elif smp3 or smp4: - # Type 4 (SMP message 3) or 5 (SMP message 4) TLVs received - # in both cases it is the final message of the SMP exchange - if ctx.smpIsSuccess(): - tab.add_message(InfoMessage(SMP_SUCCESS % format_dict)) - if not ctx.getCurrentTrust(): - tab.add_message(InfoMessage(SMP_RECIPROCATE % format_dict)) - else: - tab.add_message(InfoMessage(SMP_FAIL % format_dict)) - ctx.reset_smp() - hl(tab) - self.core.refresh_window() - - def unencrypted_message_received(self, err, ctx, msg, tab, format_dict): - """ - An unencrypted message was received while we expected it to be - encrypted. Display it with a warning. - """ - format_dict['msg'] = err.args[0].decode('utf-8') - text = MESSAGE_UNENCRYPTED % format_dict - tab.add_message( - Message( - text, - nickname=tab.nick, - jid=msg['from'], - ), - ) - del msg['body'] - del msg['html'] - hl(tab) - self.core.refresh_window() - - def encrypted_message_received(self, msg, ctx, tab, txt): - """ - A properly encrypted message was received, so we add it to the - buffer, and try to format it according to the configuration. - """ - # remove xhtml - del msg['html'] - del msg['body'] - - if not txt: - return - if isinstance(tab, PrivateTab): - user = tab.parent_muc.get_user_by_name(msg['from'].resource) - nick_color = None - else: - user = None - nick_color = get_theme().COLOR_REMOTE_USER - - body = txt.decode() - decode_entities = self.config.get_by_tabname( - 'decode_entities', msg['from'].bare, default=True) - decode_newlines = self.config.get_by_tabname( - 'decode_newlines', msg['from'].bare, default=True) - if self.config.get_by_tabname( - 'decode_xhtml', msg['from'].bare, default=True): - try: - body = xhtml.xhtml_to_poezio_colors(body, force=True) - except Exception: - if decode_entities: - body = html.unescape(body) - if decode_newlines: - body = body.replace('
', '\n').replace('
', '\n') - else: - if decode_entities: - body = html.unescape(body) - if decode_newlines: - body = body.replace('
', '\n').replace('
', '\n') - tab.add_message( - Message( - body, - nickname=tab.nick, - jid=msg['from'], - user=user, - nick_color=nick_color - ), - ) - hl(tab) - self.core.refresh_window() - del msg['body'] - - def find_encrypted_context_with_matching(self, bare_jid): - """ - Find an OTR session from a bare JID. - """ - for ctx in self.contexts: - try: - jid = JID(ctx).bare - except InvalidJID: - continue - if jid == bare_jid and self.contexts[ctx].state == STATE_ENCRYPTED: - return self.contexts[ctx] - return None - - def on_conversation_say(self, msg, tab): - """ - On message sent - """ - name = tab.name - jid = tab.jid - - format_dict = { - 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), - 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'jid': name, - } - - ctx = self.find_encrypted_context_with_matching(jid) - default_ctx = self.get_context(name) - - if ctx is None: - ctx = default_ctx - - if is_relevant(tab) and ctx and ctx.state == STATE_ENCRYPTED: - ctx.sendMessage(0, msg['body'].encode('utf-8')) - if not tab.send_chat_state('active'): - tab.send_chat_state('inactive', always_send=True) - - tab.add_message( - Message( - msg['body'], - nickname=self.core.own_nick or tab.own_nick, - nick_color=get_theme().COLOR_OWN_NICK, - identifier=msg['id'], - jid=self.core.xmpp.boundjid, - ), - ) - # remove everything from the message so that it doesn’t get sent - del msg['body'] - del msg['replace'] - del msg['html'] - elif is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'): - warning_msg = MESSAGE_NOT_SENT % format_dict - tab.add_message(InfoMessage(warning_msg)) - del msg['body'] - del msg['replace'] - del msg['html'] - self.otr_start(tab, name, format_dict) - elif not is_relevant(tab) and ctx and ( - ctx.state == STATE_ENCRYPTED - or ctx.getPolicy('REQUIRE_ENCRYPTION')): - contact = roster[tab.jid.bare] - res = [] - if contact: - res = [resource.jid for resource in contact.resources] - help_msg = '' - if res: - help_msg = TAB_HELP_RESOURCE % ''.join( - ('\n - /message %s' % jid) for jid in res) - format_dict['help'] = help_msg - warning_msg = INCOMPATIBLE_TAB % format_dict - tab.add_message(InfoMessage(warning_msg)) - del msg['body'] - del msg['replace'] - del msg['html'] - - def display_encryption_status(self, jid): - """ - Returns the text to display in the infobar (the OTR status) - """ - context = self.get_context(jid) - try: - bare_jid = JID(jid).bare - except InvalidJID: - bare_jid = '' - if bare_jid == jid and context.state != STATE_ENCRYPTED: - ctx = self.find_encrypted_context_with_matching(jid) - if ctx: - context = ctx - state = states[context.state] - trust = 'trusted' if context.getCurrentTrust() else 'untrusted' - - return ' OTR: %s (%s)' % (state, trust) - - def command_otr(self, arg): - """ - /otr [start|refresh|end|fpr|ourfpr] - """ - args = common.shell_split(arg) - if not args: - return self.core.command.help('otr') - action = args.pop(0) - tab = self.api.current_tab() - name = tab.jid.full - format_dict = { - 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), - 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'normal': '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT), - 'jid': tab.jid.full, - 'bare_jid': tab.jid.bare, - } - - if action == 'end': # close the session - context = self.get_context(name) - context.disconnect() - elif action == 'start' or action == 'refresh': - self.otr_start(tab, name, format_dict) - elif action == 'ourfpr': - format_dict['fpr'] = self.account.getPrivkey() - tab.add_message(InfoMessage(OTR_OWN_FPR % format_dict)) - elif action == 'fpr': - if name in self.contexts: - ctx = self.contexts[name] - if ctx.getCurrentKey() is not None: - format_dict['fpr'] = ctx.getCurrentKey() - tab.add_message(InfoMessage(OTR_REMOTE_FPR % format_dict)) - else: - tab.add_message(InfoMessage(OTR_NO_FPR % format_dict)) - elif action == 'drop': - # drop the privkey (and obviously, end the current conversations before that) - for context in self.contexts.values(): - if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): - context.disconnect() - self.account.drop_privkey() - tab.add_message(InfoMessage(KEY_DROPPED % format_dict)) - elif action == 'trust': - ctx = self.get_context(name) - key = ctx.getCurrentKey() - if key: - fpr = key.cfingerprint() - else: - return - if not ctx.getCurrentTrust(): - format_dict['key'] = key - ctx.setTrust(fpr, 'verified') - self.account.saveTrusts() - tab.add_message(InfoMessage(TRUST_ADDED % format_dict)) - elif action == 'untrust': - ctx = self.get_context(name) - key = ctx.getCurrentKey() - if key: - fpr = key.cfingerprint() - else: - return - if ctx.getCurrentTrust(): - format_dict['key'] = key - ctx.setTrust(fpr, '') - self.account.saveTrusts() - tab.add_message(InfoMessage(TRUST_REMOVED % format_dict)) - self.core.refresh_window() - - def otr_start(self, tab, name, format_dict): - """ - Start an otr conversation with a contact - """ - secs = self.config.get('timeout', 3) - - def notify_otr_timeout(): - tab_name = tab.name - otr = self.find_encrypted_context_with_matching(tab_name) - if otr.state != STATE_ENCRYPTED: - format_dict['secs'] = secs - text = OTR_NOT_ENABLED % format_dict - tab.add_message(InfoMessage(text)) - self.core.refresh_window() - - if secs > 0: - event = self.api.create_delayed_event(secs, notify_otr_timeout) - self.api.add_timed_event(event) - body = self.get_context(name).sendMessage(0, b'?OTRv?').decode() - self.core.xmpp.send_message(mto=name, mtype='chat', mbody=body) - tab.add_message(InfoMessage(OTR_REQUEST % format_dict)) - - @staticmethod - def completion_otr(the_input): - """ - Completion for /otr - """ - comp = ['start', 'fpr', 'ourfpr', 'refresh', 'end', 'trust', 'untrust'] - return Completion(the_input.new_completion, comp, 1, quotify=False) - - @command_args_parser.quoted(1, 2) - def command_smp(self, args): - """ - /otrsmp [question] [secret] - """ - if args is None or not args: - return self.core.command.help('otrsmp') - length = len(args) - action = args.pop(0) - if length == 2: - question = None - secret = args.pop(0).encode('utf-8') - elif length == 3: - question = args.pop(0).encode('utf-8') - secret = args.pop(0).encode('utf-8') - else: - question = secret = None - - tab = self.api.current_tab() - name = tab.jid.full - format_dict = { - 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), - 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'jid': tab.jid.full, - 'bare_jid': tab.jid.bare, - } - - ctx = self.get_context(name) - if ctx.state != STATE_ENCRYPTED: - self.api.information('The current conversation is not encrypted', - 'Error') - return - - if action == 'ask': - ctx.in_smp = True - ctx.smp_own = True - if question: - ctx.smpInit(secret, question) - else: - ctx.smpInit(secret) - tab.add_message(InfoMessage(SMP_INITIATED % format_dict)) - elif action == 'answer': - ctx.smpGotSecret(secret) - elif action == 'abort': - if ctx.in_smp: - ctx.smpAbort() - tab.add_message(InfoMessage(SMP_ABORTED % format_dict)) - self.core.refresh_window() - - @staticmethod - def completion_smp(the_input): - """Completion for /otrsmp""" - if the_input.get_argument_position() == 1: - return Completion( - the_input.new_completion, ['ask', 'answer', 'abort'], - 1, - quotify=False) - - -def get_tlv(tlvs, cls): - """Find the instance of a class in a list""" - for tlv in tlvs: - if isinstance(tlv, cls): - return tlv - - -def is_relevant(tab): - """Check if a tab should be concerned with OTR""" - return isinstance(tab, (StaticConversationTab, PrivateTab)) diff --git a/desktop/.local/share/poezio/plugins/pacokick.py b/desktop/.local/share/poezio/plugins/pacokick.py deleted file mode 100644 index 4ec9d0a..0000000 --- a/desktop/.local/share/poezio/plugins/pacokick.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -This plugin adds a :term:`/pacokick` command, which is a random kick. - -Usage ------ - -.. glossary:: - - /pacokick - - Run the command in a room where you are a moderator to - kick someone randomly. -""" - -from random import choice -from poezio.tabs import MucTab - -from poezio.plugin import BasePlugin - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'pacokick', - self.command_kick, - usage='', - help='Kick a random user.', - short='Kick a random user') - - def command_kick(self, arg): - tab = self.api.current_tab() - if isinstance(tab, MucTab): - kickable = list( - filter(lambda x: x.affiliation in ('none', 'member'), - tab.users)) - if kickable: - to_kick = choice(kickable) - if to_kick: - to_kick = to_kick.nick - tab.command_kick(to_kick + ' ' + arg) - else: - self.api.information('No one to kick :(', 'Info') diff --git a/desktop/.local/share/poezio/plugins/ping.py b/desktop/.local/share/poezio/plugins/ping.py deleted file mode 100644 index cc987bf..0000000 --- a/desktop/.local/share/poezio/plugins/ping.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -This plugin allows you to ping an entity. - -Command -------- - -.. glossary:: - - /ping - **Usage (globally):** ``/ping `` - - **Usage (in a MUC tab):** ``/ping `` - - **Usage (in a conversation tab):** ``/ping [jid]`` - - Globally, you can do ``/ping jid@example.com`` to get a ping. - - In a MUC, you can either do it to a JID or a nick (``/ping nick`` or ``/ping - jid@example.com``). - - In a private or a direct conversation, you can do ``/ping`` to ping - the current interlocutor. -""" -import asyncio - -from slixmpp import InvalidJID, JID -from slixmpp.exceptions import IqTimeout -from poezio.decorators import command_args_parser -from poezio.plugin import BasePlugin -from poezio.roster import roster -from poezio.contact import Contact, Resource -from poezio.core.structs import Completion -from poezio import tabs -import time - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'ping', - self.command_ping, - usage='', - help='Send an XMPP ping to jid (see XEP-0199).', - short='Send a ping', - completion=self.completion_ping) - self.api.add_tab_command( - tabs.MucTab, - 'ping', - self.command_muc_ping, - usage='', - help='Send an XMPP ping to jid or nick (see XEP-0199).', - short='Send a ping.', - completion=self.completion_muc_ping) - self.api.add_tab_command( - tabs.RosterInfoTab, - 'ping', - self.command_roster_ping, - usage='', - help='Send an XMPP ping to jid (see XEP-0199).', - short='Send a ping.', - completion=self.completion_ping) - for _class in (tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab): - self.api.add_tab_command( - _class, - 'ping', - self.command_private_ping, - usage='[jid]', - help= - 'Send an XMPP ping to the current interlocutor or the given JID.', - short='Send a ping', - completion=self.completion_ping) - - @command_args_parser.raw - async def command_ping(self, arg): - if not arg: - return self.core.command.help('ping') - try: - jid = JID(arg) - except InvalidJID: - return self.api.information('Invalid JID: %s' % arg, 'Error') - start = time.time() - - try: - iq = await self.core.xmpp.plugin['xep_0199'].send_ping( - jid=jid, timeout=10 - ) - delay = time.time() - start - error = False - reply = '' - if iq['type'] == 'error': - error_condition = iq['error']['condition'] - reply = error_condition - # These IQ errors are not ping errors: - # 'service-unavailable': official "not supported" response as of RFC6120 (§8.4) and XEP-0199 (§4.1) - # 'feature-not-implemented': inoffcial not-supported response from many clients - if error_condition not in ('service-unavailable', - 'feature-not-implemented'): - error = True - error_text = iq['error']['text'] - if error_text: - reply = '%s: %s' % (error_condition, error_text) - if error: - message = '%s did not respond to ping: %s' % (jid, reply) - else: - reply = ' (%s)' % reply if reply else '' - message = '%s responded to ping after %ss%s' % ( - jid, round(delay, 4), reply) - self.api.information(message, 'Info') - except IqTimeout: - self.api.information( - '%s did not respond to ping after 10s: timeout' % jid, - 'Info' - ) - - def completion_muc_ping(self, the_input): - users = [user.nick for user in self.api.current_tab().users] - l = self.resources() - users.extend(l) - return Completion(the_input.auto_completion, users, '', quotify=False) - - @command_args_parser.raw - def command_private_ping(self, arg): - jid = arg - if not arg: - jid = self.api.current_tab().jid - asyncio.create_task( - self.command_ping(jid) - ) - - @command_args_parser.raw - def command_muc_ping(self, arg): - if not arg: - return - user = self.api.current_tab().get_user_by_name(arg) - if user: - jid = self.api.current_tab().jid - jid.resource = user.nick - else: - try: - jid = JID(arg) - except InvalidJID: - return self.api.information('Invalid JID: %s' % arg, 'Error') - asyncio.create_task( - self.command_ping(jid.full) - ) - - @command_args_parser.raw - def command_roster_ping(self, arg): - if arg: - jid = arg - else: - current = self.api.current_tab().selected_row - if isinstance(current, Resource): - jid = current.jid - elif isinstance(current, Contact): - res = current.get_highest_priority_resource() - if res is not None: - jid =res.jid - asyncio.create_task( - self.command_ping(jid) - ) - - def resources(self): - l = [] - for contact in roster.get_contacts(): - for resource in contact.resources: - l.append(resource.jid) - return l - - def completion_ping(self, the_input): - return Completion( - the_input.auto_completion, self.resources(), '', quotify=False) diff --git a/desktop/.local/share/poezio/plugins/pipe_cmd.py b/desktop/.local/share/poezio/plugins/pipe_cmd.py deleted file mode 100644 index 28d6f87..0000000 --- a/desktop/.local/share/poezio/plugins/pipe_cmd.py +++ /dev/null @@ -1,68 +0,0 @@ -""" - -This plugins allows commands to be sent to poezio via a named pipe. - -You can run the same commands that you would in the poezio input -(e.g. ``echo '/message toto@example.tld Hi' >> /tmp/poezio.fifo``). - -Configuration -------------- - -.. glossary:: - :sorted: - - pipename - **Default:** :file:`/tmp/poezio.fifo` - - The path to the fifo which will receive commands. - -""" - -from poezio.plugin import BasePlugin -import os -import stat -import logging -import asyncio - -log = logging.getLogger(__name__) - -PIPENAME = "/tmp/poezio.fifo" - - -class Plugin(BasePlugin): - def init(self): - self.stop = False - - self.pipename = self.config.get("pipename", PIPENAME) - - if not os.path.exists(self.pipename): - os.mkfifo(self.pipename) - - if not stat.S_ISFIFO(os.stat(self.pipename).st_mode): - raise TypeError("File %s is not a fifo file" % self.pipename) - - self.fd = os.open(self.pipename, os.O_RDONLY | os.O_NONBLOCK) - - self.data = b"" - asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo) - - def read_from_fifo(self): - data = os.read(self.fd, 512) - if not data: - # EOF, close the fifo. And reopen it - asyncio.get_event_loop().remove_reader(self.fd) - os.close(self.fd) - self.fd = os.open(self.pipename, os.O_RDONLY | os.O_NONBLOCK) - asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo) - self.data = b'' - else: - self.data += data - l = self.data.split(b'\n', 1) - if len(l) == 2: - line, self.data = l - log.debug("run: %s" % (line.decode().strip())) - self.api.run_command(line.decode().strip()) - - def cleanup(self): - asyncio.get_event_loop().remove_reader(self.fd) - os.close(self.fd) diff --git a/desktop/.local/share/poezio/plugins/pointpoint.py b/desktop/.local/share/poezio/plugins/pointpoint.py deleted file mode 100644 index dd1ae1c..0000000 --- a/desktop/.local/share/poezio/plugins/pointpoint.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This plugin adds a command (that can be bound to a key) that adds a random -number of dots in the input, making you look depressed, or overly thinking... - -Installation ------------- -Load the plugin.:: - - /load pointpoint - -Then use the command: :: - - /pointpoint - -But since the goal is to be able to add the dots while typing a message, -entering a command is not really useful. To be useful, this plugin needs to -be used through a bound key, for example like this: :: - - /bind M-. _exc_pointpoint - -You just need to press Alt+. and this will insert dots in your message. - -Command -------- - -.. glossary:: - - /pointpoint - **Usage:** ``/pointpoint`` - - … - - -""" - -from poezio.plugin import BasePlugin -from random import randrange - - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'pointpoint', - self.command_pointpoint, - help='Insert a random number of dots in the input') - - def command_pointpoint(self, args): - for i in range(randrange(8, 25)): - self.core.current_tab().input.do_command(".") diff --git a/desktop/.local/share/poezio/plugins/qr.py b/desktop/.local/share/poezio/plugins/qr.py deleted file mode 100755 index 735c300..0000000 --- a/desktop/.local/share/poezio/plugins/qr.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 - -import io -import logging -import qrcode - -from typing import Dict, Callable - -from slixmpp import JID, InvalidJID - -from poezio import windows -from poezio.tabs import Tab -from poezio.core.structs import Command -from poezio.decorators import command_args_parser -from poezio.plugin import BasePlugin -from poezio.theming import get_theme, to_curses_attr -from poezio.windows.base_wins import Win - -log = logging.getLogger(__name__) - -class QrWindow(Win): - __slots__ = ('qr', 'invert', 'inverted') - - str_invert = " Invert " - str_close = " Close " - - def __init__(self, qr: str) -> None: - self.qr = qr - self.invert = True - self.inverted = True - - def refresh(self) -> None: - self._win.erase() - # draw QR code - code = qrcode.QRCode() - code.add_data(self.qr) - out = io.StringIO() - code.print_ascii(out, invert=self.inverted) - self.addstr(" " + self.qr + "\n") - self.addstr(out.getvalue(), to_curses_attr((15, 0))) - self.addstr(" ") - - col = to_curses_attr(get_theme().COLOR_TAB_NORMAL) - - if self.invert: - self.addstr(self.str_invert, col) - else: - self.addstr(self.str_invert) - - self.addstr(" ") - - if self.invert: - self.addstr(self.str_close) - else: - self.addstr(self.str_close, col) - - self._refresh() - - def toggle_choice(self) -> None: - self.invert = not self.invert - - def engage(self) -> bool: - if self.invert: - self.inverted = not self.inverted - return False - else: - return True - -class QrTab(Tab): - plugin_commands = {} # type: Dict[str, Command] - plugin_keys = {} # type: Dict[str, Callable] - - def __init__(self, core, qr): - Tab.__init__(self, core) - self.state = 'highlight' - self.text = qr - self._name = qr - self.topic_win = windows.Topic() - self.topic_win.set_message(qr) - self.qr_win = QrWindow(qr) - self.help_win = windows.HelpText( - "Choose with arrow keys and press enter") - self.key_func['^I'] = self.toggle_choice - self.key_func[' '] = self.toggle_choice - self.key_func['KEY_LEFT'] = self.toggle_choice - self.key_func['KEY_RIGHT'] = self.toggle_choice - self.key_func['^M'] = self.engage - self.resize() - self.update_commands() - self.update_keys() - - def resize(self): - self.need_resize = False - self.topic_win.resize(1, self.width, 0, 0) - self.qr_win.resize(self.height-3, self.width, 1, 0) - self.help_win.resize(1, self.width, self.height-1, 0) - - def refresh(self): - if self.need_resize: - self.resize() - log.debug(' TAB Refresh: %s', self.__class__.__name__) - self.refresh_tab_win() - self.info_win.refresh() - self.topic_win.refresh() - self.qr_win.refresh() - self.help_win.refresh() - - def on_input(self, key, raw): - if not raw and key in self.key_func: - return self.key_func[key]() - - def toggle_choice(self): - log.debug(' TAB toggle_choice: %s', self.__class__.__name__) - self.qr_win.toggle_choice() - self.refresh() - self.core.doupdate() - - def engage(self): - log.debug(' TAB engage: %s', self.__class__.__name__) - if self.qr_win.engage(): - self.core.close_tab(self) - else: - self.refresh() - self.core.doupdate() - -class Plugin(BasePlugin): - def init(self): - self.api.add_command( - 'qr', - self.command_qr, - usage='', - short='Display a QR code', - help='Display a QR code of in a new tab') - self.api.add_command( - 'invitation', - self.command_invite, - usage='[]', - short='Invite a user', - help='Generate a XEP-0401 invitation on your server or on and display a QR code') - - def command_qr(self, msg): - t = QrTab(self.core, msg) - self.core.add_tab(t, True) - self.core.doupdate() - - def on_next(self, iq, adhoc_session): - status = iq['command']['status'] - xform = iq.xml.find( - '{http://jabber.org/protocol/commands}command/{jabber:x:data}x') - if xform is not None: - form = self.core.xmpp.plugin['xep_0004'].build_form(xform) - else: - form = None - uri = None - if status == 'completed' and form: - for field in form: - log.debug(' field: %s -> %s', field['var'], field['value']) - if field['var'] == 'landing-url' and field['value']: - uri = field.get_value(convert=False) - if field['var'] == 'uri' and field['value'] and uri is None: - uri = field.get_value(convert=False) - if uri: - t = QrTab(self.core, uri) - self.core.add_tab(t, True) - self.core.doupdate() - else: - self.core.handler.next_adhoc_step(iq, adhoc_session) - - - @command_args_parser.quoted(0, 1, defaults=[]) - def command_invite(self, args): - server = self.core.xmpp.boundjid.domain - if len(args) > 0: - try: - server = JID(args[0]) - except InvalidJID: - self.api.information(f'Invalid JID: {args[0]}', 'Error') - return - session = { - 'next' : self.on_next, - 'error': self.core.handler.adhoc_error - } - self.core.xmpp.plugin['xep_0050'].start_command(server, 'urn:xmpp:invite#invite', session) - diff --git a/desktop/.local/share/poezio/plugins/quote.py b/desktop/.local/share/poezio/plugins/quote.py deleted file mode 100644 index d7bc1e2..0000000 --- a/desktop/.local/share/poezio/plugins/quote.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -This plugin allows you to quote messages easily. - -Usage ------ - -.. glossary:: - - /quote - **Usage:** ``/quote `` - - The message must exist. You can use autocompletion to get the message - you want to quote easily. - - Example: - - .. code-block:: none - - /quote "Pouet" - - If the message "Pouet" exists, it will be put in the input. If not you - will get a warning. - -Options -------- - -.. glossary:: - :sorted: - - before_quote - - **Default value:** ``[empty]`` - - Text to insert before the quote. ``%(nick)s`` and ``%(time)s`` can - be used to insert the nick of the user who sent the message or the - time of the message. - - after_quote - - **Default value:** ``[empty]`` - - Text to insert after the quote. ``%(nick)s`` and ``%(time)s`` can - be used to insert the nick of the user who sent the message or the - time of the message. -""" - -from poezio.core.structs import Completion -from poezio.ui.types import Message -from poezio.plugin import BasePlugin -from poezio.xhtml import clean_text -from poezio.theming import get_theme -from poezio import common -from poezio import tabs - -import logging -log = logging.getLogger(__name__) - - -class Plugin(BasePlugin): - def init(self): - for _class in (tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab): - self.api.add_tab_command( - _class, - 'quote', - self.command_quote, - usage='', - help='Quote the message you typed if it exists.', - short='Quote a message.', - completion=self.completion_quote) - - def command_quote(self, args): - args = common.shell_split(args) - if len(args) == 1: - message = args[-1] - else: - return self.api.run_command('/help quote') - message = self.find_message(message) - if message: - str_time = message.time.strftime(get_theme().SHORT_TIME_FORMAT) - before = self.config.get('before_quote', '') % { - 'nick': message.nickname or '', - 'time': str_time, - } - after = self.config.get('after_quote', '') % { - 'nick': message.nickname or '', - 'time': str_time, - } - self.core.insert_input_text( - '%(before)s%(quote)s%(after)s' % { - 'before': before.replace('\\n', '\n').replace('[SP]', ' '), - 'quote': clean_text(message.txt), - 'after': after.replace('\\n', '\n').replace('[SP]', ' ') - }) - else: - self.api.information('No message found', 'Warning') - - def find_message(self, txt): - messages = self.api.get_conversation_messages() - if not messages: - return None - for message in messages[::-1]: - if isinstance(message, Message) and clean_text(message.txt) == txt: - return message - return None - - def completion_quote(self, the_input): - def message_match(msg): - return input_message.lower() in clean_text(msg.txt).lower() - - messages = self.api.get_conversation_messages() - if not messages: - return - text = the_input.get_text() - args = common.shell_split(text) - if not text.endswith(' '): - input_message = args[-1] - messages = list(filter(message_match, messages)) - elif len(args) > 1: - return False - return Completion( - the_input.auto_completion, - [clean_text(msg.txt) for msg in messages[::-1] if isinstance(msg, Message)], - '' - ) diff --git a/desktop/.local/share/poezio/plugins/rainbow.py b/desktop/.local/share/poezio/plugins/rainbow.py deleted file mode 100644 index e598708..0000000 --- a/desktop/.local/share/poezio/plugins/rainbow.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -This plugin colors each character of a message with a random color. - -.. note:: As ticket `#3273`_ puts it, the final output is closer to vomit than a rainbow. - -Usage ------ - -.. glossary:: - - /rainbow - - Say something in a Chat tab. - -.. note:: Can create fun things when used with :ref:`The figlet plugin `. - -.. _#3273: https://lab.louiz.org/poezio/poezio/-/issues/3273 -""" -from poezio.plugin import BasePlugin -from poezio import xhtml -import random - -possible_colors = list(range(256)) -# remove the colors that are almost white or almost black -for col in [ - 16, 232, 233, 234, 235, 236, 237, 15, 231, 255, 254, 253, 252, 251 -]: - possible_colors.remove(col) - - -def rand_color(): - return '\x19%s}' % (random.choice(possible_colors), ) - - -class Plugin(BasePlugin): - def init(self): - self.api.add_event_handler('muc_say', self.rainbowize) - self.api.add_event_handler('private_say', self.rainbowize) - self.api.add_event_handler('conversation_say', self.rainbowize) - - def rainbowize(self, msg, tab): - msg['body'] = ''.join([ - '%s%s' % ( - rand_color(), - char, - ) for char in xhtml.clean_text(msg['body']) - ]) diff --git a/desktop/.local/share/poezio/plugins/random_nick.py b/desktop/.local/share/poezio/plugins/random_nick.py deleted file mode 100644 index 0f3f027..0000000 --- a/desktop/.local/share/poezio/plugins/random_nick.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -This plugin makes you have a random nick when joining a chatroom. - -Usage ------ - -To have a random nick, just join a room with “RANDOM” as your nick. It will -automatically be changed to something random, for example: :: - - /join coucou@conference.example.com/RANDOM - -""" - -from poezio.plugin import BasePlugin -from random import choice - - -class Plugin(BasePlugin): - def init(self): - self.api.add_event_handler('joining_muc', self.change_nick_to_random) - self.api.add_event_handler('changing_nick', self.change_nick_to_random) - - def change_nick_to_random(self, presence): - to = presence["to"] - if to.resource == 'RANDOM': - to.resource = gen_nick(3) - presence["to"] = to - - -s = ["i", "ou", "ou", "on", "a", "o", "u", "i"] -c = [ - "b", "c", "d", "f", "g", "h", "j", "k", "m", "l", "n", "p", "r", "s", "t", - "v", "z" -] - - -def gen_nick(size): - res = '' - for _ in range(size): - res += '%s%s' % (choice(c), choice(s)) - return res diff --git a/desktop/.local/share/poezio/plugins/regex_admin.py b/desktop/.local/share/poezio/plugins/regex_admin.py deleted file mode 100644 index befbcac..0000000 --- a/desktop/.local/share/poezio/plugins/regex_admin.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -This plugins adds a :term:`/rkick` and a :term:`/rban` command, -in order to kick/ban according to a regex on a nick. - -Commands --------- - -Those commands take a regular expression (as defined in the -`re module documentation`_) as a parameter. - -.. glossary:: - :sorted: - - /rkick - **Usage:** ``/rkick `` - - Kick a participant using a regex. - - - /rban - **Usage:** ``/rban `` - - Ban a participant using a regex. - -.. _re module documentation: http://docs.python.org/3/library/re.html -""" - -from poezio.plugin import BasePlugin -from poezio.tabs import MucTab - -import re - - -class Plugin(BasePlugin): - def init(self): - self.api.add_tab_command( - MucTab, - 'rkick', - self.command_rkick, - usage='', - help='Kick occupants of a room according to a regex', - short='Regex Kick') - - self.api.add_tab_command( - MucTab, - 'rban', - self.command_rban, - usage='', - help='Ban occupants of a room according to a regex', - short='Regex Ban') - - def return_users(self, users, regex): - try: - reg = re.compile(regex) - except: - return [] - - ret = [] - for user in users: - if reg.match(user.nick): - ret.append(user) - - return ret - - def command_rban(self, regex): - tab = self.api.current_tab() - users = self.return_users(tab.users, regex) - for user in users: - tab.command_ban(user.nick) - - def command_rkick(self, regex): - tab = self.api.current_tab() - users = self.return_users(tab.users, regex) - for user in users: - tab.command_kick(user.nick) diff --git a/desktop/.local/share/poezio/plugins/reminder.py b/desktop/.local/share/poezio/plugins/reminder.py deleted file mode 100644 index e5ed96c..0000000 --- a/desktop/.local/share/poezio/plugins/reminder.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Usage ------ - -This plugin defines three new global commands: :term:`/remind`, -:term:`/done`, and :term:`/tasks`. - -.. glossary:: - - /remind - **Usage:** ``/remind