From 841b1fb25e7e9e5d3e7720cbb0499dbeb777412b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Jan 2025 19:15:37 +0100 Subject: [PATCH] cq: Format with black and isort --- .ci/deploy.py | 93 +++--- acronyms_expander/acronyms_expander.py | 74 ++--- acronyms_expander/gtk/config.py | 33 +- anti_spam/anti_spam.py | 30 +- anti_spam/config_dialog.py | 146 ++++---- anti_spam/modules/anti_spam.py | 90 ++--- clients_icons/clients.py | 294 +++++++++-------- clients_icons/clients_icons.py | 45 ++- clients_icons/config_dialog.py | 33 +- length_notifier/config_dialog.py | 85 ++--- length_notifier/length_notifier.py | 108 +++--- message_box_size/config_dialog.py | 37 ++- message_box_size/msg_box_size.py | 14 +- now_listen/gtk/config.py | 32 +- now_listen/now_listen.py | 52 ++- openpgp/backend/gpgme.py | 51 +-- openpgp/backend/pygpg.py | 70 ++-- openpgp/backend/sql.py | 39 ++- openpgp/backend/util.py | 9 +- openpgp/gtk/key.py | 93 +++--- openpgp/gtk/wizard.py | 63 ++-- openpgp/modules/key_store.py | 72 ++-- openpgp/modules/openpgp.py | 165 +++++----- openpgp/modules/util.py | 25 +- openpgp/pgpplugin.py | 103 +++--- pgp/backend/python_gnupg.py | 70 ++-- pgp/backend/store.py | 92 +++--- pgp/exceptions.py | 3 + pgp/gtk/config.py | 37 +-- pgp/gtk/key.py | 57 ++-- pgp/modules/events.py | 9 +- pgp/modules/pgp_legacy.py | 195 ++++++----- pgp/modules/util.py | 18 +- pgp/plugin.py | 111 ++++--- plugins_translations/__init__.py | 4 +- plugins_translations/plugins_translations.py | 38 +-- quick_replies/gtk/config.py | 31 +- quick_replies/plugin.py | 72 ++-- quick_replies/quick_replies.py | 6 +- scripts/build_repository.py | 70 ++-- scripts/update_translations.py | 102 +++--- triggers/gtk/config.py | 330 +++++++++---------- triggers/triggers.py | 195 +++++------ triggers/util.py | 5 +- 44 files changed, 1641 insertions(+), 1660 deletions(-) diff --git a/.ci/deploy.py b/.ci/deploy.py index 1ce78bb..f85afa2 100644 --- a/.ci/deploy.py +++ b/.ci/deploy.py @@ -1,40 +1,40 @@ -from typing import Any, Iterator +from typing import Any +from typing import Iterator -import os -import json import functools -from shutil import make_archive +import json +import os from ftplib import FTP_TLS from pathlib import Path +from shutil import make_archive import requests from rich.console import Console - PackageT = tuple[dict[str, Any], Path] ManifestT = dict[str, Any] PackageIndexT = dict[str, Any] -FTP_URL = 'panoramix.gajim.org' -FTP_USER = os.environ['FTP_USER'] -FTP_PASS = os.environ['FTP_PASS'] +FTP_URL = "panoramix.gajim.org" +FTP_USER = os.environ["FTP_USER"] +FTP_PASS = os.environ["FTP_PASS"] -REPOSITORY_FOLDER = 'plugins/master' -PACKAGE_INDEX_URL = 'https://ftp.gajim.org/plugins/master/package_index.json' +REPOSITORY_FOLDER = "plugins/master" +PACKAGE_INDEX_URL = "https://ftp.gajim.org/plugins/master/package_index.json" REPO_ROOT = Path(__file__).parent.parent -BUILD_PATH = REPO_ROOT / 'build' +BUILD_PATH = REPO_ROOT / "build" REQUIRED_KEYS = { - 'authors', - 'description', - 'homepage', - 'name', - 'platforms', - 'requirements', - 'short_name', - 'version' + "authors", + "description", + "homepage", + "name", + "platforms", + "requirements", + "short_name", + "version", } @@ -45,11 +45,12 @@ def ftp_connection(func: Any) -> Any: @functools.wraps(func) def func_wrapper(*args: Any) -> None: ftp = FTP_TLS(FTP_URL, FTP_USER, FTP_PASS) - console.print('Successfully connected to', FTP_URL) + console.print("Successfully connected to", FTP_URL) func(ftp, *args) ftp.quit() - console.print('Quit') + console.print("Quit") return + return func_wrapper @@ -59,7 +60,7 @@ def is_manifest_valid(manifest: ManifestT) -> bool: def download_package_index() -> ManifestT: - console.print('Download package index') + console.print("Download package index") r = requests.get(PACKAGE_INDEX_URL) if r.status_code == 404: return {} @@ -70,7 +71,7 @@ def download_package_index() -> ManifestT: def iter_manifests() -> Iterator[PackageT]: - for path in REPO_ROOT.rglob('plugin-manifest.json'): + for path in REPO_ROOT.rglob("plugin-manifest.json"): with path.open() as f: manifest = json.load(f) yield manifest, path.parent @@ -80,43 +81,41 @@ def find_plugins_to_publish(index: PackageIndexT) -> list[PackageT]: packages_to_publish: list[PackageT] = [] for manifest, path in iter_manifests(): if not is_manifest_valid(manifest): - exit('Invalid manifest found') + exit("Invalid manifest found") - short_name = manifest['short_name'] - version = manifest['version'] + short_name = manifest["short_name"] + version = manifest["version"] try: - index['plugins'][short_name][version] + index["plugins"][short_name][version] except KeyError: packages_to_publish.append((manifest, path)) - console.print('Found package to publish:', path.stem) + console.print("Found package to publish:", path.stem) return packages_to_publish def get_release_zip_name(manifest: ManifestT) -> str: - short_name = manifest['short_name'] - version = manifest['version'] - return f'{short_name}_{version}' + short_name = manifest["short_name"] + version = manifest["version"] + return f"{short_name}_{version}" def get_dir_list(ftp: FTP_TLS) -> set[str]: return {x[0] for x in ftp.mlsd()} -def upload_file(ftp: FTP_TLS, - filepath: Path) -> None: +def upload_file(ftp: FTP_TLS, filepath: Path) -> None: name = filepath.name - console.print('Upload file', name) - with open(filepath, 'rb') as f: - ftp.storbinary('STOR ' + name, f) + console.print("Upload file", name) + with open(filepath, "rb") as f: + ftp.storbinary("STOR " + name, f) -def create_release_folder(ftp: FTP_TLS, - packages_to_publish: list[PackageT]) -> None: +def create_release_folder(ftp: FTP_TLS, packages_to_publish: list[PackageT]) -> None: - folders = {manifest['short_name'] for manifest, _ in packages_to_publish} + folders = {manifest["short_name"] for manifest, _ in packages_to_publish} dir_list = get_dir_list(ftp) missing_folders = folders - dir_list for folder in missing_folders: @@ -129,26 +128,26 @@ def deploy(ftp: FTP_TLS, packages_to_publish: list[PackageT]) -> None: create_release_folder(ftp, packages_to_publish) for manifest, path in packages_to_publish: - package_name = manifest['short_name'] + package_name = manifest["short_name"] zip_name = get_release_zip_name(manifest) - zip_path = BUILD_PATH / f'{zip_name}.zip' - image_path = path / f'{package_name}.png' + zip_path = BUILD_PATH / f"{zip_name}.zip" + image_path = path / f"{package_name}.png" - make_archive(str(BUILD_PATH / zip_name), 'zip', path) + make_archive(str(BUILD_PATH / zip_name), "zip", path) ftp.cwd(package_name) upload_file(ftp, zip_path) if image_path.exists(): upload_file(ftp, image_path) - ftp.cwd('..') + ftp.cwd("..") - console.print('Deployed', package_name) + console.print("Deployed", package_name) -if __name__ == '__main__': +if __name__ == "__main__": index = download_package_index() packages_to_publish = find_plugins_to_publish(index) if not packages_to_publish: - console.print('No new packages deployed') + console.print("No new packages deployed") else: deploy(packages_to_publish) diff --git a/acronyms_expander/acronyms_expander.py b/acronyms_expander/acronyms_expander.py index 72bac89..bff3d21 100644 --- a/acronyms_expander/acronyms_expander.py +++ b/acronyms_expander/acronyms_expander.py @@ -19,8 +19,8 @@ from __future__ import annotations import json import logging -from pathlib import Path from functools import partial +from pathlib import Path from gi.repository import GLib from gi.repository import GObject @@ -29,28 +29,27 @@ from gi.repository import Gtk from gajim.common import configpaths from gajim.common import types from gajim.common.modules.contacts import GroupchatContact - from gajim.gtk.message_input import MessageInputTextView - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ from acronyms_expander.acronyms import DEFAULT_DATA from acronyms_expander.gtk.config import ConfigDialog -log = logging.getLogger('gajim.p.acronyms') +log = logging.getLogger("gajim.p.acronyms") class AcronymsExpanderPlugin(GajimPlugin): def init(self) -> None: - self.description = _('Replaces acronyms (or other strings) ' - 'with given expansions/substitutes.') + self.description = _( + "Replaces acronyms (or other strings) " "with given expansions/substitutes." + ) self.config_dialog = partial(ConfigDialog, self) self.gui_extension_points = { - 'message_input': (self._connect, None), - 'switch_contact': (self._on_switch_contact, None) + "message_input": (self._connect, None), + "switch_contact": (self._on_switch_contact, None), } - self._invoker = ' ' + self._invoker = " " self._replace_in_progress = False self._signal_id = None @@ -62,42 +61,40 @@ class AcronymsExpanderPlugin(GajimPlugin): @staticmethod def _load_acronyms() -> dict[str, str]: try: - data_path = Path(configpaths.get('PLUGINS_DATA')) + data_path = Path(configpaths.get("PLUGINS_DATA")) except KeyError: # PLUGINS_DATA was added in 1.0.99.1 return DEFAULT_DATA - path = data_path / 'acronyms' / 'acronyms' + path = data_path / "acronyms" / "acronyms" if not path.exists(): return DEFAULT_DATA - with path.open('r') as file: + with path.open("r") as file: acronyms = json.load(file) return acronyms @staticmethod def _save_acronyms(acronyms: dict[str, str]) -> None: try: - data_path = Path(configpaths.get('PLUGINS_DATA')) + data_path = Path(configpaths.get("PLUGINS_DATA")) except KeyError: # PLUGINS_DATA was added in 1.0.99.1 return - path = data_path / 'acronyms' + path = data_path / "acronyms" if not path.exists(): path.mkdir(parents=True) - filepath = path / 'acronyms' - with filepath.open('w') as file: + filepath = path / "acronyms" + with filepath.open("w") as file: json.dump(acronyms, file) def set_acronyms(self, acronyms: dict[str, str]) -> None: self.acronyms = acronyms self._save_acronyms(acronyms) - def _on_buffer_changed(self, - message_input: MessageInputTextView - ) -> None: + def _on_buffer_changed(self, message_input: MessageInputTextView) -> None: if self._contact is None: # If no chat has been activated yet @@ -126,9 +123,8 @@ class AcronymsExpanderPlugin(GajimPlugin): # Get to the start of the last word # word_start_iter = insert_iter.copy() result = insert_iter.backward_search( - self._invoker, - Gtk.TextSearchFlags.VISIBLE_ONLY, - None) + self._invoker, Gtk.TextSearchFlags.VISIBLE_ONLY, None + ) if result is None: word_start_iter = buffer_.get_start_iter() @@ -140,31 +136,30 @@ class AcronymsExpanderPlugin(GajimPlugin): if isinstance(self._contact, GroupchatContact): if last_word in self._contact.get_user_nicknames(): - log.info('Groupchat participant has same nick as acronym') + log.info("Groupchat participant has same nick as acronym") return if self._contact.is_pm_contact: if last_word == self._contact.name: - log.info('Contact name equals acronym') + log.info("Contact name equals acronym") return substitute = self.acronyms.get(last_word) if substitute is None: - log.debug('%s not an acronym', last_word) + log.debug("%s not an acronym", last_word) return - GLib.idle_add(self._replace_text, - buffer_, - word_start_iter, - insert_iter, - substitute) + GLib.idle_add( + self._replace_text, buffer_, word_start_iter, insert_iter, substitute + ) - def _replace_text(self, - buffer_: Gtk.TextBuffer, - start: Gtk.TextIter, - end: Gtk.TextIter, - substitute: str - ) -> None: + def _replace_text( + self, + buffer_: Gtk.TextBuffer, + start: Gtk.TextIter, + end: Gtk.TextIter, + substitute: str, + ) -> None: self._replace_in_progress = True buffer_.delete(start, end) @@ -176,11 +171,12 @@ class AcronymsExpanderPlugin(GajimPlugin): def _connect(self, message_input: MessageInputTextView) -> None: self._message_input = message_input - self._signal_id = message_input.connect('buffer-changed', self._on_buffer_changed) + self._signal_id = message_input.connect( + "buffer-changed", self._on_buffer_changed + ) def deactivate(self) -> None: assert self._message_input is not None assert self._signal_id is not None - if GObject.signal_handler_is_connected( - self._message_input, self._signal_id): + if GObject.signal_handler_is_connected(self._message_input, self._signal_id): self._message_input.disconnect(self._signal_id) diff --git a/acronyms_expander/gtk/config.py b/acronyms_expander/gtk/config.py index e01ecd7..3685f10 100644 --- a/acronyms_expander/gtk/config.py +++ b/acronyms_expander/gtk/config.py @@ -23,24 +23,20 @@ from pathlib import Path from gi.repository import Gtk from gajim.gtk.widgets import GajimAppWindow - -from gajim.plugins.plugins_i18n import _ from gajim.plugins.helpers import get_builder +from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: from ..acronyms_expander import AcronymsExpanderPlugin class ConfigDialog(GajimAppWindow): - def __init__(self, - plugin: AcronymsExpanderPlugin, - transient: Gtk.Window - ) -> None: + def __init__(self, plugin: AcronymsExpanderPlugin, transient: Gtk.Window) -> None: GajimAppWindow.__init__( self, name="AcronymsConfigDialog", - title=_('Acronyms Configuration'), + title=_("Acronyms Configuration"), default_width=400, default_height=400, transient_for=transient, @@ -48,7 +44,7 @@ class ConfigDialog(GajimAppWindow): ) ui_path = Path(__file__).parent - self._ui = get_builder(str(ui_path.resolve() / 'config.ui')) + self._ui = get_builder(str(ui_path.resolve() / "config.ui")) self._plugin = plugin @@ -71,29 +67,24 @@ class ConfigDialog(GajimAppWindow): for acronym, substitute in self._plugin.acronyms.items(): self._ui.acronyms_store.append([acronym, substitute]) - def _on_acronym_edited(self, - _renderer: Gtk.CellRendererText, - path: str, - new_text: str - ) -> None: + def _on_acronym_edited( + self, _renderer: Gtk.CellRendererText, path: str, new_text: str + ) -> None: iter_ = self._ui.acronyms_store.get_iter(path) self._ui.acronyms_store.set_value(iter_, 0, new_text) - def _on_substitute_edited(self, - _renderer: Gtk.CellRendererText, - path: str, - new_text: str - ) -> None: + def _on_substitute_edited( + self, _renderer: Gtk.CellRendererText, path: str, new_text: str + ) -> None: iter_ = self._ui.acronyms_store.get_iter(path) self._ui.acronyms_store.set_value(iter_, 1, new_text) def _on_add_clicked(self, _button: Gtk.Button) -> None: - self._ui.acronyms_store.append(['', '']) + self._ui.acronyms_store.append(["", ""]) row = self._ui.acronyms_store[-1] - self._ui.acronyms_treeview.scroll_to_cell( - row.path, None, False, 0, 0) + self._ui.acronyms_treeview.scroll_to_cell(row.path, None, False, 0, 0) self._ui.selection.unselect_all() self._ui.selection.select_path(row.path) diff --git a/anti_spam/anti_spam.py b/anti_spam/anti_spam.py index 3feda08..3bafcf8 100644 --- a/anti_spam/anti_spam.py +++ b/anti_spam/anti_spam.py @@ -12,37 +12,39 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . -''' +""" :author: Yann Leboulanger :since: 16 August 2012 :copyright: Copyright (2012) Yann Leboulanger :license: GPLv3 -''' +""" from functools import partial from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from anti_spam.modules import anti_spam from anti_spam.config_dialog import AntiSpamConfigDialog +from anti_spam.modules import anti_spam class AntiSpamPlugin(GajimPlugin): def init(self) -> None: - self.description = _('Allows you to block various kinds of incoming ' - 'messages (Spam, XHTML formatting, etc.)') + self.description = _( + "Allows you to block various kinds of incoming " + "messages (Spam, XHTML formatting, etc.)" + ) self.config_dialog = partial(AntiSpamConfigDialog, self) self.config_default_values = { - 'disable_xhtml_muc': (False, ''), - 'disable_xhtml_pm': (False, ''), - 'block_subscription_requests': (False, ''), - 'msgtxt_limit': (0, ''), - 'msgtxt_question': ('12 x 12 = ?', ''), - 'msgtxt_answer': ('', ''), - 'antispam_for_conference': (False, ''), - 'block_domains': ('', ''), - 'whitelist': ([], ''), + "disable_xhtml_muc": (False, ""), + "disable_xhtml_pm": (False, ""), + "block_subscription_requests": (False, ""), + "msgtxt_limit": (0, ""), + "msgtxt_question": ("12 x 12 = ?", ""), + "msgtxt_answer": ("", ""), + "antispam_for_conference": (False, ""), + "block_domains": ("", ""), + "whitelist": ([], ""), } self.gui_extension_points = {} self.modules = [anti_spam] diff --git a/anti_spam/config_dialog.py b/anti_spam/config_dialog.py index 4d8604f..dee937f 100644 --- a/anti_spam/config_dialog.py +++ b/anti_spam/config_dialog.py @@ -21,11 +21,10 @@ from typing import TYPE_CHECKING from gi.repository import Gtk -from gajim.gtk.settings import SettingsDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType - +from gajim.gtk.settings import SettingsDialog from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: @@ -35,72 +34,89 @@ if TYPE_CHECKING: class AntiSpamConfigDialog(SettingsDialog): def __init__(self, plugin: AntiSpamPlugin, parent: Gtk.Window) -> None: self.plugin = plugin - msgtxt_limit = cast(int, self.plugin.config['msgtxt_limit']) - max_length = '' if msgtxt_limit == 0 else msgtxt_limit + msgtxt_limit = cast(int, self.plugin.config["msgtxt_limit"]) + max_length = "" if msgtxt_limit == 0 else msgtxt_limit settings = [ - Setting(SettingKind.ENTRY, - _('Limit Message Length'), - SettingType.VALUE, - str(max_length), - callback=self._on_length_setting, - data='msgtxt_limit', - desc=_('Limits maximum message length (leave empty to ' - 'disable)')), - Setting(SettingKind.SWITCH, - _('Deny Subscription Requests'), - SettingType.VALUE, - self.plugin.config['block_subscription_requests'], - callback=self._on_setting, - data='block_subscription_requests'), - Setting(SettingKind.SWITCH, - _('Disable XHTML for Group Chats'), - SettingType.VALUE, - self.plugin.config['disable_xhtml_muc'], - callback=self._on_setting, - data='disable_xhtml_muc', - desc=_('Removes XHTML formatting from group chat ' - 'messages')), - Setting(SettingKind.SWITCH, - _('Disable XHTML for PMs'), - SettingType.VALUE, - self.plugin.config['disable_xhtml_pm'], - callback=self._on_setting, - data='disable_xhtml_pm', - desc=_('Removes XHTML formatting from private messages ' - 'in group chats')), - Setting(SettingKind.ENTRY, - _('Anti Spam Question'), - SettingType.VALUE, - self.plugin.config['msgtxt_question'], - callback=self._on_setting, - data='msgtxt_question', - desc=_('Question has to be answered in order to ' - 'contact you')), - Setting(SettingKind.ENTRY, - _('Anti Spam Answer'), - SettingType.VALUE, - self.plugin.config['msgtxt_answer'], - callback=self._on_setting, - data='msgtxt_answer', - desc=_('Correct answer to your Anti Spam Question ' - '(leave empty to disable question)')), - Setting(SettingKind.SWITCH, - _('Anti Spam Question in Group Chats'), - SettingType.VALUE, - self.plugin.config['antispam_for_conference'], - callback=self._on_setting, - data='antispam_for_conference', - desc=_('Enables anti spam question for private messages ' - 'in group chats')), - ] + Setting( + SettingKind.ENTRY, + _("Limit Message Length"), + SettingType.VALUE, + str(max_length), + callback=self._on_length_setting, + data="msgtxt_limit", + desc=_("Limits maximum message length (leave empty to " "disable)"), + ), + Setting( + SettingKind.SWITCH, + _("Deny Subscription Requests"), + SettingType.VALUE, + self.plugin.config["block_subscription_requests"], + callback=self._on_setting, + data="block_subscription_requests", + ), + Setting( + SettingKind.SWITCH, + _("Disable XHTML for Group Chats"), + SettingType.VALUE, + self.plugin.config["disable_xhtml_muc"], + callback=self._on_setting, + data="disable_xhtml_muc", + desc=_("Removes XHTML formatting from group chat " "messages"), + ), + Setting( + SettingKind.SWITCH, + _("Disable XHTML for PMs"), + SettingType.VALUE, + self.plugin.config["disable_xhtml_pm"], + callback=self._on_setting, + data="disable_xhtml_pm", + desc=_( + "Removes XHTML formatting from private messages " "in group chats" + ), + ), + Setting( + SettingKind.ENTRY, + _("Anti Spam Question"), + SettingType.VALUE, + self.plugin.config["msgtxt_question"], + callback=self._on_setting, + data="msgtxt_question", + desc=_("Question has to be answered in order to " "contact you"), + ), + Setting( + SettingKind.ENTRY, + _("Anti Spam Answer"), + SettingType.VALUE, + self.plugin.config["msgtxt_answer"], + callback=self._on_setting, + data="msgtxt_answer", + desc=_( + "Correct answer to your Anti Spam Question " + "(leave empty to disable question)" + ), + ), + Setting( + SettingKind.SWITCH, + _("Anti Spam Question in Group Chats"), + SettingType.VALUE, + self.plugin.config["antispam_for_conference"], + callback=self._on_setting, + data="antispam_for_conference", + desc=_( + "Enables anti spam question for private messages " "in group chats" + ), + ), + ] - SettingsDialog.__init__(self, - parent, - _('Anti Spam Configuration'), - Gtk.DialogFlags.MODAL, - settings, - '') + SettingsDialog.__init__( + self, + parent, + _("Anti Spam Configuration"), + Gtk.DialogFlags.MODAL, + settings, + "", + ) def _on_setting(self, value: Any, data: Any) -> None: self.plugin.config[data] = value diff --git a/anti_spam/modules/anti_spam.py b/anti_spam/modules/anti_spam.py index 1b546c9..6245c63 100644 --- a/anti_spam/modules/anti_spam.py +++ b/anti_spam/modules/anti_spam.py @@ -32,7 +32,7 @@ from gajim.common.events import MessageSent from gajim.common.modules.base import BaseModule # Module name -name = 'AntiSpam' +name = "AntiSpam" zeroconf = False @@ -41,21 +41,23 @@ class AntiSpam(BaseModule): BaseModule.__init__(self, client, plugin=True) self.handlers = [ - StanzaHandler(name='message', - callback=self._message_received, - priority=48), - StanzaHandler(name='presence', - callback=self._subscribe_received, - typ='subscribe', - priority=48), + StanzaHandler(name="message", callback=self._message_received, priority=48), + StanzaHandler( + name="presence", + callback=self._subscribe_received, + typ="subscribe", + priority=48, + ), ] - self.register_events([ - ('message-sent', ged.GUI2, self._on_message_sent), - ]) + self.register_events( + [ + ("message-sent", ged.GUI2, self._on_message_sent), + ] + ) for plugin in app.plugin_manager.plugins: - if plugin.manifest.short_name == 'anti_spam': + if plugin.manifest.short_name == "anti_spam": self._config = plugin.config self._contacted_jids: set[JID] = set() @@ -66,11 +68,9 @@ class AntiSpam(BaseModule): # This set contains JIDs of all outgoing chats. self._contacted_jids.add(event.jid) - def _message_received(self, - _con: Client, - _stanza: Message, - properties: MessageProperties - ) -> None: + def _message_received( + self, _con: Client, _stanza: Message, properties: MessageProperties + ) -> None: if properties.is_sent_carbon: # Another device already sent a message @@ -86,33 +86,35 @@ class AntiSpam(BaseModule): raise NodeProcessed msg_from = properties.jid - limit = cast(int, self._config['msgtxt_limit']) + limit = cast(int, self._config["msgtxt_limit"]) if limit > 0 and len(msg_body) > limit: - self._log.info('Discarded message from %s: message ' - 'length exceeded' % msg_from) + self._log.info( + "Discarded message from %s: message " "length exceeded" % msg_from + ) raise NodeProcessed - if self._config['disable_xhtml_muc'] and properties.type.is_groupchat: + if self._config["disable_xhtml_muc"] and properties.type.is_groupchat: properties.xhtml = None - self._log.info('Stripped message from %s: message ' - 'contained XHTML' % msg_from) + self._log.info( + "Stripped message from %s: message " "contained XHTML" % msg_from + ) - if self._config['disable_xhtml_pm'] and properties.is_muc_pm: + if self._config["disable_xhtml_pm"] and properties.is_muc_pm: properties.xhtml = None - self._log.info('Stripped message from %s: message ' - 'contained XHTML' % msg_from) + self._log.info( + "Stripped message from %s: message " "contained XHTML" % msg_from + ) def _ask_question(self, properties: MessageProperties) -> bool: - answer = cast(str, self._config['msgtxt_answer']) + answer = cast(str, self._config["msgtxt_answer"]) if len(answer) == 0: return False is_muc_pm = properties.is_muc_pm - if is_muc_pm and not self._config['antispam_for_conference']: + if is_muc_pm and not self._config["antispam_for_conference"]: return False - if (properties.type.value not in ('chat', 'normal') or - properties.is_mam_message): + if properties.type.value not in ("chat", "normal") or properties.is_mam_message: return False assert properties.jid @@ -126,15 +128,15 @@ class AntiSpam(BaseModule): # If we receive a PM or a message from an unknown user, our anti spam # question will silently be sent in the background - whitelist = cast(list[str], self._config['whitelist']) + whitelist = cast(list[str], self._config["whitelist"]) if str(msg_from) in whitelist: return False - roster_item = self._client.get_module('Roster').get_item(msg_from) + roster_item = self._client.get_module("Roster").get_item(msg_from) if is_muc_pm or roster_item is None: assert properties.body - if answer in properties.body.split('\n'): + if answer in properties.body.split("\n"): if str(msg_from) not in whitelist: whitelist.append(str(msg_from)) # We need to explicitly save, because 'append' does not @@ -146,26 +148,24 @@ class AntiSpam(BaseModule): return False def _send_question(self, properties: MessageProperties, jid: JID) -> None: - message = 'Anti Spam Question: %s' % self._config['msgtxt_question'] + message = "Anti Spam Question: %s" % self._config["msgtxt_question"] stanza = Message(to=jid, body=message, typ=properties.type.value) self._client.connection.send_stanza(stanza) - self._log.info('Anti spam question sent to %s', jid) + self._log.info("Anti spam question sent to %s", jid) - def _subscribe_received(self, - _con: Client, - _stanza: Presence, - properties: PresenceProperties - ) -> None: + def _subscribe_received( + self, _con: Client, _stanza: Presence, properties: PresenceProperties + ) -> None: msg_from = properties.jid - block_sub = self._config['block_subscription_requests'] - roster_item = self._client.get_module('Roster').get_item(msg_from) + block_sub = self._config["block_subscription_requests"] + roster_item = self._client.get_module("Roster").get_item(msg_from) if block_sub and roster_item is None: - self._client.get_module('Presence').unsubscribed(msg_from) - self._log.info('Denied subscription request from %s' % msg_from) + self._client.get_module("Presence").unsubscribed(msg_from) + self._log.info("Denied subscription request from %s" % msg_from) raise NodeProcessed def get_instance(*args: Any, **kwargs: Any) -> tuple[AntiSpam, str]: - return AntiSpam(*args, **kwargs), 'AntiSpam' + return AntiSpam(*args, **kwargs), "AntiSpam" diff --git a/clients_icons/clients.py b/clients_icons/clients.py index baa9d53..2c1e3d7 100644 --- a/clients_icons/clients.py +++ b/clients_icons/clients.py @@ -39,7 +39,7 @@ def get_variations(client_name: str) -> list[str]: if client_name is None: return [] alts = client_name.split() - alts = [' '.join(alts[:(i + 1)]) for i in range(len(alts))] + alts = [" ".join(alts[: (i + 1)]) for i in range(len(alts))] alts.reverse() return alts @@ -48,23 +48,23 @@ class ClientsDict(UserDict[str, ClientData]): def get_client_data(self, name: str, node: str) -> tuple[str, str]: client_data = self.get(node) if client_data is None: - return _('Unknown'), 'xmpp-client-unknown' + return _("Unknown"), "xmpp-client-unknown" if client_data.variations is None: assert client_data.default is not None client_name, icon_name = client_data.default - return client_name, f'xmpp-client-{icon_name}' + return client_name, f"xmpp-client-{icon_name}" variations = get_variations(name) for var in variations: try: - return var, f'xmpp-client-{client_data.variations[var]}' + return var, f"xmpp-client-{client_data.variations[var]}" except KeyError: pass assert client_data.default is not None client_name, icon_name = client_data.default - return client_name, f'xmpp-client-{icon_name}' + return client_name, f"xmpp-client-{icon_name}" # ClientData( @@ -73,137 +73,159 @@ class ClientsDict(UserDict[str, ClientData]): # ) # pylint: disable=too-many-lines -CLIENTS = ClientsDict({ - 'http://gajim.org': ClientData(('Gajim', 'gajim')), - 'https://gajim.org': ClientData(('Gajim', 'gajim')), - 'http://conversations.im': ClientData( - default=('Conversations', 'conversations'), - variations={'Conversations Legacy': 'conversations-legacy'} - ), - 'http://jabber.pix-art.de': ClientData(('Pix-Art Messenger', 'pixart')), - 'http://blabber.im': ClientData(('blabber.im', 'blabber')), - 'http://monocles.de': ClientData(('monocles chat', 'monocles-chat')), - 'http://pidgin.im/': ClientData(('Pidgin', 'pidgin')), - 'https://poez.io': ClientData(('Poezio', 'poezio')), - 'https://yaxim.org/': ClientData(('yaxim', 'yaxim')), - 'https://yaxim.org/bruno/': ClientData(('Bruno', 'bruno')), - 'http://mcabber.com/caps': ClientData(('MCabber', 'mcabber')), - 'http://psi-plus.com': ClientData(('Psi+', 'psiplus')), - 'https://psi-plus.com': ClientData(('Psi+', 'psiplus')), - 'https://dino.im': ClientData(('Dino', 'dino')), - 'http://monal.im/': ClientData(('Monal', 'monal')), - 'http://slixmpp.com/ver/1.2.4': ClientData(('Bot', 'bot')), - 'http://slixmpp.com/ver/1.3.0': ClientData(('Bot', 'bot')), - 'https://www.xabber.com/': ClientData(('Xabber', 'xabber')), - 'http://www.profanity.im': ClientData(('Profanity', 'profanity')), - 'http://swift.im': ClientData(('Swift', 'swift')), - 'https://salut-a-toi.org': ClientData(('Salut à Toi', 'sat')), - 'https://conversejs.org': ClientData(('Converse', 'conversejs')), - 'http://bitlbee.org/xmpp/caps': ClientData(('BitlBee', 'bitlbee')), - 'http://tkabber.jabber.ru/': ClientData(('Tkabber', 'tkabber')), - 'http://miranda-ng.org/caps': ClientData(('Miranda NG', 'miranda_ng')), - 'http://www.adium.im/': ClientData(('Adium', 'adium')), - 'http://www.adiumx.com/caps': ClientData(('Adium', 'adium')), - 'http://www.adiumx.com': ClientData(('Adium', 'adium')), - 'http://aqq.eu/': ClientData(('Aqq', 'aqq')), - 'http://www.asterisk.org/xmpp/client/caps': ClientData(('Asterisk', 'asterisk')), - 'http://ayttm.souceforge.net/caps': ClientData(('Ayttm', 'ayttm')), - 'http://www.barobin.com/caps': ClientData(('Bayanicq', 'bayanicq')), - 'http://simpleapps.ru/caps#blacksmith': ClientData(('Blacksmith', 'bot')), - 'http://blacksmith-2.googlecode.com/svn/': ClientData(('Blacksmith-2', 'bot')), - 'http://coccinella.sourceforge.net/protocol/caps': ClientData(('Coccinella', 'coccinella')), - 'http://digsby.com/caps': ClientData(('Digsby', 'digsby')), - 'http://emacs-jabber.sourceforge.net': ClientData(('Emacs Jabber Client', 'emacs')), - 'http://emess.eqx.su/caps': ClientData(('Emess', 'emess')), - 'http://live.gnome.org/empathy/caps': ClientData(('Empathy', 'telepathy.freedesktop.org')), - 'http://eqo.com/': ClientData(('Eqo', 'libpurple')), - 'http://exodus.jabberstudio.org/caps': ClientData(('Exodus', 'exodus')), - 'http://fatal-bot.spb.ru/caps': ClientData(('Fatal-bot', 'bot')), - 'http://svn.posix.ru/fatal-bot/trunk': ClientData(('Fatal-bot', 'bot')), - 'http://isida.googlecode.com': ClientData(('Isida', 'isida-bot')), - 'http://isida-bot.com': ClientData(('Isida', 'isida-bot')), - 'http://jabga.ru': ClientData(('Fin jabber', 'fin')), - 'http://chat.freize.org/caps': ClientData(('Freize', 'freize')), - 'http://gabber.sourceforge.net': ClientData(('Gabber', 'gabber')), - 'http://glu.net/': ClientData(('Glu', 'glu')), - 'http://mail.google.com/xmpp/client/caps': ClientData(('GMail', 'google.com')), - 'http://www.android.com/gtalk/client/caps': ClientData(('GTalk', 'talk.google.com')), - 'talk.google.com': ClientData(('GTalk', 'talk.google.com')), - 'http://talkgadget.google.com/client/caps': ClientData(('GTalk', 'google')), - 'http://talk.google.com/xmpp/bot/caps': ClientData(('GTalk', 'google')), - 'http://aspro.users.ru/historian-bot/': ClientData(('Historian-bot', 'bot')), - 'http://www.apple.com/ichat/caps': ClientData(('IChat', 'ichat')), - 'http://instantbird.com/': ClientData(('Instantbird', 'instantbird')), - 'http://j-tmb.ru/caps': ClientData(('J-tmb', 'bot')), - 'http://jabbroid.akuz.de': ClientData(('Jabbroid', 'android')), - 'http://jabbroid.akuz.de/caps': ClientData(('Jabbroid', 'android')), - 'http://dev.jabbim.cz/jabbim/caps': ClientData(('Jabbim', 'jabbim')), - 'http://jabbrik.ru/caps': ClientData(('Jabbrik', 'bot')), - 'http://jabrvista.net.ru': ClientData(('Jabvista', 'bot')), - 'http://jajc.jrudevels.org/caps': ClientData(('JAJC', 'jajc')), - 'http://qabber.ru/jame-bot': ClientData(('Jame-bot', 'bot')), - 'https://www.jappix.com/': ClientData(('Jappix', 'jappix')), - 'http://japyt.googlecode.com': ClientData(('Japyt', 'japyt')), - 'http://jasmineicq.ru/caps': ClientData(('Jasmine', 'jasmine')), - 'http://jimm.net.ru/caps': ClientData(('Jimm', 'jimm-aspro')), - 'http://jitsi.org': ClientData(('Jitsi', 'jitsi')), - 'http://jtalk.ustyugov.net/caps': ClientData(('Jtalk', 'jtalk')), - 'http://pjc.googlecode.com/caps': ClientData(('Jubo', 'jubo')), - 'http://juick.com/caps': ClientData(('Juick', 'juick')), - 'http://kopete.kde.org/jabber/caps': ClientData(('Kopete', 'kopete')), - 'http://bluendo.com/protocol/caps': ClientData(('Lampiro', 'lampiro')), - 'http://lytgeygen.ru/caps': ClientData(('Lytgeygen', 'bot')), - 'http://agent.mail.ru/caps': ClientData(('Mailruagent', 'mailruagent')), - 'http://agent.mail.ru/': ClientData(('Mailruagent', 'mailruagent')), - 'http://tomclaw.com/mandarin_im/caps': ClientData(('Mandarin', 'mandarin')), - 'http://mchat.mgslab.com/': ClientData(('Mchat', 'mchat')), - 'https://www.meebo.com/': ClientData(('Meebo', 'meebo')), - 'http://megafonvolga.ru/': ClientData(('Megafon', 'megafon')), - 'http://miranda-im.org/caps': ClientData(('Miranda', 'miranda')), - 'https://movim.eu/': ClientData(('Movim', 'movim')), - 'http://moxl.movim.eu/': ClientData(('Movim', 'movim')), - 'nimbuzz:caps': ClientData(('Nimbuzz', 'nimbuzz')), - 'http://nimbuzz.com/caps': ClientData(('Nimbuzz', 'nimbuzz')), - 'http://home.gna.org/': ClientData(('Omnipresence', 'omnipresence')), - 'http://oneteam.im/caps': ClientData(('OneTeam', 'oneteamiphone')), - 'http://www.process-one.net/en/solutions/oneteam_iphone/': ClientData(('OneTeam-IPhone', 'oneteamiphone')), - 'rss@isida-bot.com': ClientData(('Osiris', 'osiris')), - 'http://chat.ovi.com/caps': ClientData(('Ovi-chat', 'ovi-chat')), - 'http://opensource.palm.com/packages.html': ClientData(('Palm', 'palm')), - 'http://palringo.com/caps': ClientData(('Palringo', 'palringo')), - 'http://pandion.im/': ClientData(('Pandion', 'pandion')), - 'http://pigeon.vpro.ru/caps': ClientData(('Pigeon', 'pigeon')), - 'psto@psto.net': ClientData(('Psto', 'psto')), - 'http://qq-im.com/caps': ClientData(('QQ', 'qq')), - 'http://qq.com/caps': ClientData(('QQ', 'qq')), - 'http://2010.qip.ru/caps': ClientData(('Qip', 'qip')), - 'http://qip.ru/caps': ClientData(('Qip', 'qip')), - 'http://qip.ru/caps?QIP': ClientData(('Qip', 'qip')), - 'http://pda.qip.ru/caps': ClientData(('Qip-PDA', 'qippda')), - 'http://qutim.org': ClientData(('QutIM', 'qutim')), - 'http://qutim.org/': ClientData(('QutIM', 'qutim')), - 'http://apps.radio-t.com/caps': ClientData(('Radio-t', 'radio-t')), - 'http://sim-im.org/caps': ClientData(('Sim', 'sim')), - 'http://www.lonelycatgames.com/slick/caps': ClientData(('Slick', 'slick')), - 'http://snapi-bot.googlecode.com/caps': ClientData(('Snapi-bot', 'bot')), - 'http://www.igniterealtime.org/project/spark/caps': ClientData(('Spark', 'spark')), - 'http://spectrum.im/': ClientData(('Spectrum', 'spectrum')), - 'http://storm-bot.googlecode.com/svn/trunk': ClientData(('Storm-bot', 'bot')), - 'http://jabber-net.ru/caps/talisman-bot': ClientData(('Talisman-bot', 'bot')), - 'http://jabber-net.ru/talisman-bot/caps': ClientData(('Talisman-bot', 'bot')), - 'http://www.google.com/xmpp/client/caps': ClientData(('Talkonaut', 'talkonaut')), - 'http://telepathy.freedesktop.org/caps': ClientData(('SlicTelepathyk', 'telepathy.freedesktop.org')), - 'http://tigase.org/messenger': ClientData(('Tigase', 'tigase')), - 'http://trillian.im/caps': ClientData(('Trillian', 'trillian')), - 'http://vacuum-im.googlecode.com': ClientData(('Vacuum', 'vacuum')), - 'http://code.google.com/p/vacuum-im/': ClientData(('Vacuum', 'vacuum')), - 'http://witcher-team.ucoz.ru/': ClientData(('Witcher', 'bot')), - 'http://online.yandex.ru/caps': ClientData(('Yaonline', 'yaonline')), - 'http://www.igniterealtime.org/projects/smack/': ClientData(('Xabber', 'xabber')), - 'http://www.xfire.com/': ClientData(('Xfire', 'xfire')), - 'http://www.xfire.com/caps': ClientData(('Xfire', 'xfire')), - 'http://xu-6.jabbrik.ru/caps': ClientData(('XU-6', 'bot')), -}) +CLIENTS = ClientsDict( + { + "http://gajim.org": ClientData(("Gajim", "gajim")), + "https://gajim.org": ClientData(("Gajim", "gajim")), + "http://conversations.im": ClientData( + default=("Conversations", "conversations"), + variations={"Conversations Legacy": "conversations-legacy"}, + ), + "http://jabber.pix-art.de": ClientData(("Pix-Art Messenger", "pixart")), + "http://blabber.im": ClientData(("blabber.im", "blabber")), + "http://monocles.de": ClientData(("monocles chat", "monocles-chat")), + "http://pidgin.im/": ClientData(("Pidgin", "pidgin")), + "https://poez.io": ClientData(("Poezio", "poezio")), + "https://yaxim.org/": ClientData(("yaxim", "yaxim")), + "https://yaxim.org/bruno/": ClientData(("Bruno", "bruno")), + "http://mcabber.com/caps": ClientData(("MCabber", "mcabber")), + "http://psi-plus.com": ClientData(("Psi+", "psiplus")), + "https://psi-plus.com": ClientData(("Psi+", "psiplus")), + "https://dino.im": ClientData(("Dino", "dino")), + "http://monal.im/": ClientData(("Monal", "monal")), + "http://slixmpp.com/ver/1.2.4": ClientData(("Bot", "bot")), + "http://slixmpp.com/ver/1.3.0": ClientData(("Bot", "bot")), + "https://www.xabber.com/": ClientData(("Xabber", "xabber")), + "http://www.profanity.im": ClientData(("Profanity", "profanity")), + "http://swift.im": ClientData(("Swift", "swift")), + "https://salut-a-toi.org": ClientData(("Salut à Toi", "sat")), + "https://conversejs.org": ClientData(("Converse", "conversejs")), + "http://bitlbee.org/xmpp/caps": ClientData(("BitlBee", "bitlbee")), + "http://tkabber.jabber.ru/": ClientData(("Tkabber", "tkabber")), + "http://miranda-ng.org/caps": ClientData(("Miranda NG", "miranda_ng")), + "http://www.adium.im/": ClientData(("Adium", "adium")), + "http://www.adiumx.com/caps": ClientData(("Adium", "adium")), + "http://www.adiumx.com": ClientData(("Adium", "adium")), + "http://aqq.eu/": ClientData(("Aqq", "aqq")), + "http://www.asterisk.org/xmpp/client/caps": ClientData( + ("Asterisk", "asterisk") + ), + "http://ayttm.souceforge.net/caps": ClientData(("Ayttm", "ayttm")), + "http://www.barobin.com/caps": ClientData(("Bayanicq", "bayanicq")), + "http://simpleapps.ru/caps#blacksmith": ClientData(("Blacksmith", "bot")), + "http://blacksmith-2.googlecode.com/svn/": ClientData(("Blacksmith-2", "bot")), + "http://coccinella.sourceforge.net/protocol/caps": ClientData( + ("Coccinella", "coccinella") + ), + "http://digsby.com/caps": ClientData(("Digsby", "digsby")), + "http://emacs-jabber.sourceforge.net": ClientData( + ("Emacs Jabber Client", "emacs") + ), + "http://emess.eqx.su/caps": ClientData(("Emess", "emess")), + "http://live.gnome.org/empathy/caps": ClientData( + ("Empathy", "telepathy.freedesktop.org") + ), + "http://eqo.com/": ClientData(("Eqo", "libpurple")), + "http://exodus.jabberstudio.org/caps": ClientData(("Exodus", "exodus")), + "http://fatal-bot.spb.ru/caps": ClientData(("Fatal-bot", "bot")), + "http://svn.posix.ru/fatal-bot/trunk": ClientData(("Fatal-bot", "bot")), + "http://isida.googlecode.com": ClientData(("Isida", "isida-bot")), + "http://isida-bot.com": ClientData(("Isida", "isida-bot")), + "http://jabga.ru": ClientData(("Fin jabber", "fin")), + "http://chat.freize.org/caps": ClientData(("Freize", "freize")), + "http://gabber.sourceforge.net": ClientData(("Gabber", "gabber")), + "http://glu.net/": ClientData(("Glu", "glu")), + "http://mail.google.com/xmpp/client/caps": ClientData(("GMail", "google.com")), + "http://www.android.com/gtalk/client/caps": ClientData( + ("GTalk", "talk.google.com") + ), + "talk.google.com": ClientData(("GTalk", "talk.google.com")), + "http://talkgadget.google.com/client/caps": ClientData(("GTalk", "google")), + "http://talk.google.com/xmpp/bot/caps": ClientData(("GTalk", "google")), + "http://aspro.users.ru/historian-bot/": ClientData(("Historian-bot", "bot")), + "http://www.apple.com/ichat/caps": ClientData(("IChat", "ichat")), + "http://instantbird.com/": ClientData(("Instantbird", "instantbird")), + "http://j-tmb.ru/caps": ClientData(("J-tmb", "bot")), + "http://jabbroid.akuz.de": ClientData(("Jabbroid", "android")), + "http://jabbroid.akuz.de/caps": ClientData(("Jabbroid", "android")), + "http://dev.jabbim.cz/jabbim/caps": ClientData(("Jabbim", "jabbim")), + "http://jabbrik.ru/caps": ClientData(("Jabbrik", "bot")), + "http://jabrvista.net.ru": ClientData(("Jabvista", "bot")), + "http://jajc.jrudevels.org/caps": ClientData(("JAJC", "jajc")), + "http://qabber.ru/jame-bot": ClientData(("Jame-bot", "bot")), + "https://www.jappix.com/": ClientData(("Jappix", "jappix")), + "http://japyt.googlecode.com": ClientData(("Japyt", "japyt")), + "http://jasmineicq.ru/caps": ClientData(("Jasmine", "jasmine")), + "http://jimm.net.ru/caps": ClientData(("Jimm", "jimm-aspro")), + "http://jitsi.org": ClientData(("Jitsi", "jitsi")), + "http://jtalk.ustyugov.net/caps": ClientData(("Jtalk", "jtalk")), + "http://pjc.googlecode.com/caps": ClientData(("Jubo", "jubo")), + "http://juick.com/caps": ClientData(("Juick", "juick")), + "http://kopete.kde.org/jabber/caps": ClientData(("Kopete", "kopete")), + "http://bluendo.com/protocol/caps": ClientData(("Lampiro", "lampiro")), + "http://lytgeygen.ru/caps": ClientData(("Lytgeygen", "bot")), + "http://agent.mail.ru/caps": ClientData(("Mailruagent", "mailruagent")), + "http://agent.mail.ru/": ClientData(("Mailruagent", "mailruagent")), + "http://tomclaw.com/mandarin_im/caps": ClientData(("Mandarin", "mandarin")), + "http://mchat.mgslab.com/": ClientData(("Mchat", "mchat")), + "https://www.meebo.com/": ClientData(("Meebo", "meebo")), + "http://megafonvolga.ru/": ClientData(("Megafon", "megafon")), + "http://miranda-im.org/caps": ClientData(("Miranda", "miranda")), + "https://movim.eu/": ClientData(("Movim", "movim")), + "http://moxl.movim.eu/": ClientData(("Movim", "movim")), + "nimbuzz:caps": ClientData(("Nimbuzz", "nimbuzz")), + "http://nimbuzz.com/caps": ClientData(("Nimbuzz", "nimbuzz")), + "http://home.gna.org/": ClientData(("Omnipresence", "omnipresence")), + "http://oneteam.im/caps": ClientData(("OneTeam", "oneteamiphone")), + "http://www.process-one.net/en/solutions/oneteam_iphone/": ClientData( + ("OneTeam-IPhone", "oneteamiphone") + ), + "rss@isida-bot.com": ClientData(("Osiris", "osiris")), + "http://chat.ovi.com/caps": ClientData(("Ovi-chat", "ovi-chat")), + "http://opensource.palm.com/packages.html": ClientData(("Palm", "palm")), + "http://palringo.com/caps": ClientData(("Palringo", "palringo")), + "http://pandion.im/": ClientData(("Pandion", "pandion")), + "http://pigeon.vpro.ru/caps": ClientData(("Pigeon", "pigeon")), + "psto@psto.net": ClientData(("Psto", "psto")), + "http://qq-im.com/caps": ClientData(("QQ", "qq")), + "http://qq.com/caps": ClientData(("QQ", "qq")), + "http://2010.qip.ru/caps": ClientData(("Qip", "qip")), + "http://qip.ru/caps": ClientData(("Qip", "qip")), + "http://qip.ru/caps?QIP": ClientData(("Qip", "qip")), + "http://pda.qip.ru/caps": ClientData(("Qip-PDA", "qippda")), + "http://qutim.org": ClientData(("QutIM", "qutim")), + "http://qutim.org/": ClientData(("QutIM", "qutim")), + "http://apps.radio-t.com/caps": ClientData(("Radio-t", "radio-t")), + "http://sim-im.org/caps": ClientData(("Sim", "sim")), + "http://www.lonelycatgames.com/slick/caps": ClientData(("Slick", "slick")), + "http://snapi-bot.googlecode.com/caps": ClientData(("Snapi-bot", "bot")), + "http://www.igniterealtime.org/project/spark/caps": ClientData( + ("Spark", "spark") + ), + "http://spectrum.im/": ClientData(("Spectrum", "spectrum")), + "http://storm-bot.googlecode.com/svn/trunk": ClientData(("Storm-bot", "bot")), + "http://jabber-net.ru/caps/talisman-bot": ClientData(("Talisman-bot", "bot")), + "http://jabber-net.ru/talisman-bot/caps": ClientData(("Talisman-bot", "bot")), + "http://www.google.com/xmpp/client/caps": ClientData( + ("Talkonaut", "talkonaut") + ), + "http://telepathy.freedesktop.org/caps": ClientData( + ("SlicTelepathyk", "telepathy.freedesktop.org") + ), + "http://tigase.org/messenger": ClientData(("Tigase", "tigase")), + "http://trillian.im/caps": ClientData(("Trillian", "trillian")), + "http://vacuum-im.googlecode.com": ClientData(("Vacuum", "vacuum")), + "http://code.google.com/p/vacuum-im/": ClientData(("Vacuum", "vacuum")), + "http://witcher-team.ucoz.ru/": ClientData(("Witcher", "bot")), + "http://online.yandex.ru/caps": ClientData(("Yaonline", "yaonline")), + "http://www.igniterealtime.org/projects/smack/": ClientData( + ("Xabber", "xabber") + ), + "http://www.xfire.com/": ClientData(("Xfire", "xfire")), + "http://www.xfire.com/caps": ClientData(("Xfire", "xfire")), + "http://xu-6.jabbrik.ru/caps": ClientData(("XU-6", "bot")), + } +) # pylint: enable=too-many-lines diff --git a/clients_icons/clients_icons.py b/clients_icons/clients_icons.py index 0d82a7c..888d693 100644 --- a/clients_icons/clients_icons.py +++ b/clients_icons/clients_icons.py @@ -18,42 +18,39 @@ from __future__ import annotations from typing import cast import logging -from pathlib import Path from functools import partial +from pathlib import Path from gi.repository import Gtk - from nbxmpp.structs import DiscoInfo from gajim.common import app from gajim.common.modules.contacts import GroupchatParticipant from gajim.common.modules.contacts import ResourceContact - +from gajim.gtk.util import load_icon_surface from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from gajim.gtk.util import load_icon_surface - from clients_icons import clients from clients_icons.config_dialog import ClientsIconsConfigDialog -log = logging.getLogger('gajim.p.client_icons') +log = logging.getLogger("gajim.p.client_icons") class ClientsIconsPlugin(GajimPlugin): def init(self) -> None: - self.description = _('Shows client icons in your contact list' - ' in a tooltip.') + self.description = _("Shows client icons in your contact list" " in a tooltip.") self.config_dialog = partial(ClientsIconsConfigDialog, self) self.gui_extension_points = { - 'roster_tooltip_resource_populate': ( + "roster_tooltip_resource_populate": ( self._roster_tooltip_resource_populate, - None), + None, + ), } self.config_default_values = { - 'show_unknown_icon': (True, ''), + "show_unknown_icon": (True, ""), } _icon_theme = Gtk.IconTheme.get_default() @@ -63,15 +60,13 @@ class ClientsIconsPlugin(GajimPlugin): @staticmethod def _get_client_identity_name(disco_info: DiscoInfo) -> str | None: for identity in disco_info.identities: - if identity.category == 'client': + if identity.category == "client": return identity.name return None - def _get_image_and_client_name(self, - contact: - GroupchatParticipant | ResourceContact, - _widget: Gtk.Widget - ) -> tuple[Gtk.Image, str] | None: + def _get_image_and_client_name( + self, contact: GroupchatParticipant | ResourceContact, _widget: Gtk.Widget + ) -> tuple[Gtk.Image, str] | None: disco_info = app.storage.cache.get_last_disco_info(contact.jid) if disco_info is None: @@ -80,18 +75,17 @@ class ClientsIconsPlugin(GajimPlugin): if disco_info.node is None: return None - node = disco_info.node.split('#')[0] + node = disco_info.node.split("#")[0] client_name = self._get_client_identity_name(disco_info) - log.info('Lookup client: %s %s', client_name, node) + log.info("Lookup client: %s %s", client_name, node) client_name, icon_name = clients.get_data(client_name, node) surface = load_icon_surface(icon_name) return Gtk.Image.new_from_surface(surface), client_name - def _roster_tooltip_resource_populate(self, - resource_box: Gtk.Box, - resource: ResourceContact - ) -> None: + def _roster_tooltip_resource_populate( + self, resource_box: Gtk.Box, resource: ResourceContact + ) -> None: result = self._get_image_and_client_name(resource, resource_box) if result is None: @@ -100,8 +94,9 @@ class ClientsIconsPlugin(GajimPlugin): image, client_name = result label = Gtk.Label(label=client_name) - client_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, - halign=Gtk.Align.START) + client_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START + ) client_box.add(image) client_box.add(label) diff --git a/clients_icons/config_dialog.py b/clients_icons/config_dialog.py index 2a58502..e19b11d 100644 --- a/clients_icons/config_dialog.py +++ b/clients_icons/config_dialog.py @@ -20,12 +20,11 @@ from typing import TYPE_CHECKING from gi.repository import Gtk -from gajim.plugins.plugins_i18n import _ - -from gajim.gtk.settings import SettingsDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType +from gajim.gtk.settings import SettingsDialog +from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: from .clients_icons import ClientsIconsPlugin @@ -36,20 +35,24 @@ class ClientsIconsConfigDialog(SettingsDialog): self.plugin = plugin settings = [ - Setting(SettingKind.SWITCH, - _('Show Icon for Unknown Clients'), - SettingType.VALUE, - self.plugin.config['show_unknown_icon'], - callback=self._on_setting, - data='show_unknown_icon'), + Setting( + SettingKind.SWITCH, + _("Show Icon for Unknown Clients"), + SettingType.VALUE, + self.plugin.config["show_unknown_icon"], + callback=self._on_setting, + data="show_unknown_icon", + ), ] - SettingsDialog.__init__(self, - parent, - _('Clients Icons Configuration'), - Gtk.DialogFlags.MODAL, - settings, - '') + SettingsDialog.__init__( + self, + parent, + _("Clients Icons Configuration"), + Gtk.DialogFlags.MODAL, + settings, + "", + ) def _on_setting(self, value: Any, data: Any) -> None: self.plugin.config[data] = value diff --git a/length_notifier/config_dialog.py b/length_notifier/config_dialog.py index 5cc3c7d..6be8b0a 100644 --- a/length_notifier/config_dialog.py +++ b/length_notifier/config_dialog.py @@ -20,11 +20,10 @@ from typing import TYPE_CHECKING from gi.repository import Gtk -from gajim.gtk.settings import SettingsDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType - +from gajim.gtk.settings import SettingsDialog from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: @@ -32,52 +31,56 @@ if TYPE_CHECKING: class LengthNotifierConfigDialog(SettingsDialog): - def __init__(self, - plugin: LengthNotifierPlugin, - parent: Gtk.Window - ) -> None: + def __init__(self, plugin: LengthNotifierPlugin, parent: Gtk.Window) -> None: self.plugin = plugin - jids = self.plugin.config['JIDS'] or '' + jids = self.plugin.config["JIDS"] or "" if isinstance(jids, list): # Gajim 1.0 stored this as list[str] - jids = ','.join(jids) + jids = ",".join(jids) settings = [ - Setting(SettingKind.SPIN, - _('Message Length'), - SettingType.VALUE, - str(self.plugin.config['MESSAGE_WARNING_LENGTH']), - callback=self._on_setting, - data='MESSAGE_WARNING_LENGTH', - desc=_('Message length at which the highlight is shown'), - props={'range_': (1, 1000, 1)}, - ), - Setting(SettingKind.COLOR, - _('Color'), - SettingType.VALUE, - self.plugin.config['WARNING_COLOR'], - callback=self._on_setting, - data='WARNING_COLOR', - desc=_('Highlight color for the message input'), - ), - Setting(SettingKind.ENTRY, - _('Selected Addresses'), - SettingType.VALUE, - jids, - callback=self._on_setting, - data='JIDS', - desc=_('Enable the plugin for selected XMPP addresses ' - 'only (comma separated)'), - ), - ] + Setting( + SettingKind.SPIN, + _("Message Length"), + SettingType.VALUE, + str(self.plugin.config["MESSAGE_WARNING_LENGTH"]), + callback=self._on_setting, + data="MESSAGE_WARNING_LENGTH", + desc=_("Message length at which the highlight is shown"), + props={"range_": (1, 1000, 1)}, + ), + Setting( + SettingKind.COLOR, + _("Color"), + SettingType.VALUE, + self.plugin.config["WARNING_COLOR"], + callback=self._on_setting, + data="WARNING_COLOR", + desc=_("Highlight color for the message input"), + ), + Setting( + SettingKind.ENTRY, + _("Selected Addresses"), + SettingType.VALUE, + jids, + callback=self._on_setting, + data="JIDS", + desc=_( + "Enable the plugin for selected XMPP addresses " + "only (comma separated)" + ), + ), + ] - SettingsDialog.__init__(self, - parent, - _('Length Notifier Configuration'), - Gtk.DialogFlags.MODAL, - settings, - '') + SettingsDialog.__init__( + self, + parent, + _("Length Notifier Configuration"), + Gtk.DialogFlags.MODAL, + settings, + "", + ) def _on_setting(self, value: Any, data: Any) -> None: if isinstance(value, str): diff --git a/length_notifier/length_notifier.py b/length_notifier/length_notifier.py index d48934e..e8fe2c7 100644 --- a/length_notifier/length_notifier.py +++ b/length_notifier/length_notifier.py @@ -12,14 +12,14 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . -''' +""" Message length notifier plugin. :author: Mateusz Biliński :since: 1st June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL -''' +""" from __future__ import annotations from typing import Any @@ -31,48 +31,50 @@ from functools import partial from gi.repository import Gdk from gi.repository import GObject from gi.repository import Gtk - from nbxmpp.protocol import JID from gajim.common import app from gajim.common import types - from gajim.gtk.message_actions_box import MessageActionsBox from gajim.gtk.message_input import MessageInputTextView - from gajim.plugins import GajimPlugin from gajim.plugins.gajimplugin import GajimPluginConfig from gajim.plugins.plugins_i18n import _ from length_notifier.config_dialog import LengthNotifierConfigDialog -log = logging.getLogger('gajim.p.length_notifier') +log = logging.getLogger("gajim.p.length_notifier") class LengthNotifierPlugin(GajimPlugin): def init(self) -> None: - self.description = _('Highlights the chat window’s message input if ' - 'a specified message length is exceeded.') + self.description = _( + "Highlights the chat window’s message input if " + "a specified message length is exceeded." + ) self.config_dialog = partial(LengthNotifierConfigDialog, self) self.gui_extension_points = { - 'message_actions_box': (self._message_actions_box_created, None), - 'switch_contact': (self._on_switch_contact, None) + "message_actions_box": (self._message_actions_box_created, None), + "switch_contact": (self._on_switch_contact, None), } self.config_default_values = { - 'MESSAGE_WARNING_LENGTH': ( + "MESSAGE_WARNING_LENGTH": ( 140, - 'Message length at which the highlight is shown'), - 'WARNING_COLOR': ( - 'rgb(240, 220, 60)', - 'Highlight color for the message input'), - 'JIDS': ( - '', - 'Enable the plugin for selected XMPP addresses ' - 'only (comma separated)') - } + "Message length at which the highlight is shown", + ), + "WARNING_COLOR": ( + "rgb(240, 220, 60)", + "Highlight color for the message input", + ), + "JIDS": ( + "", + "Enable the plugin for selected XMPP addresses " + "only (comma separated)", + ), + } self._message_action_box = None self._actions_box_widget = None @@ -92,14 +94,12 @@ class LengthNotifierPlugin(GajimPlugin): def _create_counter(self) -> None: assert self._message_action_box is not None assert self._actions_box_widget is not None - self._counter = Counter(self._message_action_box.msg_textview, - self.config) + self._counter = Counter(self._message_action_box.msg_textview, self.config) self._actions_box_widget.pack_end(self._counter, False, False, 0) - def _message_actions_box_created(self, - message_actions_box: MessageActionsBox, - gtk_box: Gtk.Box - ) -> None: + def _message_actions_box_created( + self, message_actions_box: MessageActionsBox, gtk_box: Gtk.Box + ) -> None: self._message_action_box = message_actions_box self._actions_box_widget = gtk_box @@ -117,14 +117,13 @@ class LengthNotifierPlugin(GajimPlugin): class Counter(Gtk.Label): - def __init__(self, - message_input: MessageInputTextView, - config: GajimPluginConfig - ) -> None: + def __init__( + self, message_input: MessageInputTextView, config: GajimPluginConfig + ) -> None: Gtk.Label.__init__(self) - self.set_tooltip_text(_('Number of typed characters')) - self.get_style_context().add_class('dim-label') + self.set_tooltip_text(_("Number of typed characters")) + self.get_style_context().add_class("dim-label") self._config = config @@ -135,47 +134,48 @@ class Counter(Gtk.Label): self._inverted_color = None self._textview = message_input - self._signal_id = self._textview.connect('buffer-changed', self._update) + self._signal_id = self._textview.connect("buffer-changed", self._update) self._provider = None self._parse_config() self._set_css() - self.connect('destroy', self._on_destroy) + self.connect("destroy", self._on_destroy) def _on_destroy(self, _widget: Counter) -> None: - self._context.remove_class('length-warning') + self._context.remove_class("length-warning") assert self._signal_id is not None - if GObject.signal_handler_is_connected( - self._textview, self._signal_id): + if GObject.signal_handler_is_connected(self._textview, self._signal_id): self._textview.disconnect(self._signal_id) app.check_finalize(self) def _parse_config(self) -> None: - self._max_length = cast(int, self._config['MESSAGE_WARNING_LENGTH']) + self._max_length = cast(int, self._config["MESSAGE_WARNING_LENGTH"]) - self._color = cast(str, self._config['WARNING_COLOR']) + self._color = cast(str, self._config["WARNING_COLOR"]) rgba = Gdk.RGBA() rgba.parse(self._color) red = int(255 - rgba.red * 255) green = int(255 - rgba.green * 255) blue = int(255 - rgba.blue * 255) - self._inverted_color = f'rgb({red}, {green}, {blue})' + self._inverted_color = f"rgb({red}, {green}, {blue})" def _set_css(self) -> None: self._context = self._textview.get_style_context() if self._provider is not None: self._context.remove_provider(self._provider) - css = ''' + css = """ .length-warning > * { color: %s; background-color: %s; } - ''' % (self._inverted_color, self._color) + """ % ( + self._inverted_color, + self._color, + ) self._provider = Gtk.CssProvider() self._provider.load_from_data(bytes(css.encode())) - self._context.add_provider( - self._provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) + self._context.add_provider(self._provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _set_count(self, count: int) -> None: self.set_label(str(count)) @@ -196,38 +196,38 @@ class Counter(Gtk.Label): len_text = len(text) self._set_count(len_text) if len_text > self._max_length: - self._context.add_class('length-warning') + self._context.add_class("length-warning") else: - self._context.remove_class('length-warning') + self._context.remove_class("length-warning") else: self._set_count(0) - self._context.remove_class('length-warning') + self._context.remove_class("length-warning") return False def _jid_allowed(self, current_jid: JID) -> bool: - jids = self._config['JIDS'] + jids = self._config["JIDS"] if isinstance(jids, list): # Gajim 1.0 stored this as list[str] - jids = ','.join(jids) + jids = ",".join(jids) assert isinstance(jids, str) if not len(jids): # Not restricted to any JIDs return True - allowed_jids = jids.split(',') + allowed_jids = jids.split(",") for allowed_jid in allowed_jids: try: address = JID.from_string(allowed_jid.strip()) except Exception as error: - log.error('Error parsing JID: %s (%s)' % (error, allowed_jid)) + log.error("Error parsing JID: %s (%s)" % (error, allowed_jid)) continue if address.is_domain: if current_jid.domain == address: - log.debug('Show counter for Domain %s' % address) + log.debug("Show counter for Domain %s" % address) return True if current_jid == address: - log.debug('Show counter for JID %s' % address) + log.debug("Show counter for JID %s" % address) return True return False @@ -241,6 +241,6 @@ class Counter(Gtk.Label): self._update() def reset(self) -> None: - self._context.remove_class('length-warning') + self._context.remove_class("length-warning") self._parse_config() self._set_css() diff --git a/message_box_size/config_dialog.py b/message_box_size/config_dialog.py index 4fcea1d..e3692c3 100644 --- a/message_box_size/config_dialog.py +++ b/message_box_size/config_dialog.py @@ -21,11 +21,10 @@ from typing import TYPE_CHECKING from gi.repository import Gtk -from gajim.gtk.settings import SettingsDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType - +from gajim.gtk.settings import SettingsDialog from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: @@ -37,22 +36,26 @@ class MessageBoxSizeConfigDialog(SettingsDialog): self.plugin = plugin settings = [ - Setting(SettingKind.SPIN, - _('Height in pixels'), - SettingType.VALUE, - str(self.plugin.config['HEIGHT']), - callback=self._on_setting, - data='HEIGHT', - desc=_('Size of message input in pixels'), - props={'range_': (20, 200, 1)}), - ] + Setting( + SettingKind.SPIN, + _("Height in pixels"), + SettingType.VALUE, + str(self.plugin.config["HEIGHT"]), + callback=self._on_setting, + data="HEIGHT", + desc=_("Size of message input in pixels"), + props={"range_": (20, 200, 1)}, + ), + ] - SettingsDialog.__init__(self, - parent, - _('Message Box Size Configuration'), - Gtk.DialogFlags.MODAL, - settings, - '') + SettingsDialog.__init__( + self, + parent, + _("Message Box Size Configuration"), + Gtk.DialogFlags.MODAL, + settings, + "", + ) def _on_setting(self, value: Any, data: Any) -> None: self.plugin.config[data] = value diff --git a/message_box_size/msg_box_size.py b/message_box_size/msg_box_size.py index ce9d04e..28a2719 100644 --- a/message_box_size/msg_box_size.py +++ b/message_box_size/msg_box_size.py @@ -21,7 +21,6 @@ from typing import cast from functools import partial from gajim.gtk.message_input import MessageInputTextView - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ @@ -31,23 +30,20 @@ from message_box_size.config_dialog import MessageBoxSizeConfigDialog class MsgBoxSizePlugin(GajimPlugin): def init(self) -> None: # pylint: disable=attribute-defined-outside-init - self.description = _('Allows you to adjust the height ' - 'of the message input.') + self.description = _("Allows you to adjust the height " "of the message input.") self.config_dialog = partial(MessageBoxSizeConfigDialog, self) self.gui_extension_points = { - 'message_input': (self._on_message_input_created, None) + "message_input": (self._on_message_input_created, None) } self.config_default_values = { - 'HEIGHT': (20, ''), + "HEIGHT": (20, ""), } self._message_input = None - def _on_message_input_created(self, - message_input: MessageInputTextView - ) -> None: + def _on_message_input_created(self, message_input: MessageInputTextView) -> None: self._message_input = message_input - self.set_input_height(cast(int, self.config['HEIGHT'])) + self.set_input_height(cast(int, self.config["HEIGHT"])) def deactivate(self) -> None: self.set_input_height(-1) diff --git a/now_listen/gtk/config.py b/now_listen/gtk/config.py index b1e73a9..7951808 100644 --- a/now_listen/gtk/config.py +++ b/now_listen/gtk/config.py @@ -20,12 +20,11 @@ from typing import TYPE_CHECKING from gi.repository import Gtk -from gajim.plugins.plugins_i18n import _ - from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType from gajim.gtk.settings import SettingsDialog +from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: from ..now_listen import NowListenPlugin @@ -36,19 +35,24 @@ class NowListenConfigDialog(SettingsDialog): self.plugin = plugin settings = [ - Setting(SettingKind.ENTRY, - _('Format string'), - SettingType.VALUE, - self.plugin.config['format_string'], - callback=self._on_setting, data='format_string') - ] + Setting( + SettingKind.ENTRY, + _("Format string"), + SettingType.VALUE, + self.plugin.config["format_string"], + callback=self._on_setting, + data="format_string", + ) + ] - SettingsDialog.__init__(self, - parent, - _('Now Listen Configuration'), - Gtk.DialogFlags.MODAL, - settings, - '') + SettingsDialog.__init__( + self, + parent, + _("Now Listen Configuration"), + Gtk.DialogFlags.MODAL, + settings, + "", + ) def _on_setting(self, value: Any, data: Any) -> None: self.plugin.config[data] = value diff --git a/now_listen/now_listen.py b/now_listen/now_listen.py index 2111a03..d779294 100644 --- a/now_listen/now_listen.py +++ b/now_listen/now_listen.py @@ -17,45 +17,42 @@ from __future__ import annotations from typing import cast -import sys import logging +import sys from functools import partial from gi.repository import Gdk from gi.repository import GObject - from nbxmpp.structs import TuneData +from gajim.common.dbus.music_track import MusicTrackListener from gajim.gtk.message_input import MessageInputTextView - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from gajim.common.dbus.music_track import MusicTrackListener - from now_listen.gtk.config import NowListenConfigDialog - -log = logging.getLogger('gajim.p.now_listen') +log = logging.getLogger("gajim.p.now_listen") class NowListenPlugin(GajimPlugin): def init(self) -> None: # pylint: disable=attribute-defined-outside-init - self.description = _('Copy tune info of playing music to conversation ' - 'input box at cursor position (Alt + N)') + self.description = _( + "Copy tune info of playing music to conversation " + "input box at cursor position (Alt + N)" + ) self.config_dialog = partial(NowListenConfigDialog, self) self.gui_extension_points = { - 'message_input': (self._on_message_input_created, None) + "message_input": (self._on_message_input_created, None) } self.config_default_values = { - 'format_string': - (_('Now listening to: "%title" by %artist'), ''), + "format_string": (_('Now listening to: "%title" by %artist'), ""), } - if sys.platform != 'linux': - self.available_text = _('Plugin only available for Linux') + if sys.platform != "linux": + self.available_text = _("Plugin only available for Linux") self.activatable = False self._signal_id = None @@ -64,29 +61,24 @@ class NowListenPlugin(GajimPlugin): def deactivate(self) -> None: assert self._message_input is not None assert self._signal_id is not None - if GObject.signal_handler_is_connected( - self._message_input, self._signal_id): + if GObject.signal_handler_is_connected(self._message_input, self._signal_id): self._message_input.disconnect(self._signal_id) - def _on_message_input_created(self, - message_input: MessageInputTextView - ) -> None: + def _on_message_input_created(self, message_input: MessageInputTextView) -> None: self._message_input = message_input - self._signal_id = message_input.connect( - 'key-press-event', self._on_key_press) + self._signal_id = message_input.connect("key-press-event", self._on_key_press) def _get_tune_string(self, info: TuneData) -> str: - format_string = cast(str, self.config['format_string']) - tune_string = format_string.replace( - '%artist', info.artist or '').replace( - '%title', info.title or '') + format_string = cast(str, self.config["format_string"]) + tune_string = format_string.replace("%artist", info.artist or "").replace( + "%title", info.title or "" + ) return tune_string - def _on_key_press(self, - textview: MessageInputTextView, - event: Gdk.EventKey - ) -> bool: + def _on_key_press( + self, textview: MessageInputTextView, event: Gdk.EventKey + ) -> bool: # Insert text to message input box, at cursor position if event.keyval != Gdk.KEY_n: @@ -96,7 +88,7 @@ class NowListenPlugin(GajimPlugin): info = MusicTrackListener.get().current_tune if info is None: - log.info('No current tune available') + log.info("No current tune available") return False tune_string = self._get_tune_string(info) diff --git a/openpgp/backend/gpgme.py b/openpgp/backend/gpgme.py index 56a74d2..40d451f 100644 --- a/openpgp/backend/gpgme.py +++ b/openpgp/backend/gpgme.py @@ -16,15 +16,14 @@ import logging -from nbxmpp.protocol import JID - import gpg from gpg.results import ImportResult +from nbxmpp.protocol import JID from openpgp.backend.util import parse_uid from openpgp.modules.util import DecryptionFailed -log = logging.getLogger('gajim.p.openpgp.gpgme') +log = logging.getLogger("gajim.p.openpgp.gpgme") class KeyringItem: @@ -73,31 +72,33 @@ class GPGME: def __init__(self, jid, gnuhome): self._jid = jid self._context_args = { - 'home_dir': str(gnuhome), - 'offline': True, - 'armor': False, + "home_dir": str(gnuhome), + "offline": True, + "armor": False, } def generate_key(self): with gpg.Context(**self._context_args) as context: - result = context.create_key(f'xmpp:{str(self._jid)}', - algorithm='default', - expires=False, - passphrase=None, - force=False) + result = context.create_key( + f"xmpp:{str(self._jid)}", + algorithm="default", + expires=False, + passphrase=None, + force=False, + ) - log.info('Generated new key: %s', result.fpr) + log.info("Generated new key: %s", result.fpr) def get_key(self, fingerprint): with gpg.Context(**self._context_args) as context: try: key = context.get_key(fingerprint) except gpg.errors.KeyNotFound as error: - log.warning('key not found: %s', error.keystr) + log.warning("key not found: %s", error.keystr) return except Exception as error: - log.warning('get_key() error: %s', error) + log.warning("get_key() error: %s", error) return return key @@ -121,7 +122,7 @@ class GPGME: for key in context.keylist(): keyring_item = KeyringItem(key) if not keyring_item.is_xmpp_key: - log.warning('Key not suited for xmpp: %s', key.fpr) + log.warning("Key not suited for xmpp: %s", key.fpr) self.delete_key(keyring_item.fingerprint) continue @@ -157,12 +158,12 @@ class GPGME: recipients.append(key) if not recipients: - return None, 'No keys found to encrypt to' + return None, "No keys found to encrypt to" with gpg.Context(**self._context_args) as context: - result = context.encrypt(str(plaintext).encode(), - recipients, - always_trust=True) + result = context.encrypt( + str(plaintext).encode(), recipients, always_trust=True + ) ciphertext, result, _sign_result = result return ciphertext, None @@ -172,7 +173,7 @@ class GPGME: try: result = context.decrypt(ciphertext) except Exception as error: - raise DecryptionFailed('Decryption failed: %s' % error) + raise DecryptionFailed("Decryption failed: %s" % error) plaintext, result, verify_result = result plaintext = plaintext.decode() @@ -181,16 +182,16 @@ class GPGME: if not fingerprints or len(fingerprints) > 1: log.error(result) log.error(verify_result) - raise DecryptionFailed('Verification failed') + raise DecryptionFailed("Verification failed") return plaintext, fingerprints[0] def import_key(self, data, jid): - log.info('Import key from %s', jid) + log.info("Import key from %s", jid) with gpg.Context(**self._context_args) as context: result = context.key_import(data) if not isinstance(result, ImportResult) or result.imported != 1: - log.error('Key import failed: %s', jid) + log.error("Key import failed: %s", jid) log.error(result) return @@ -198,7 +199,7 @@ class GPGME: key = self.get_key(fingerprint) item = KeyringItem(key) if not item.is_valid(jid): - log.warning('Invalid key found') + log.warning("Invalid key found") log.warning(key) self.delete_key(item.fingerprint) return @@ -206,7 +207,7 @@ class GPGME: return item def delete_key(self, fingerprint): - log.info('Delete Key: %s', fingerprint) + log.info("Delete Key: %s", fingerprint) key = self.get_key(fingerprint) with gpg.Context(**self._context_args) as context: context.op_delete(key, True) diff --git a/openpgp/backend/pygpg.py b/openpgp/backend/pygpg.py index 15b3b13..c091f58 100644 --- a/openpgp/backend/pygpg.py +++ b/openpgp/backend/pygpg.py @@ -23,10 +23,9 @@ from nbxmpp.protocol import JID from openpgp.backend.util import parse_uid from openpgp.modules.util import DecryptionFailed - -log = logging.getLogger('gajim.p.openpgp.pygnupg') +log = logging.getLogger("gajim.p.openpgp.pygnupg") if log.getEffectiveLevel() == logging.DEBUG: - log = logging.getLogger('gnupg') + log = logging.getLogger("gnupg") log.addHandler(logging.StreamHandler()) log.setLevel(logging.DEBUG) @@ -50,10 +49,10 @@ class KeyringItem: @property def keyid(self) -> str: - return self._key['keyid'] + return self._key["keyid"] def _get_uid(self) -> str | None: - for uid in self._key['uids']: + for uid in self._key["uids"]: try: return parse_uid(uid) except Exception: @@ -61,7 +60,7 @@ class KeyringItem: @property def fingerprint(self): - return self._key['fingerprint'] + return self._key["fingerprint"] @property def uid(self): @@ -79,28 +78,28 @@ class KeyringItem: class PythonGnuPG(gnupg.GPG): def __init__(self, jid: str, gnupghome: Path) -> None: - gnupg.GPG.__init__(self, gpgbinary='gpg', gnupghome=str(gnupghome)) + gnupg.GPG.__init__(self, gpgbinary="gpg", gnupghome=str(gnupghome)) self._jid = jid self._own_fingerprint = None @staticmethod def _get_key_params(jid): - ''' + """ Generate --gen-key input - ''' + """ params = { - 'Key-Type': 'RSA', - 'Key-Length': 2048, - 'Name-Real': 'xmpp:%s' % jid, + "Key-Type": "RSA", + "Key-Length": 2048, + "Name-Real": "xmpp:%s" % jid, } - out = 'Key-Type: %s\n' % params.pop('Key-Type') + out = "Key-Type: %s\n" % params.pop("Key-Type") for key, val in list(params.items()): - out += '%s: %s\n' % (key, val) - out += '%no-protection\n' - out += '%commit\n' + out += "%s: %s\n" % (key, val) + out += "%no-protection\n" + out += "%commit\n" return out def generate_key(self): @@ -108,18 +107,20 @@ class PythonGnuPG(gnupg.GPG): def encrypt(self, payload, keys): recipients = [key.fingerprint for key in keys] - log.info('encrypt to:') + log.info("encrypt to:") for fingerprint in recipients: log.info(fingerprint) - result = super().encrypt(str(payload).encode('utf8'), - recipients, - armor=False, - sign=self._own_fingerprint, - always_trust=True) + result = super().encrypt( + str(payload).encode("utf8"), + recipients, + armor=False, + sign=self._own_fingerprint, + always_trust=True, + ) if result.ok: - error = '' + error = "" else: error = result.status @@ -130,7 +131,7 @@ class PythonGnuPG(gnupg.GPG): if not result.ok: raise DecryptionFailed(result.status) - return result.data.decode('utf8'), result.fingerprint + return result.data.decode("utf8"), result.fingerprint def get_key(self, fingerprint): return super().list_keys(keys=[fingerprint]) @@ -141,7 +142,7 @@ class PythonGnuPG(gnupg.GPG): for key in result: item = KeyringItem(key) if not item.is_xmpp_key: - log.warning('Invalid key found, deleting key') + log.warning("Invalid key found, deleting key") log.warning(key) self.delete_key(item.fingerprint) continue @@ -149,17 +150,17 @@ class PythonGnuPG(gnupg.GPG): return keys def import_key(self, data, jid): - log.info('Import key from %s', jid) + log.info("Import key from %s", jid) result = super().import_keys(data) if not result: - log.error('Could not import key') + log.error("Could not import key") log.error(result) return - key = self.get_key(result.results[0]['fingerprint']) + key = self.get_key(result.results[0]["fingerprint"]) item = KeyringItem(key[0]) if not item.is_valid(jid): - log.warning('Invalid key found, deleting key') + log.warning("Invalid key found, deleting key") log.warning(key) self.delete_key(item.fingerprint) return @@ -172,17 +173,16 @@ class PythonGnuPG(gnupg.GPG): return None, None if len(result) > 1: - log.error('More than one secret key found') + log.error("More than one secret key found") return None, None - self._own_fingerprint = result[0]['fingerprint'] - return self._own_fingerprint, int(result[0]['date']) + self._own_fingerprint = result[0]["fingerprint"] + return self._own_fingerprint, int(result[0]["date"]) def export_key(self, fingerprint): - key = super().export_keys( - fingerprint, secret=False, armor=False, minimal=True) + key = super().export_keys(fingerprint, secret=False, armor=False, minimal=True) return key def delete_key(self, fingerprint): - log.info('Delete Key: %s', fingerprint) + log.info("Delete Key: %s", fingerprint) super().delete_keys(fingerprint) diff --git a/openpgp/backend/sql.py b/openpgp/backend/sql.py index ae385bc..6dd468c 100644 --- a/openpgp/backend/sql.py +++ b/openpgp/backend/sql.py @@ -14,13 +14,13 @@ # You should have received a copy of the GNU General Public License # along with OpenPGP Gajim Plugin. If not, see . -import sqlite3 import logging +import sqlite3 from collections import namedtuple -log = logging.getLogger('gajim.p.openpgp.sql') +log = logging.getLogger("gajim.p.openpgp.sql") -TABLE_LAYOUT = ''' +TABLE_LAYOUT = """ CREATE TABLE contacts ( jid TEXT, fingerprint TEXT, @@ -29,13 +29,14 @@ TABLE_LAYOUT = ''' timestamp INTEGER, comment TEXT ); - CREATE UNIQUE INDEX jid_fingerprint ON contacts (jid, fingerprint);''' + CREATE UNIQUE INDEX jid_fingerprint ON contacts (jid, fingerprint);""" class Storage: def __init__(self, folder_path): - self._con = sqlite3.connect(str(folder_path / 'contacts.db'), - detect_types=sqlite3.PARSE_COLNAMES) + self._con = sqlite3.connect( + str(folder_path / "contacts.db"), detect_types=sqlite3.PARSE_COLNAMES + ) self._con.row_factory = self._namedtuple_factory self._create_database() @@ -51,11 +52,11 @@ class Storage: return named_row def _user_version(self): - return self._con.execute('PRAGMA user_version').fetchone()[0] + return self._con.execute("PRAGMA user_version").fetchone()[0] def _create_database(self): if not self._user_version(): - log.info('Create contacts.db') + log.info("Create contacts.db") self._execute_query(TABLE_LAYOUT) def _execute_query(self, query): @@ -64,41 +65,43 @@ class Storage: %s PRAGMA user_version=1; END TRANSACTION; - """ % (query) + """ % ( + query + ) self._con.executescript(transaction) def _migrate_database(self): pass def load_contacts(self): - sql = '''SELECT jid as "jid [jid]", + sql = """SELECT jid as "jid [jid]", fingerprint, active, trust, timestamp, comment - FROM contacts''' + FROM contacts""" return self._con.execute(sql).fetchall() def save_contact(self, db_values): - sql = '''REPLACE INTO + sql = """REPLACE INTO contacts(jid, fingerprint, active, trust, timestamp, comment) - VALUES(?, ?, ?, ?, ?, ?)''' + VALUES(?, ?, ?, ?, ?, ?)""" for values in db_values: - log.info('Store key: %s', values) + log.info("Store key: %s", values) self._con.execute(sql, values) self._con.commit() def set_trust(self, jid, fingerprint, trust): - sql = 'UPDATE contacts SET trust = ? WHERE jid = ? AND fingerprint = ?' - log.info('Set Trust: %s %s %s', trust, jid, fingerprint) + sql = "UPDATE contacts SET trust = ? WHERE jid = ? AND fingerprint = ?" + log.info("Set Trust: %s %s %s", trust, jid, fingerprint) self._con.execute(sql, (trust, jid, fingerprint)) self._con.commit() def delete_key(self, jid, fingerprint): - sql = 'DELETE from contacts WHERE jid = ? AND fingerprint = ?' - log.info('Delete Key: %s %s', jid, fingerprint) + sql = "DELETE from contacts WHERE jid = ? AND fingerprint = ?" + log.info("Delete Key: %s %s", jid, fingerprint) self._con.execute(sql, (jid, fingerprint)) self._con.commit() diff --git a/openpgp/backend/util.py b/openpgp/backend/util.py index e4cc329..c5e6946 100644 --- a/openpgp/backend/util.py +++ b/openpgp/backend/util.py @@ -1,13 +1,12 @@ - from __future__ import annotations def parse_uid(uid: str, compat=False) -> str: - if uid.startswith('xmpp:'): + if uid.startswith("xmpp:"): return uid[5:] # Compat with uids of form "Name " - if compat and ''): - return uid[:-1].split('"): + return uid[:-1].split(". +import logging import sys import time -import logging from pathlib import Path -from nbxmpp.namespaces import Namespace from nbxmpp import Node from nbxmpp import StanzaMalformed +from nbxmpp.errors import MalformedStanzaError +from nbxmpp.errors import StanzaError from nbxmpp.exceptions import StanzaDecrypted +from nbxmpp.modules.openpgp import create_message_stanza +from nbxmpp.modules.openpgp import create_signcrypt_node +from nbxmpp.modules.openpgp import parse_signcrypt +from nbxmpp.modules.openpgp import PGPKeyMetadata +from nbxmpp.namespaces import Namespace from nbxmpp.structs import EncryptionData from nbxmpp.structs import MessageProperties from nbxmpp.structs import StanzaHandler -from nbxmpp.errors import StanzaError -from nbxmpp.errors import MalformedStanzaError -from nbxmpp.modules.openpgp import PGPKeyMetadata -from nbxmpp.modules.openpgp import parse_signcrypt -from nbxmpp.modules.openpgp import create_signcrypt_node -from nbxmpp.modules.openpgp import create_message_stanza from gajim.common import app from gajim.common import configpaths @@ -40,22 +40,22 @@ from gajim.common.modules.base import BaseModule from gajim.common.modules.util import event_node from gajim.common.structs import OutgoingMessage -from openpgp.modules.util import ENCRYPTION_NAME -from openpgp.modules.util import NOT_ENCRYPTED_TAGS -from openpgp.modules.util import Key -from openpgp.modules.util import Trust -from openpgp.modules.util import DecryptionFailed -from openpgp.modules.util import prepare_stanza -from openpgp.modules.key_store import PGPContacts from openpgp.backend.sql import Storage +from openpgp.modules.key_store import PGPContacts +from openpgp.modules.util import DecryptionFailed +from openpgp.modules.util import ENCRYPTION_NAME +from openpgp.modules.util import Key +from openpgp.modules.util import NOT_ENCRYPTED_TAGS +from openpgp.modules.util import prepare_stanza +from openpgp.modules.util import Trust -if sys.platform == 'win32': +if sys.platform == "win32": from openpgp.backend.pygpg import PythonGnuPG as PGPBackend else: from openpgp.backend.gpgme import GPGME as PGPBackend -log = logging.getLogger('gajim.p.openpgp') +log = logging.getLogger("gajim.p.openpgp") # Module name @@ -65,24 +65,26 @@ zeroconf = False class OpenPGP(BaseModule): - _nbxmpp_extends = 'OpenPGP' + _nbxmpp_extends = "OpenPGP" _nbxmpp_methods = [ - 'set_keylist', - 'request_keylist', - 'set_public_key', - 'request_public_key', - 'set_secret_key', - 'request_secret_key', + "set_keylist", + "request_keylist", + "set_public_key", + "request_public_key", + "set_secret_key", + "request_secret_key", ] def __init__(self, client): BaseModule.__init__(self, client) self.handlers = [ - StanzaHandler(name='message', - callback=self.decrypt_message, - ns=Namespace.OPENPGP, - priority=9), + StanzaHandler( + name="message", + callback=self.decrypt_message, + ns=Namespace.OPENPGP, + priority=9, + ), ] self._register_pubsub_handler(self._keylist_notification_received) @@ -90,7 +92,7 @@ class OpenPGP(BaseModule): self.own_jid = self._client.get_own_jid() own_bare_jid = self.own_jid.bare - path = Path(configpaths.get('MY_DATA')) / 'openpgp' / own_bare_jid + path = Path(configpaths.get("MY_DATA")) / "openpgp" / own_bare_jid if not path.exists(): path.mkdir(mode=0o700, parents=True) @@ -98,7 +100,7 @@ class OpenPGP(BaseModule): self._storage = Storage(path) self._contacts = PGPContacts(self._pgp, self._storage) self._fingerprint, self._date = self.get_own_key_details() - log.info('Own Fingerprint at start: %s', self._fingerprint) + log.info("Own Fingerprint at start: %s", self._fingerprint) @property def secret_key_available(self): @@ -112,27 +114,22 @@ class OpenPGP(BaseModule): self._pgp.generate_key() def set_public_key(self): - log.info('%s => Publish public key', self._account) + log.info("%s => Publish public key", self._account) key = self._pgp.export_key(self._fingerprint) - self._nbxmpp('OpenPGP').set_public_key( - key, self._fingerprint, self._date) + self._nbxmpp("OpenPGP").set_public_key(key, self._fingerprint, self._date) def request_public_key(self, jid, fingerprint): - log.info('%s => Request public key %s - %s', - self._account, fingerprint, jid) - self._nbxmpp('OpenPGP').request_public_key( - jid, - fingerprint, - callback=self._public_key_received, - user_data=fingerprint) + log.info("%s => Request public key %s - %s", self._account, fingerprint, jid) + self._nbxmpp("OpenPGP").request_public_key( + jid, fingerprint, callback=self._public_key_received, user_data=fingerprint + ) def _public_key_received(self, task): fingerprint = task.get_user_data() try: result = task.finish() except (StanzaError, MalformedStanzaError) as error: - log.error('%s => Public Key not found: %s', - self._account, error) + log.error("%s => Public Key not found: %s", self._account, error) return imported_key = self._pgp.import_key(result.key, result.jid) @@ -142,8 +139,8 @@ class OpenPGP(BaseModule): def set_keylist(self, keylist=None): if keylist is None: keylist = [PGPKeyMetadata(None, self._fingerprint, self._date)] - log.info('%s => Publish keylist', self._account) - self._nbxmpp('OpenPGP').set_keylist(keylist) + log.info("%s => Publish keylist", self._account) + self._nbxmpp("OpenPGP").set_keylist(keylist) @event_node(Namespace.OPENPGP_PK) def _keylist_notification_received(self, _con, _stanza, properties): @@ -157,46 +154,43 @@ class OpenPGP(BaseModule): def request_keylist(self, jid=None): if jid is None: jid = self.own_jid - log.info('%s => Fetch keylist %s', self._account, jid) + log.info("%s => Fetch keylist %s", self._account, jid) - self._nbxmpp('OpenPGP').request_keylist( - jid, - callback=self._keylist_received, - user_data=jid) + self._nbxmpp("OpenPGP").request_keylist( + jid, callback=self._keylist_received, user_data=jid + ) def _keylist_received(self, task): jid = task.get_user_data() try: keylist = task.finish() except (StanzaError, MalformedStanzaError) as error: - log.error('%s => Keylist query failed: %s', - self._account, error) + log.error("%s => Keylist query failed: %s", self._account, error) if self.own_jid.bare_match(jid) and self._fingerprint is not None: self.set_keylist() return - log.info('Keylist received from %s', jid) + log.info("Keylist received from %s", jid) self._process_keylist(keylist, jid) def _process_keylist(self, keylist, from_jid): if not keylist: - log.warning('%s => Empty keylist received from %s', - self._account, from_jid) + log.warning("%s => Empty keylist received from %s", self._account, from_jid) self._contacts.process_keylist(self.own_jid, keylist) if self.own_jid.bare_match(from_jid) and self._fingerprint is not None: self.set_keylist() return if self.own_jid.bare_match(from_jid): - log.info('Received own keylist') + log.info("Received own keylist") for key in keylist: log.info(key.fingerprint) for key in keylist: # Check if own fingerprint is published if key.fingerprint == self._fingerprint: - log.info('Own key found in keys list') + log.info("Own key found in keys list") return - log.info('Own key not published') + log.info("Own key not published") if self._fingerprint is not None: keylist.append(Key(self._fingerprint, self._date)) self.set_keylist(keylist) @@ -228,31 +222,29 @@ class OpenPGP(BaseModule): try: payload, recipients, _timestamp = parse_signcrypt(signcrypt) except StanzaMalformed as error: - log.warning('Decryption failed: %s', error) + log.warning("Decryption failed: %s", error) log.warning(payload) return if not any(map(self.own_jid.bare_match, recipients)): - log.warning('to attr not valid') + log.warning("to attr not valid") log.warning(signcrypt) return keys = self._contacts.get_keys(remote_jid) fingerprints = [key.fingerprint for key in keys] if fingerprint not in fingerprints: - log.warning('Invalid fingerprint on message: %s', fingerprint) - log.warning('Expected: %s', fingerprints) + log.warning("Invalid fingerprint on message: %s", fingerprint) + log.warning("Expected: %s", fingerprints) return - log.info('Received OpenPGP message from: %s', properties.jid) + log.info("Received OpenPGP message from: %s", properties.jid) prepare_stanza(stanza, payload) trust = self._contacts.get_trust(remote_jid, fingerprint) properties.encrypted = EncryptionData( - protocol=ENCRYPTION_NAME, - key=fingerprint, - trust=trust + protocol=ENCRYPTION_NAME, key=fingerprint, trust=trust ) raise StanzaDecrypted @@ -262,39 +254,38 @@ class OpenPGP(BaseModule): keys = self._contacts.get_keys(remote_jid) if not keys: - log.error('Droping stanza to %s, because we have no key', remote_jid) + log.error("Droping stanza to %s, because we have no key", remote_jid) return keys += self._contacts.get_keys(self.own_jid) keys += [Key(self._fingerprint, None)] - payload = create_signcrypt_node(message.get_stanza(), - [remote_jid], - NOT_ENCRYPTED_TAGS) + payload = create_signcrypt_node( + message.get_stanza(), [remote_jid], NOT_ENCRYPTED_TAGS + ) encrypted_payload, error = self._pgp.encrypt(payload, keys) if error: - log.error('Error: %s', error) - text = message.get_text(with_fallback=False) or '' + log.error("Error: %s", error) + text = message.get_text(with_fallback=False) or "" app.ged.raise_event( - MessageNotSent(client=self._client, - jid=str(remote_jid), - message=text, - error=error, - time=time.time())) + MessageNotSent( + client=self._client, + jid=str(remote_jid), + message=text, + error=error, + time=time.time(), + ) + ) return create_message_stanza( - message.get_stanza(), - encrypted_payload, - bool(message.get_text()) + message.get_stanza(), encrypted_payload, bool(message.get_text()) ) message.set_encryption( EncryptionData( - protocol=ENCRYPTION_NAME, - key='Unknown', - trust=Trust.VERIFIED + protocol=ENCRYPTION_NAME, key="Unknown", trust=Trust.VERIFIED ) ) @@ -302,12 +293,12 @@ class OpenPGP(BaseModule): @staticmethod def print_msg_to_log(stanza): - """ Prints a stanza in a fancy way to the log """ - log.debug('-'*15) - stanzastr = '\n' + stanza.__str__(fancy=True) + """Prints a stanza in a fancy way to the log""" + log.debug("-" * 15) + stanzastr = "\n" + stanza.__str__(fancy=True) stanzastr = stanzastr[0:-1] log.debug(stanzastr) - log.debug('-'*15) + log.debug("-" * 15) def get_keys(self, jid=None, only_trusted=True): if jid is None: @@ -324,4 +315,4 @@ class OpenPGP(BaseModule): def get_instance(*args, **kwargs): - return OpenPGP(*args, **kwargs), 'OpenPGP' + return OpenPGP(*args, **kwargs), "OpenPGP" diff --git a/openpgp/modules/util.py b/openpgp/modules/util.py index b8de1a7..99e01e4 100644 --- a/openpgp/modules/util.py +++ b/openpgp/modules/util.py @@ -14,24 +14,23 @@ # You should have received a copy of the GNU General Public License # along with OpenPGP Gajim Plugin. If not, see . -from enum import IntEnum from collections import namedtuple +from enum import IntEnum from nbxmpp.namespaces import Namespace - -ENCRYPTION_NAME = 'OpenPGP' +ENCRYPTION_NAME = "OpenPGP" NOT_ENCRYPTED_TAGS = [ - ('no-store', Namespace.HINTS), - ('store', Namespace.HINTS), - ('no-copy', Namespace.HINTS), - ('no-permanent-store', Namespace.HINTS), - ('origin-id', Namespace.SID), - ('thread', None) + ("no-store", Namespace.HINTS), + ("store", Namespace.HINTS), + ("no-copy", Namespace.HINTS), + ("no-permanent-store", Namespace.HINTS), + ("origin-id", Namespace.SID), + ("thread", None), ] -Key = namedtuple('Key', 'fingerprint date') +Key = namedtuple("Key", "fingerprint date") class Trust(IntEnum): @@ -42,8 +41,8 @@ class Trust(IntEnum): def prepare_stanza(stanza, payload): - delete_nodes(stanza, 'openpgp', Namespace.OPENPGP) - delete_nodes(stanza, 'body') + delete_nodes(stanza, "openpgp", Namespace.OPENPGP) + delete_nodes(stanza, "body") nodes = [(node.getName(), node.getNamespace()) for node in payload] for name, namespace in nodes: @@ -56,7 +55,7 @@ def prepare_stanza(stanza, payload): def delete_nodes(stanza, name, namespace=None): attrs = None if namespace is not None: - attrs = {'xmlns': Namespace.OPENPGP} + attrs = {"xmlns": Namespace.OPENPGP} nodes = stanza.getTags(name, attrs) for node in nodes: stanza.delChild(node) diff --git a/openpgp/pgpplugin.py b/openpgp/pgpplugin.py index ceb0198..3dfef69 100644 --- a/openpgp/pgpplugin.py +++ b/openpgp/pgpplugin.py @@ -17,22 +17,21 @@ import logging from pathlib import Path -from gi.repository import Gtk from gi.repository import Gdk -from nbxmpp.namespaces import Namespace +from gi.repository import Gtk from nbxmpp import JID +from nbxmpp.namespaces import Namespace from gajim.common import app -from gajim.common import ged from gajim.common import configpaths +from gajim.common import ged from gajim.common.const import CSSPriority - from gajim.gtk.dialogs import SimpleDialog - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ from openpgp.modules.util import ENCRYPTION_NAME + try: from openpgp.modules import openpgp except (ImportError, OSError) as e: @@ -40,7 +39,7 @@ except (ImportError, OSError) as e: else: ERROR_MSG = None -log = logging.getLogger('gajim.p.openpgp') +log = logging.getLogger("gajim.p.openpgp") class OpenPGPPlugin(GajimPlugin): @@ -52,23 +51,21 @@ class OpenPGPPlugin(GajimPlugin): return self.events_handlers = { - 'signed-in': (ged.PRECORE, self.signed_in), - } + "signed-in": (ged.PRECORE, self.signed_in), + } self.modules = [openpgp] self.encryption_name = ENCRYPTION_NAME self.config_dialog = None self.gui_extension_points = { - 'encrypt' + self.encryption_name: (self._encrypt_message, None), - 'send_message' + self.encryption_name: ( - self._before_sendmessage, None), - 'encryption_dialog' + self.encryption_name: ( - self.on_encryption_button_clicked, None), - 'encryption_state' + self.encryption_name: ( - self.encryption_state, None), - 'update_caps': (self._update_caps, None), - } + "encrypt" + self.encryption_name: (self._encrypt_message, None), + "send_message" + self.encryption_name: (self._before_sendmessage, None), + "encryption_dialog" + + self.encryption_name: (self.on_encryption_button_clicked, None), + "encryption_state" + self.encryption_name: (self.encryption_state, None), + "update_caps": (self._update_caps, None), + } self.connections = {} @@ -80,74 +77,78 @@ class OpenPGPPlugin(GajimPlugin): self._load_css() def _load_css(self): - path = Path(__file__).parent / 'gtk' / 'style.css' + path = Path(__file__).parent / "gtk" / "style.css" try: - with path.open('r') as f: + with path.open("r") as f: css = f.read() except Exception as exc: - log.error('Error loading css: %s', exc) + log.error("Error loading css: %s", exc) return try: provider = Gtk.CssProvider() - provider.load_from_data(bytes(css.encode('utf-8'))) - Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), - provider, - CSSPriority.DEFAULT_THEME) + provider.load_from_data(bytes(css.encode("utf-8"))) + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), provider, CSSPriority.DEFAULT_THEME + ) except Exception: - log.exception('Error loading application css') + log.exception("Error loading application css") @staticmethod def _create_paths(): - keyring_path = Path(configpaths.get('MY_DATA')) / 'openpgp' + keyring_path = Path(configpaths.get("MY_DATA")) / "openpgp" if not keyring_path.exists(): keyring_path.mkdir() def signed_in(self, event): client = app.get_client(event.account) - if client.get_module('OpenPGP').secret_key_available: - log.info('%s => Publish keylist and public key after sign in', - event.account) - client.get_module('OpenPGP').request_keylist() - client.get_module('OpenPGP').set_public_key() + if client.get_module("OpenPGP").secret_key_available: + log.info( + "%s => Publish keylist and public key after sign in", event.account + ) + client.get_module("OpenPGP").request_keylist() + client.get_module("OpenPGP").set_public_key() def activate(self): for account in app.settings.get_active_accounts(): client = app.get_client(account) - client.get_module('Caps').update_caps() + client.get_module("Caps").update_caps() if app.account_is_connected(account): - if client.get_module('OpenPGP').secret_key_available: - log.info('%s => Publish keylist and public key ' - 'after plugin activation', account) - client.get_module('OpenPGP').request_keylist() - client.get_module('OpenPGP').set_public_key() + if client.get_module("OpenPGP").secret_key_available: + log.info( + "%s => Publish keylist and public key " + "after plugin activation", + account, + ) + client.get_module("OpenPGP").request_keylist() + client.get_module("OpenPGP").set_public_key() def deactivate(self): pass @staticmethod def _update_caps(_account, features): - features.append('%s+notify' % Namespace.OPENPGP_PK) + features.append("%s+notify" % Namespace.OPENPGP_PK) def activate_encryption(self, chat_control): account = chat_control.account jid = chat_control.contact.jid client = app.get_client(account) - if client.get_module('OpenPGP').secret_key_available: - keys = client.get_module('OpenPGP').get_keys( - jid, only_trusted=False) + if client.get_module("OpenPGP").secret_key_available: + keys = client.get_module("OpenPGP").get_keys(jid, only_trusted=False) if not keys: - client.get_module('OpenPGP').request_keylist(JID.from_string(jid)) + client.get_module("OpenPGP").request_keylist(JID.from_string(jid)) return True from openpgp.gtk.wizard import KeyWizard + KeyWizard(self, account, chat_control) return False @staticmethod def encryption_state(_chat_control, state): - state['authenticated'] = True - state['visible'] = True + state["authenticated"] = True + state["visible"] = True @staticmethod def on_encryption_button_clicked(chat_control): @@ -155,6 +156,7 @@ class OpenPGPPlugin(GajimPlugin): jid = chat_control.contact.jid from openpgp.gtk.key import KeyDialog + KeyDialog(account, jid, app.window) def _before_sendmessage(self, chat_control): @@ -162,20 +164,21 @@ class OpenPGPPlugin(GajimPlugin): jid = chat_control.contact.jid client = app.get_client(account) - if not client.get_module('OpenPGP').secret_key_available: + if not client.get_module("OpenPGP").secret_key_available: from openpgp.gtk.wizard import KeyWizard + KeyWizard(self, account, chat_control) return - keys = client.get_module('OpenPGP').get_keys(jid) + keys = client.get_module("OpenPGP").get_keys(jid) if not keys: SimpleDialog( - _('Not Trusted'), - _('There was no trusted and active key found')) + _("Not Trusted"), _("There was no trusted and active key found") + ) chat_control.sendmessage = False @staticmethod def _encrypt_message(client, obj, callback): - if not client.get_module('OpenPGP').secret_key_available: + if not client.get_module("OpenPGP").secret_key_available: return - client.get_module('OpenPGP').encrypt_message(obj, callback) + client.get_module("OpenPGP").encrypt_message(obj, callback) diff --git a/pgp/backend/python_gnupg.py b/pgp/backend/python_gnupg.py index dbad0df..ed6b789 100644 --- a/pgp/backend/python_gnupg.py +++ b/pgp/backend/python_gnupg.py @@ -20,8 +20,8 @@ # You should have received a copy of the GNU General Public License # along with PGP Gajim Plugin. If not, see . -import os import logging +import os from functools import lru_cache import gnupg @@ -30,56 +30,51 @@ from gajim.common.util.classes import Singleton from pgp.exceptions import SignError - -logger = logging.getLogger('gajim.p.pgplegacy') +logger = logging.getLogger("gajim.p.pgplegacy") if logger.getEffectiveLevel() == logging.DEBUG: - logger = logging.getLogger('gnupg') + logger = logging.getLogger("gnupg") logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) class PGP(gnupg.GPG, metaclass=Singleton): def __init__(self, binary, encoding=None): - super().__init__(gpgbinary=binary, - use_agent=True) + super().__init__(gpgbinary=binary, use_agent=True) if encoding is not None: self.encoding = encoding - self.decode_errors = 'replace' + self.decode_errors = "replace" def encrypt(self, payload, recipients, always_trust=False): if not always_trust: # check that we'll be able to encrypt result = self.get_key(recipients[0]) for key in result: - if key['trust'] not in ('f', 'u'): - return '', 'NOT_TRUSTED ' + key['keyid'][-8:] + if key["trust"] not in ("f", "u"): + return "", "NOT_TRUSTED " + key["keyid"][-8:] result = super().encrypt( - payload.encode('utf8'), - recipients, - always_trust=always_trust) + payload.encode("utf8"), recipients, always_trust=always_trust + ) if result.ok: - error = '' + error = "" else: error = result.status return self._strip_header_footer(str(result)), error def decrypt(self, payload): - data = self._add_header_footer(payload, 'MESSAGE') - result = super().decrypt(data.encode('utf8')) + data = self._add_header_footer(payload, "MESSAGE") + result = super().decrypt(data.encode("utf8")) - return result.data.decode('utf8') + return result.data.decode("utf8") @lru_cache(maxsize=8) def sign(self, payload, key_id): if payload is None: - payload = '' - result = super().sign(payload.encode('utf8'), - keyid=key_id, - detach=True) + payload = "" + result = super().sign(payload.encode("utf8"), keyid=key_id, detach=True) if result.fingerprint: return self._strip_header_footer(str(result)) @@ -91,19 +86,20 @@ class PGP(gnupg.GPG, metaclass=Singleton): # Text name for hash algorithms from RFC 4880 - section 9.4 if payload is None: - payload = '' + payload = "" - hash_algorithms = ['SHA512', 'SHA384', 'SHA256', - 'SHA224', 'SHA1', 'RIPEMD160'] + hash_algorithms = ["SHA512", "SHA384", "SHA256", "SHA224", "SHA1", "RIPEMD160"] for algo in hash_algorithms: data = os.linesep.join( - ['-----BEGIN PGP SIGNED MESSAGE-----', - 'Hash: ' + algo, - '', - payload, - self._add_header_footer(signed, 'SIGNATURE')] - ) - result = super().verify(data.encode('utf8')) + [ + "-----BEGIN PGP SIGNED MESSAGE-----", + "Hash: " + algo, + "", + payload, + self._add_header_footer(signed, "SIGNATURE"), + ] + ) + result = super().verify(data.encode("utf8")) if result.valid: return result.fingerprint @@ -116,7 +112,7 @@ class PGP(gnupg.GPG, metaclass=Singleton): for key in result: # Take first not empty uid - keys[key['fingerprint']] = next(uid for uid in key['uids'] if uid) + keys[key["fingerprint"]] = next(uid for uid in key["uids"] if uid) return keys @staticmethod @@ -125,19 +121,19 @@ class PGP(gnupg.GPG, metaclass=Singleton): Remove header and footer from data """ if not data: - return '' + return "" lines = data.splitlines() - while lines[0] != '': + while lines[0] != "": lines.remove(lines[0]) - while lines[0] == '': + while lines[0] == "": lines.remove(lines[0]) i = 0 for line in lines: if line: - if line[0] == '-': + if line[0] == "-": break - i = i+1 - line = '\n'.join(lines[0:i]) + i = i + 1 + line = "\n".join(lines[0:i]) return line @staticmethod diff --git a/pgp/backend/store.py b/pgp/backend/store.py index 0a484ba..1090c46 100644 --- a/pgp/backend/store.py +++ b/pgp/backend/store.py @@ -34,31 +34,30 @@ class KeyStore: self._account = account own_bare_jid = own_jid.bare - path = Path(configpaths.get('PLUGINS_DATA')) / 'pgplegacy' / own_bare_jid + path = Path(configpaths.get("PLUGINS_DATA")) / "pgplegacy" / own_bare_jid if not path.exists(): path.mkdir(parents=True) - self._store_path = path / 'store' + self._store_path = path / "store" if self._store_path.exists(): # having store v2 or higher - with self._store_path.open('r') as file: + with self._store_path.open("r") as file: try: self._store = json.load(file) except Exception: - log.exception('Could not load config') + log.exception("Could not load config") self._store = self._empty_store() - ver = self._store.get('_version', 2) + ver = self._store.get("_version", 2) if ver > CURRENT_STORE_VERSION: - raise Exception('Unknown store version! ' - 'Please upgrade pgp plugin.') + raise Exception("Unknown store version! " "Please upgrade pgp plugin.") elif ver == 2: self._migrate_v2_store() self._save_store() elif ver != CURRENT_STORE_VERSION: # garbled version self._store = self._empty_store() - log.warning('Bad pgp key store version. Initializing new.') + log.warning("Bad pgp key store version. Initializing new.") else: # having store v1 or fresh install self._store = self._empty_store() @@ -69,15 +68,16 @@ class KeyStore: @staticmethod def _empty_store(): return { - '_version': CURRENT_STORE_VERSION, - 'own_key_data': None, - 'contact_key_data': {}, + "_version": CURRENT_STORE_VERSION, + "own_key_data": None, + "contact_key_data": {}, } def _migrate_v1_store(self): keys = {} attached_keys = app.settings.get_account_setting( - self._account, 'attached_gpg_keys') + self._account, "attached_gpg_keys" + ) if not attached_keys: return attached_keys = attached_keys.split() @@ -86,23 +86,25 @@ class KeyStore: keys[attached_keys[2 * i]] = attached_keys[2 * i + 1] for jid, key_id in keys.items(): - self._set_contact_key_data_nosync(jid, (key_id, '')) + self._set_contact_key_data_nosync(jid, (key_id, "")) - own_key_id = app.settings.get_account_setting(self._account, 'keyid') - own_key_user = app.settings.get_account_setting( - self._account, 'keyname') + own_key_id = app.settings.get_account_setting(self._account, "keyid") + own_key_user = app.settings.get_account_setting(self._account, "keyname") if own_key_id: self._set_own_key_data_nosync((own_key_id, own_key_user)) attached_keys = app.settings.set_account_setting( - self._account, 'attached_gpg_keys', '') - self._log.info('Migration from store v1 was successful') + self._account, "attached_gpg_keys", "" + ) + self._log.info("Migration from store v1 was successful") def _migrate_v2_store(self): own_key_data = self.get_own_key_data() if own_key_data is not None: - own_key_id, own_key_user = (own_key_data['key_id'], - own_key_data['key_user']) + own_key_id, own_key_user = ( + own_key_data["key_id"], + own_key_data["key_user"], + ) try: own_key_fp = self._resolve_short_id(own_key_id, has_secret=True) self._set_own_key_data_nosync((own_key_fp, own_key_user)) @@ -111,38 +113,41 @@ class KeyStore: prune_list = [] - for dict_key, key_data in self._store['contact_key_data'].items(): + for dict_key, key_data in self._store["contact_key_data"].items(): try: - key_data['key_id'] = self._resolve_short_id(key_data['key_id']) + key_data["key_id"] = self._resolve_short_id(key_data["key_id"]) except KeyResolveError: prune_list.append(dict_key) for dict_key in prune_list: - del self._store['contact_key_data'][dict_key] + del self._store["contact_key_data"][dict_key] - self._store['_version'] = CURRENT_STORE_VERSION - self._log.info('Migration from store v2 was successful') + self._store["_version"] = CURRENT_STORE_VERSION + self._log.info("Migration from store v2 was successful") def _save_store(self): - with self._store_path.open('w') as file: + with self._store_path.open("w") as file: json.dump(self._store, file) def _get_dict_key(self, jid): - return '%s-%s' % (self._account, jid) + return "%s-%s" % (self._account, jid) def _resolve_short_id(self, short_id, has_secret=False): candidates = self._list_keys_func( - secret=has_secret, keys=(short_id,)).fingerprints + secret=has_secret, keys=(short_id,) + ).fingerprints if len(candidates) == 1: return candidates[0] elif len(candidates) > 1: - self._log.critical('Key collision during migration. ' - 'Key ID is %s. Removing binding...', - repr(short_id)) + self._log.critical( + "Key collision during migration. " "Key ID is %s. Removing binding...", + repr(short_id), + ) else: - self._log.warning('Key %s was not found during migration. ' - 'Removing binding...', - repr(short_id)) + self._log.warning( + "Key %s was not found during migration. " "Removing binding...", + repr(short_id), + ) raise KeyResolveError def set_own_key_data(self, key_data): @@ -151,18 +156,18 @@ class KeyStore: def _set_own_key_data_nosync(self, key_data): if key_data is None: - self._store['own_key_data'] = None + self._store["own_key_data"] = None else: - self._store['own_key_data'] = { - 'key_id': key_data[0], - 'key_user': key_data[1] + self._store["own_key_data"] = { + "key_id": key_data[0], + "key_user": key_data[1], } def get_own_key_data(self): - return self._store['own_key_data'] + return self._store["own_key_data"] def get_contact_key_data(self, jid): - key_ids = self._store['contact_key_data'] + key_ids = self._store["contact_key_data"] dict_key = self._get_dict_key(jid) return key_ids.get(dict_key) @@ -171,12 +176,9 @@ class KeyStore: self._save_store() def _set_contact_key_data_nosync(self, jid, key_data): - key_ids = self._store['contact_key_data'] + key_ids = self._store["contact_key_data"] dict_key = self._get_dict_key(jid) if key_data is None: key_ids[dict_key] = None else: - key_ids[dict_key] = { - 'key_id': key_data[0], - 'key_user': key_data[1] - } + key_ids[dict_key] = {"key_id": key_data[0], "key_user": key_data[1]} diff --git a/pgp/exceptions.py b/pgp/exceptions.py index a8fb64c..d5fd929 100644 --- a/pgp/exceptions.py +++ b/pgp/exceptions.py @@ -14,11 +14,14 @@ # You should have received a copy of the GNU General Public License # along with PGP Gajim Plugin. If not, see . + class SignError(Exception): pass + class KeyMismatch(Exception): pass + class NoKeyIdFound(Exception): pass diff --git a/pgp/gtk/config.py b/pgp/gtk/config.py index 2f4831d..b407c42 100644 --- a/pgp/gtk/config.py +++ b/pgp/gtk/config.py @@ -16,12 +16,11 @@ from pathlib import Path -from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GLib +from gi.repository import Gtk from gajim.common import app - from gajim.plugins.helpers import get_builder from gajim.plugins.plugins_i18n import _ @@ -33,14 +32,14 @@ class PGPConfigDialog(Gtk.ApplicationWindow): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_show_menubar(False) - self.set_title(_('PGP Configuration')) + self.set_title(_("PGP Configuration")) self.set_transient_for(parent) self.set_resizable(True) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.set_destroy_with_parent(True) ui_path = Path(__file__).parent - self._ui = get_builder(ui_path.resolve() / 'config.ui') + self._ui = get_builder(ui_path.resolve() / "config.ui") self.add(self._ui.config_box) @@ -50,9 +49,7 @@ class PGPConfigDialog(Gtk.ApplicationWindow): for account in app.settings.get_active_accounts(): page = Page(plugin, account) - self._ui.stack.add_titled(page, - account, - app.get_account_label(account)) + self._ui.stack.add_titled(page, account, app.get_account_label(account)) self.show_all() @@ -64,11 +61,11 @@ class Page(Gtk.Box): self._client = app.get_client(account) self._plugin = plugin self._label = Gtk.Label() - self._button = Gtk.Button(label=_('Assign Key')) - self._button.get_style_context().add_class('suggested-action') + self._button = Gtk.Button(label=_("Assign Key")) + self._button.get_style_context().add_class("suggested-action") self._button.set_halign(Gtk.Align.CENTER) self._button.set_margin_top(18) - self._button.connect('clicked', self._on_assign) + self._button.connect("clicked", self._on_assign) self._load_key() self.add(self._label) @@ -76,34 +73,34 @@ class Page(Gtk.Box): self.show_all() def _on_assign(self, _button): - backend = self._client.get_module('PGPLegacy').pgp_backend + backend = self._client.get_module("PGPLegacy").pgp_backend secret_keys = backend.get_keys(secret=True) dialog = ChooseGPGKeyDialog(secret_keys, self.get_toplevel()) - dialog.connect('response', self._on_response) + dialog.connect("response", self._on_response) def _load_key(self): - key_data = self._client.get_module('PGPLegacy').get_own_key_data() + key_data = self._client.get_module("PGPLegacy").get_own_key_data() if key_data is None: self._set_key(None) else: - self._set_key((key_data['key_id'], key_data['key_user'])) + self._set_key((key_data["key_id"], key_data["key_user"])) def _on_response(self, dialog, response): if response != Gtk.ResponseType.OK: return if dialog.selected_key is None: - self._client.get_module('PGPLegacy').set_own_key_data(None) + self._client.get_module("PGPLegacy").set_own_key_data(None) self._set_key(None) else: - self._client.get_module('PGPLegacy').set_own_key_data( - dialog.selected_key) + self._client.get_module("PGPLegacy").set_own_key_data(dialog.selected_key) self._set_key(dialog.selected_key) def _set_key(self, key_data): if key_data is None: - self._label.set_text(_('No key assigned')) + self._label.set_text(_("No key assigned")) else: key_id, key_user = key_data - self._label.set_markup('%s %s' % \ - (key_id, GLib.markup_escape_text(key_user))) + self._label.set_markup( + "%s %s" % (key_id, GLib.markup_escape_text(key_user)) + ) diff --git a/pgp/gtk/key.py b/pgp/gtk/key.py index 3a48c75..46d9426 100644 --- a/pgp/gtk/key.py +++ b/pgp/gtk/key.py @@ -16,18 +16,17 @@ from pathlib import Path -from gi.repository import Gtk from gi.repository import GLib +from gi.repository import Gtk from gajim.common import app -from gajim.plugins.plugins_i18n import _ from gajim.plugins.helpers import get_builder +from gajim.plugins.plugins_i18n import _ class KeyDialog(Gtk.Dialog): def __init__(self, plugin, account, jid, transient): - super().__init__(title=_('Assign key for %s') % jid, - destroy_with_parent=True) + super().__init__(title=_("Assign key for %s") % jid, destroy_with_parent=True) self.set_transient_for(transient) self.set_resizable(True) @@ -39,11 +38,11 @@ class KeyDialog(Gtk.Dialog): self._label = Gtk.Label() - self._assign_button = Gtk.Button(label=_('Assign Key')) - self._assign_button.get_style_context().add_class('suggested-action') + self._assign_button = Gtk.Button(label=_("Assign Key")) + self._assign_button.get_style_context().add_class("suggested-action") self._assign_button.set_halign(Gtk.Align.CENTER) self._assign_button.set_margin_top(18) - self._assign_button.connect('clicked', self._choose_key) + self._assign_button.connect("clicked", self._choose_key) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box.set_border_width(18) @@ -57,13 +56,12 @@ class KeyDialog(Gtk.Dialog): self.show_all() def _choose_key(self, *args): - backend = self._client.get_module('PGPLegacy').pgp_backend + backend = self._client.get_module("PGPLegacy").pgp_backend dialog = ChooseGPGKeyDialog(backend.get_keys(), self) - dialog.connect('response', self._on_response) + dialog.connect("response", self._on_response) def _load_key(self): - key_data = self._client.get_module('PGPLegacy').get_contact_key_data( - self._jid) + key_data = self._client.get_module("PGPLegacy").get_contact_key_data(self._jid) if key_data is None: self._set_key(None) else: @@ -74,42 +72,43 @@ class KeyDialog(Gtk.Dialog): return if dialog.selected_key is None: - self._client.get_module('PGPLegacy').set_contact_key_data( - self._jid, None) + self._client.get_module("PGPLegacy").set_contact_key_data(self._jid, None) self._set_key(None) else: - self._client.get_module('PGPLegacy').set_contact_key_data( - self._jid, dialog.selected_key) + self._client.get_module("PGPLegacy").set_contact_key_data( + self._jid, dialog.selected_key + ) self._set_key(dialog.selected_key) def _set_key(self, key_data): if key_data is None: - self._label.set_text(_('No key assigned')) + self._label.set_text(_("No key assigned")) else: key_id, key_user = key_data - self._label.set_markup('%s %s' % \ - (key_id, GLib.markup_escape_text(key_user))) + self._label.set_markup( + "%s %s" % (key_id, GLib.markup_escape_text(key_user)) + ) class ChooseGPGKeyDialog(Gtk.Dialog): def __init__(self, secret_keys, transient_for): - Gtk.Dialog.__init__(self, - title=_('Assign PGP Key'), - transient_for=transient_for) + Gtk.Dialog.__init__( + self, title=_("Assign PGP Key"), transient_for=transient_for + ) - secret_keys[_('None')] = _('None') + secret_keys[_("None")] = _("None") self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.set_resizable(True) self.set_default_size(500, 300) - self.add_button(_('Cancel'), Gtk.ResponseType.CANCEL) - self.add_button(_('OK'), Gtk.ResponseType.OK) + self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL) + self.add_button(_("OK"), Gtk.ResponseType.OK) self._selected_key = None ui_path = Path(__file__).parent - self._ui = get_builder(ui_path.resolve() / 'choose_key.ui') + self._ui = get_builder(ui_path.resolve() / "choose_key.ui") self._ui.keys_treeview = self._ui.keys_treeview @@ -124,7 +123,7 @@ class ChooseGPGKeyDialog(Gtk.Dialog): self._ui.connect_signals(self) - self.connect_after('response', self._on_response) + self.connect_after("response", self._on_response) self.show_all() @@ -136,9 +135,9 @@ class ChooseGPGKeyDialog(Gtk.Dialog): def _sort(model, iter1, iter2, _data): value1 = model[iter1][1] value2 = model[iter2][1] - if value1 == _('None'): + if value1 == _("None"): return -1 - if value2 == _('None'): + if value2 == _("None"): return 1 if value1 < value2: return -1 @@ -154,7 +153,7 @@ class ChooseGPGKeyDialog(Gtk.Dialog): self._selected_key = None else: key_id, key_user = model[iter_][0], model[iter_][1] - if key_id == _('None'): + if key_id == _("None"): self._selected_key = None else: self._selected_key = key_id, key_user diff --git a/pgp/modules/events.py b/pgp/modules/events.py index fc03533..e12d9cb 100644 --- a/pgp/modules/events.py +++ b/pgp/modules/events.py @@ -12,9 +12,10 @@ # You should have received a copy of the GNU General Public License # along with OMEMO Gajim Plugin. If not, see . -from __future__ import annotations +from __future__ import annotations -from typing import Any, Callable +from typing import Any +from typing import Callable from dataclasses import dataclass from dataclasses import field @@ -24,12 +25,12 @@ from gajim.common.events import ApplicationEvent @dataclass class PGPNotTrusted(ApplicationEvent): - name: str = field(init=False, default='pgp-not-trusted') + name: str = field(init=False, default="pgp-not-trusted") on_yes: Callable[..., Any] on_no: Callable[..., Any] @dataclass class PGPFileEncryptionError(ApplicationEvent): - name: str = field(init=False, default='pgp-file-encryption-error') + name: str = field(init=False, default="pgp-file-encryption-error") error: str diff --git a/pgp/modules/pgp_legacy.py b/pgp/modules/pgp_legacy.py index 0cbb015..0bd3ed4 100644 --- a/pgp/modules/pgp_legacy.py +++ b/pgp/modules/pgp_legacy.py @@ -15,57 +15,55 @@ # along with PGP Gajim Plugin. If not, see . import os -import time import threading +import time import nbxmpp +from gi.repository import GLib from nbxmpp.namespaces import Namespace from nbxmpp.protocol import Message from nbxmpp.structs import EncryptionData from nbxmpp.structs import StanzaHandler -from gi.repository import GLib from gajim.common import app from gajim.common.const import Trust from gajim.common.events import MessageNotSent -from gajim.common.structs import OutgoingMessage from gajim.common.modules.base import BaseModule - +from gajim.common.structs import OutgoingMessage from gajim.plugins.plugins_i18n import _ from pgp.backend.python_gnupg import PGP +from pgp.backend.store import KeyStore +from pgp.exceptions import KeyMismatch +from pgp.exceptions import NoKeyIdFound +from pgp.exceptions import SignError from pgp.modules.events import PGPFileEncryptionError from pgp.modules.events import PGPNotTrusted from pgp.modules.util import prepare_stanza -from pgp.backend.store import KeyStore -from pgp.exceptions import SignError -from pgp.exceptions import KeyMismatch -from pgp.exceptions import NoKeyIdFound - # Module name -name = 'PGPLegacy' +name = "PGPLegacy" zeroconf = True -ENCRYPTION_NAME = 'PGP' +ENCRYPTION_NAME = "PGP" ALLOWED_TAGS = [ - ('request', Namespace.RECEIPTS), - ('active', Namespace.CHATSTATES), - ('gone', Namespace.CHATSTATES), - ('inactive', Namespace.CHATSTATES), - ('paused', Namespace.CHATSTATES), - ('composing', Namespace.CHATSTATES), - ('markable', Namespace.CHATMARKERS), - ('no-store', Namespace.HINTS), - ('store', Namespace.HINTS), - ('no-copy', Namespace.HINTS), - ('no-permanent-store', Namespace.HINTS), - ('replace', Namespace.CORRECT), - ('thread', None), - ('reply', Namespace.REPLY), - ('fallback', Namespace.FALLBACK), - ('origin-id', Namespace.SID), - ('reactions', Namespace.REACTIONS), + ("request", Namespace.RECEIPTS), + ("active", Namespace.CHATSTATES), + ("gone", Namespace.CHATSTATES), + ("inactive", Namespace.CHATSTATES), + ("paused", Namespace.CHATSTATES), + ("composing", Namespace.CHATSTATES), + ("markable", Namespace.CHATMARKERS), + ("no-store", Namespace.HINTS), + ("store", Namespace.HINTS), + ("no-copy", Namespace.HINTS), + ("no-permanent-store", Namespace.HINTS), + ("replace", Namespace.CORRECT), + ("thread", None), + ("reply", Namespace.REPLY), + ("fallback", Namespace.FALLBACK), + ("origin-id", Namespace.SID), + ("reactions", Namespace.REACTIONS), ] @@ -74,21 +72,26 @@ class PGPLegacy(BaseModule): BaseModule.__init__(self, client, plugin=True) self.handlers = [ - StanzaHandler(name='message', - callback=self._message_received, - ns=Namespace.ENCRYPTED, - priority=9), - StanzaHandler(name='presence', - callback=self._on_presence_received, - ns=Namespace.SIGNED, - priority=48), + StanzaHandler( + name="message", + callback=self._message_received, + ns=Namespace.ENCRYPTED, + priority=9, + ), + StanzaHandler( + name="presence", + callback=self._on_presence_received, + ns=Namespace.SIGNED, + priority=48, + ), ] self.own_jid = self._client.get_own_jid() self._pgp = PGP() - self._store = KeyStore(self._account, self.own_jid, self._log, - self._pgp.list_keys) + self._store = KeyStore( + self._account, self.own_jid, self._log, self._pgp.list_keys + ) self._always_trust = [] self._presence_fingerprint_store = {} @@ -112,7 +115,7 @@ class PGPLegacy(BaseModule): key_data = self.get_contact_key_data(jid) if key_data is None: return False - key_id = key_data['key_id'] + key_id = key_data["key_id"] announced_fingerprint = self._presence_fingerprint_store.get(jid) if announced_fingerprint is None: @@ -130,24 +133,31 @@ class PGPLegacy(BaseModule): fingerprint = self._pgp.verify(properties.status, properties.signed) if fingerprint is None: - self._log.info('Presence from %s was signed but no corresponding ' - 'key was found', jid) + self._log.info( + "Presence from %s was signed but no corresponding " "key was found", jid + ) return self._presence_fingerprint_store[jid] = fingerprint - self._log.info('Presence from %s was verified successfully, ' - 'fingerprint: %s', jid, fingerprint) + self._log.info( + "Presence from %s was verified successfully, " "fingerprint: %s", + jid, + fingerprint, + ) key_data = self.get_contact_key_data(jid) if key_data is None: - self._log.info('No key assigned for contact: %s', jid) + self._log.info("No key assigned for contact: %s", jid) return - if key_data['key_id'] != fingerprint: - self._log.warning('Fingerprint mismatch, ' - 'Presence was signed with fingerprint: %s, ' - 'Assigned key fingerprint: %s', - fingerprint, key_data['key_id']) + if key_data["key_id"] != fingerprint: + self._log.warning( + "Fingerprint mismatch, " + "Presence was signed with fingerprint: %s, " + "Assigned key fingerprint: %s", + fingerprint, + key_data["key_id"], + ) return def _message_received(self, _con, stanza, properties): @@ -155,15 +165,13 @@ class PGPLegacy(BaseModule): return remote_jid = properties.remote_jid - self._log.info('Message received from: %s', remote_jid) + self._log.info("Message received from: %s", remote_jid) payload = self._pgp.decrypt(properties.pgp_legacy) prepare_stanza(stanza, payload) properties.encrypted = EncryptionData( - protocol=ENCRYPTION_NAME, - key='Unknown', - trust=Trust.UNDECIDED + protocol=ENCRYPTION_NAME, key="Unknown", trust=Trust.UNDECIDED ) def encrypt_message(self, con, message: OutgoingMessage, callback): @@ -181,7 +189,9 @@ class PGPLegacy(BaseModule): always_trust = key_id in self._always_trust self._encrypt(con, message, [key_id, own_key_id], callback, always_trust) - def _encrypt(self, con, message: OutgoingMessage, keys, callback, always_trust: bool): + def _encrypt( + self, con, message: OutgoingMessage, keys, callback, always_trust: bool + ): result = self._pgp.encrypt(message.get_text(), keys, always_trust) encrypted_payload, error = result if error: @@ -194,15 +204,18 @@ class PGPLegacy(BaseModule): message.set_encryption( EncryptionData( protocol=ENCRYPTION_NAME, - key='Unknown', + key="Unknown", trust=Trust.VERIFIED, ) ) callback(message) - def _handle_encrypt_error(self, con, error: str, message: OutgoingMessage, keys, callback): - if error.startswith('NOT_TRUSTED'): + def _handle_encrypt_error( + self, con, error: str, message: OutgoingMessage, keys, callback + ): + if error.startswith("NOT_TRUSTED"): + def on_yes(checked): if checked: self._always_trust.append(keys[0]) @@ -219,64 +232,67 @@ class PGPLegacy(BaseModule): @staticmethod def _raise_message_not_sent(con, message: OutgoingMessage, error: str): app.ged.raise_event( - MessageNotSent(client=con, - jid=str(message.contact.jid), - message=message.get_text(), - error=_('Encryption error: %s') % error, - time=time.time())) + MessageNotSent( + client=con, + jid=str(message.contact.jid), + message=message.get_text(), + error=_("Encryption error: %s") % error, + time=time.time(), + ) + ) def _create_pgp_legacy_message(self, stanza: Message, payload: str) -> None: stanza.setBody(self._get_info_message()) - stanza.setTag('x', namespace=Namespace.ENCRYPTED).setData(payload) - eme_node = nbxmpp.Node('encryption', - attrs={'xmlns': Namespace.EME, - 'namespace': Namespace.ENCRYPTED}) + stanza.setTag("x", namespace=Namespace.ENCRYPTED).setData(payload) + eme_node = nbxmpp.Node( + "encryption", + attrs={"xmlns": Namespace.EME, "namespace": Namespace.ENCRYPTED}, + ) stanza.addChild(node=eme_node) def sign_presence(self, presence, status): key_data = self.get_own_key_data() if key_data is None: - self._log.warning('No own key id found, can’t sign presence') + self._log.warning("No own key id found, can’t sign presence") return try: - result = self._pgp.sign(status, key_data['key_id']) + result = self._pgp.sign(status, key_data["key_id"]) except SignError as error: - self._log.warning('Sign Error: %s', error) + self._log.warning("Sign Error: %s", error) return # self._log.debug(self._pgp.sign.cache_info()) - self._log.info('Presence signed') - presence.setTag(Namespace.SIGNED + ' x').setData(result) + self._log.info("Presence signed") + presence.setTag(Namespace.SIGNED + " x").setData(result) @staticmethod def _get_info_message(): - msg = '[This message is *encrypted* (See :XEP:`27`)]' - lang = os.getenv('LANG') - if lang is not None and not lang.startswith('en'): + msg = "[This message is *encrypted* (See :XEP:`27`)]" + lang = os.getenv("LANG") + if lang is not None and not lang.startswith("en"): # we're not english: one in locale and one en - msg = _('[This message is *encrypted* (See :XEP:`27`)]') + \ - ' (' + msg + ')' + msg = _("[This message is *encrypted* (See :XEP:`27`)]") + " (" + msg + ")" return msg def _get_key_ids(self, jid): key_data = self.get_contact_key_data(jid) if key_data is None: - raise NoKeyIdFound('No key id found for %s' % jid) - key_id = key_data['key_id'] + raise NoKeyIdFound("No key id found for %s" % jid) + key_id = key_data["key_id"] own_key_data = self.get_own_key_data() if own_key_data is None: - raise NoKeyIdFound('Own key id not found') - own_key_id = own_key_data['key_id'] + raise NoKeyIdFound("Own key id not found") + own_key_id = own_key_data["key_id"] return key_id, own_key_id @staticmethod def _cleanup_stanza(message: OutgoingMessage) -> None: - ''' We make sure only allowed tags are in the stanza ''' + """We make sure only allowed tags are in the stanza""" original_stanza = message.get_stanza() stanza = nbxmpp.Message( - to=original_stanza.getTo(), - typ=original_stanza.getType()) + to=original_stanza.getTo(), typ=original_stanza.getType() + ) stanza.setID(original_stanza.getID()) stanza.setThread(original_stanza.getThread()) for tag, ns in ALLOWED_TAGS: @@ -286,8 +302,9 @@ class PGPLegacy(BaseModule): message.set_stanza(stanza) def encrypt_file(self, file, callback): - thread = threading.Thread(target=self._encrypt_file_thread, - args=(file, callback)) + thread = threading.Thread( + target=self._encrypt_file_thread, args=(file, callback) + ) thread.daemon = True thread.start() @@ -299,8 +316,7 @@ class PGPLegacy(BaseModule): return stream = open(file.path, "rb") - encrypted = self._pgp.encrypt_file(stream, - [key_id, own_key_id]) + encrypted = self._pgp.encrypt_file(stream, [key_id, own_key_id]) stream.close() if not encrypted: @@ -308,7 +324,7 @@ class PGPLegacy(BaseModule): return file.size = len(encrypted.data) - file.set_uri_transform_func(lambda uri: '%s.pgp' % uri) + file.set_uri_transform_func(lambda uri: "%s.pgp" % uri) file.set_encrypted_data(encrypted.data) GLib.idle_add(callback, file) @@ -316,5 +332,6 @@ class PGPLegacy(BaseModule): def _on_file_encryption_error(error): app.ged.raise_event(PGPFileEncryptionError(error=error)) + def get_instance(*args, **kwargs): - return PGPLegacy(*args, **kwargs), 'PGPLegacy' + return PGPLegacy(*args, **kwargs), "PGPLegacy" diff --git a/pgp/modules/util.py b/pgp/modules/util.py index 5d11a4d..f6fc17a 100644 --- a/pgp/modules/util.py +++ b/pgp/modules/util.py @@ -21,8 +21,8 @@ from nbxmpp.namespaces import Namespace def prepare_stanza(stanza, plaintext): - delete_nodes(stanza, 'encrypted', Namespace.ENCRYPTED) - delete_nodes(stanza, 'body') + delete_nodes(stanza, "encrypted", Namespace.ENCRYPTED) + delete_nodes(stanza, "body") stanza.setBody(plaintext) @@ -34,16 +34,16 @@ def delete_nodes(stanza, name, namespace=None): def find_gpg(): def _search(binary): - if os.name == 'nt': - gpg_cmd = binary + ' -h >nul 2>&1' + if os.name == "nt": + gpg_cmd = binary + " -h >nul 2>&1" else: - gpg_cmd = binary + ' -h >/dev/null 2>&1' + gpg_cmd = binary + " -h >/dev/null 2>&1" if subprocess.call(gpg_cmd, shell=True): return False return True - if _search('gpg2'): - return 'gpg2' + if _search("gpg2"): + return "gpg2" - if _search('gpg'): - return 'gpg' + if _search("gpg"): + return "gpg" diff --git a/pgp/plugin.py b/pgp/plugin.py index fae6d97..91b460c 100644 --- a/pgp/plugin.py +++ b/pgp/plugin.py @@ -14,29 +14,29 @@ # You should have received a copy of the GNU General Public License # along with PGP Gajim Plugin. If not, see . +import logging import os import sys -import logging from functools import partial + from packaging.version import Version as V from gajim.common import app from gajim.common import ged +from gajim.gtk.dialogs import ConfirmationCheckDialog +from gajim.gtk.dialogs import DialogButton +from gajim.gtk.dialogs import SimpleDialog from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from gajim.gtk.dialogs import SimpleDialog -from gajim.gtk.dialogs import DialogButton -from gajim.gtk.dialogs import ConfirmationCheckDialog - -from pgp.gtk.key import KeyDialog -from pgp.gtk.config import PGPConfigDialog from pgp.exceptions import KeyMismatch +from pgp.gtk.config import PGPConfigDialog +from pgp.gtk.key import KeyDialog from pgp.modules.util import find_gpg -ENCRYPTION_NAME = 'PGP' +ENCRYPTION_NAME = "PGP" -log = logging.getLogger('gajim.p.pgplegacy') +log = logging.getLogger("gajim.p.pgplegacy") ERROR = False try: @@ -51,29 +51,29 @@ else: # on a much lower version number than gnupg # Also we need at least python-gnupg 0.3.8 v_gnupg = gnupg.__version__ - if V(v_gnupg) < V('0.3.8') or V(v_gnupg) > V('1.0.0'): - log.error('We need python-gnupg >= 0.3.8') + if V(v_gnupg) < V("0.3.8") or V(v_gnupg) > V("1.0.0"): + log.error("We need python-gnupg >= 0.3.8") ERROR = True ERROR_MSG = None BINARY = find_gpg() -log.info('Found GPG executable: %s', BINARY) +log.info("Found GPG executable: %s", BINARY) if BINARY is None or ERROR: - if os.name == 'nt': - ERROR_MSG = _('Please install GnuPG / Gpg4win') + if os.name == "nt": + ERROR_MSG = _("Please install GnuPG / Gpg4win") else: - ERROR_MSG = _('Please install python-gnupg and gnupg') + ERROR_MSG = _("Please install python-gnupg and gnupg") else: - from pgp.modules import pgp_legacy from pgp.backend.python_gnupg import PGP + from pgp.modules import pgp_legacy class PGPPlugin(GajimPlugin): def init(self): # pylint: disable=attribute-defined-outside-init - self.description = _('PGP encryption as per XEP-0027') + self.description = _("PGP encryption as per XEP-0027") if ERROR_MSG: self.activatable = False self.config_dialog = None @@ -84,30 +84,26 @@ class PGPPlugin(GajimPlugin): self.encryption_name = ENCRYPTION_NAME self.allow_zeroconf = True self.gui_extension_points = { - 'encrypt' + ENCRYPTION_NAME: (self._encrypt_message, None), - 'send_message' + ENCRYPTION_NAME: ( - self._before_sendmessage, None), - 'encryption_dialog' + ENCRYPTION_NAME: ( - self._on_encryption_dialog, None), - 'encryption_state' + ENCRYPTION_NAME: ( - self._encryption_state, None), - 'send-presence': (self._on_send_presence, None), + "encrypt" + ENCRYPTION_NAME: (self._encrypt_message, None), + "send_message" + ENCRYPTION_NAME: (self._before_sendmessage, None), + "encryption_dialog" + ENCRYPTION_NAME: (self._on_encryption_dialog, None), + "encryption_state" + ENCRYPTION_NAME: (self._encryption_state, None), + "send-presence": (self._on_send_presence, None), } self.modules = [pgp_legacy] self.events_handlers = { - 'pgp-not-trusted': (ged.PRECORE, self._on_not_trusted), - 'pgp-file-encryption-error': (ged.PRECORE, - self._on_file_encryption_error), + "pgp-not-trusted": (ged.PRECORE, self._on_not_trusted), + "pgp-file-encryption-error": (ged.PRECORE, self._on_file_encryption_error), } - encoding = 'utf8' if sys.platform == 'linux' else None + encoding = "utf8" if sys.platform == "linux" else None self._pgp = PGP(BINARY, encoding=encoding) @staticmethod def get_pgp_module(account): - return app.get_client(account).get_module('PGPLegacy') + return app.get_client(account).get_module("PGPLegacy") def activate(self): pass @@ -121,8 +117,8 @@ class PGPPlugin(GajimPlugin): @staticmethod def _encryption_state(_chat_control, state): - state['visible'] = True - state['authenticated'] = True + state["visible"] = True + state["authenticated"] = True def _on_encryption_dialog(self, chat_control): account = chat_control.account @@ -137,17 +133,20 @@ class PGPPlugin(GajimPlugin): @staticmethod def _on_not_trusted(event): ConfirmationCheckDialog( - _('Untrusted PGP key'), - _('The PGP key used to encrypt this chat is not ' - 'trusted. Do you really want to encrypt this ' - 'message?'), - _('_Do not ask me again'), - [DialogButton.make('Cancel', - text=_('_No'), - callback=event.on_no), - DialogButton.make('OK', - text=_('_Encrypt Anyway'), - callback=event.on_yes)]).show() + _("Untrusted PGP key"), + _( + "The PGP key used to encrypt this chat is not " + "trusted. Do you really want to encrypt this " + "message?" + ), + _("_Do not ask me again"), + [ + DialogButton.make("Cancel", text=_("_No"), callback=event.on_no), + DialogButton.make( + "OK", text=_("_Encrypt Anyway"), callback=event.on_yes + ), + ], + ).show() @staticmethod def _before_sendmessage(chat_control): @@ -156,24 +155,30 @@ class PGPPlugin(GajimPlugin): client = app.get_client(account) try: - valid = client.get_module('PGPLegacy').has_valid_key_assigned(jid) + valid = client.get_module("PGPLegacy").has_valid_key_assigned(jid) except KeyMismatch as announced_key_id: SimpleDialog( - _('PGP Key mismatch'), - _('The contact\'s key (%s) does not match the key ' - 'assigned in Gajim.') % announced_key_id) + _("PGP Key mismatch"), + _( + "The contact's key (%s) does not match the key " + "assigned in Gajim." + ) + % announced_key_id, + ) chat_control.sendmessage = False return if not valid: SimpleDialog( - _('No OpenPGP key assigned'), - _('No OpenPGP key is assigned to this contact.')) + _("No OpenPGP key assigned"), + _("No OpenPGP key is assigned to this contact."), + ) chat_control.sendmessage = False - elif client.get_module('PGPLegacy').get_own_key_data() is None: + elif client.get_module("PGPLegacy").get_own_key_data() is None: SimpleDialog( - _('No OpenPGP key assigned'), - _('No OpenPGP key is assigned to your account.')) + _("No OpenPGP key assigned"), + _("No OpenPGP key is assigned to your account."), + ) chat_control.sendmessage = False def _encrypt_message(self, conn, event, callback): @@ -185,4 +190,4 @@ class PGPPlugin(GajimPlugin): @staticmethod def _on_file_encryption_error(event): - SimpleDialog(_('Error'), event.error) + SimpleDialog(_("Error"), event.error) diff --git a/plugins_translations/__init__.py b/plugins_translations/__init__.py index abcc64f..be7ac38 100644 --- a/plugins_translations/__init__.py +++ b/plugins_translations/__init__.py @@ -1 +1,3 @@ -from .plugins_translations import PluginsTranslationsPlugin # type: ignore # noqa: F401 +from .plugins_translations import ( # type: ignore # noqa: F401 + PluginsTranslationsPlugin, +) diff --git a/plugins_translations/plugins_translations.py b/plugins_translations/plugins_translations.py index aa40190..789d63e 100644 --- a/plugins_translations/plugins_translations.py +++ b/plugins_translations/plugins_translations.py @@ -23,49 +23,49 @@ from glob import glob from pathlib import Path from gajim.common import configpaths - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -log = logging.getLogger('gajim.p.plugins_translations') +log = logging.getLogger("gajim.p.plugins_translations") class PluginsTranslationsPlugin(GajimPlugin): def init(self) -> None: - self.description = _('This plugin contains translations for other ' - 'Gajim plugins. Please restart Gajim after ' - 'enabling this plugin.') + self.description = _( + "This plugin contains translations for other " + "Gajim plugins. Please restart Gajim after " + "enabling this plugin." + ) self.config_dialog = None - self.config_default_values = {'last_version': ('0', '')} - self.locale_dir = Path(configpaths.get('PLUGINS_USER')) / 'locale' + self.config_default_values = {"last_version": ("0", "")} + self.locale_dir = Path(configpaths.get("PLUGINS_USER")) / "locale" def activate(self) -> None: current_version = str(self.manifest.version) - if cast(str, self.config['last_version']) == current_version: + if cast(str, self.config["last_version"]) == current_version: return - files = glob(self.__path__ + '/*.mo') + files = glob(self.__path__ + "/*.mo") self._remove_translations() self.locale_dir.mkdir() - locales = [ - os.path.splitext(os.path.basename(name))[0] for name in files - ] - log.info('Installing new translations...') + locales = [os.path.splitext(os.path.basename(name))[0] for name in files] + log.info("Installing new translations...") for locale in locales: - dst = self.locale_dir / locale / 'LC_MESSAGES' + dst = self.locale_dir / locale / "LC_MESSAGES" dst.mkdir(parents=True) - shutil.copy2(os.path.join(self.__path__, '%s.mo' % locale), - dst / 'gajim_plugins.mo') + shutil.copy2( + os.path.join(self.__path__, "%s.mo" % locale), dst / "gajim_plugins.mo" + ) - self.config['last_version'] = current_version + self.config["last_version"] = current_version def _remove_translations(self) -> None: - log.info('Removing old translations...') + log.info("Removing old translations...") if self.locale_dir.exists(): shutil.rmtree(str(self.locale_dir)) def deactivate(self) -> None: self._remove_translations() - self.config['last_version'] = '0' + self.config["last_version"] = "0" diff --git a/quick_replies/gtk/config.py b/quick_replies/gtk/config.py index 5663de4..3a3a816 100644 --- a/quick_replies/gtk/config.py +++ b/quick_replies/gtk/config.py @@ -21,28 +21,24 @@ from typing import TYPE_CHECKING from pathlib import Path -from gi.repository import Gtk from gi.repository import Gdk +from gi.repository import Gtk from gajim.common import app - -from gajim.plugins.plugins_i18n import _ from gajim.plugins.helpers import get_builder +from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: from ..plugin import QuickRepliesPlugin class ConfigDialog(Gtk.ApplicationWindow): - def __init__(self, - plugin: QuickRepliesPlugin, - transient: Gtk.Window - ) -> None: + def __init__(self, plugin: QuickRepliesPlugin, transient: Gtk.Window) -> None: Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_show_menubar(False) - self.set_title(_('Quick Replies Configuration')) + self.set_title(_("Quick Replies Configuration")) self.set_transient_for(transient) self.set_default_size(400, 400) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) @@ -50,7 +46,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self.set_destroy_with_parent(True) ui_path = Path(__file__).parent - self._ui = get_builder(str(ui_path.resolve() / 'config.ui')) + self._ui = get_builder(str(ui_path.resolve() / "config.ui")) self._plugin = plugin @@ -60,26 +56,23 @@ class ConfigDialog(Gtk.ApplicationWindow): self.show_all() self._ui.connect_signals(self) - self.connect('destroy', self._on_destroy) + self.connect("destroy", self._on_destroy) def _fill_list(self) -> None: for reply in self._plugin.quick_replies: self._ui.replies_store.append([reply]) - def _on_reply_edited(self, - _renderer: Gtk.CellRendererText, - path: str, - new_text: str - ) -> None: + def _on_reply_edited( + self, _renderer: Gtk.CellRendererText, path: str, new_text: str + ) -> None: iter_ = self._ui.replies_store.get_iter(path) self._ui.replies_store.set_value(iter_, 0, new_text) def _on_add_clicked(self, _button: Gtk.Button) -> None: - self._ui.replies_store.append([_('New Quick Reply')]) + self._ui.replies_store.append([_("New Quick Reply")]) row = self._ui.replies_store[-1] - self._ui.replies_treeview.scroll_to_cell( - row.path, None, False, 0, 0) + self._ui.replies_treeview.scroll_to_cell(row.path, None, False, 0, 0) self._ui.selection.unselect_all() self._ui.selection.select_path(row.path) @@ -96,7 +89,7 @@ class ConfigDialog(Gtk.ApplicationWindow): def _on_destroy(self, *args: Any) -> None: replies: list[str] = [] for row in self._ui.replies_store: - if row[0] == '': + if row[0] == "": continue replies.append(row[0]) self._plugin.set_quick_replies(replies) diff --git a/quick_replies/plugin.py b/quick_replies/plugin.py index b6fcf6e..f60b12e 100644 --- a/quick_replies/plugin.py +++ b/quick_replies/plugin.py @@ -18,8 +18,8 @@ from __future__ import annotations from typing import cast import json -from pathlib import Path from functools import partial +from pathlib import Path from gi.repository import Gio from gi.repository import GLib @@ -27,23 +27,21 @@ from gi.repository import Gtk from gajim.common import app from gajim.common import configpaths - from gajim.gtk.message_actions_box import MessageActionsBox from gajim.gtk.message_input import MessageInputTextView - from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from quick_replies.quick_replies import DEFAULT_DATA from quick_replies.gtk.config import ConfigDialog +from quick_replies.quick_replies import DEFAULT_DATA class QuickRepliesPlugin(GajimPlugin): def init(self) -> None: - self.description = _('Adds a menu with customizable quick replies') + self.description = _("Adds a menu with customizable quick replies") self.config_dialog = partial(ConfigDialog, self) self.gui_extension_points = { - 'message_actions_box': (self._message_actions_box_created, None), + "message_actions_box": (self._message_actions_box_created, None), } self._button = None self.quick_replies = self._load_quick_replies() @@ -53,47 +51,44 @@ class QuickRepliesPlugin(GajimPlugin): self._button.destroy() del self._button - def _message_actions_box_created(self, - message_actions_box: MessageActionsBox, - gtk_box: Gtk.Box - ) -> None: + def _message_actions_box_created( + self, message_actions_box: MessageActionsBox, gtk_box: Gtk.Box + ) -> None: - self._button = QuickRepliesButton( - self, - message_actions_box.msg_textview) + self._button = QuickRepliesButton(self, message_actions_box.msg_textview) gtk_box.pack_start(self._button, False, False, 0) self._button.show() @staticmethod def _load_quick_replies() -> list[str]: try: - data_path = Path(configpaths.get('PLUGINS_DATA')) + data_path = Path(configpaths.get("PLUGINS_DATA")) except KeyError: # PLUGINS_DATA was added in 1.0.99.1 return DEFAULT_DATA - path = data_path / 'quick_replies' / 'quick_replies' + path = data_path / "quick_replies" / "quick_replies" if not path.exists(): return DEFAULT_DATA - with path.open('r') as file: + with path.open("r") as file: quick_replies = json.load(file) return quick_replies @staticmethod def _save_quick_replies(quick_replies: list[str]) -> None: try: - data_path = Path(configpaths.get('PLUGINS_DATA')) + data_path = Path(configpaths.get("PLUGINS_DATA")) except KeyError: # PLUGINS_DATA was added in 1.0.99.1 return - path = data_path / 'quick_replies' + path = data_path / "quick_replies" if not path.exists(): path.mkdir(parents=True) - filepath = path / 'quick_replies' - with filepath.open('w') as file: + filepath = path / "quick_replies" + with filepath.open("w") as file: json.dump(quick_replies, file) def set_quick_replies(self, quick_replies: list[str]) -> None: @@ -104,20 +99,19 @@ class QuickRepliesPlugin(GajimPlugin): class QuickRepliesButton(Gtk.MenuButton): - def __init__(self, - plugin: QuickRepliesPlugin, - message_input: MessageInputTextView - ) -> None: + def __init__( + self, plugin: QuickRepliesPlugin, message_input: MessageInputTextView + ) -> None: Gtk.MenuButton.__init__(self) - self.get_style_context().add_class('chatcontrol-actionbar-button') - self.set_property('relief', Gtk.ReliefStyle.NONE) + self.get_style_context().add_class("chatcontrol-actionbar-button") + self.set_property("relief", Gtk.ReliefStyle.NONE) self.set_can_focus(False) plugin_path = Path(__file__).parent - img_path = plugin_path.resolve() / 'quick_replies.png' + img_path = plugin_path.resolve() / "quick_replies.png" img = Gtk.Image.new_from_file(str(img_path)) self.set_image(img) - self.set_tooltip_text(_('Quick Replies')) + self.set_tooltip_text(_("Quick Replies")) self._plugin = plugin self._message_input = message_input @@ -133,38 +127,36 @@ class QuickRepliesButton(Gtk.MenuButton): self._menu.remove_all() # Add config item - action_data = GLib.Variant('s', 'plugin-configuration') + action_data = GLib.Variant("s", "plugin-configuration") menu_item = Gio.MenuItem() - menu_item.set_label(_('Manage Replies…')) - menu_item.set_attribute_value('action-data', action_data) + menu_item.set_label(_("Manage Replies…")) + menu_item.set_attribute_value("action-data", action_data) self._menu.append_item(menu_item) # Add quick replies for reply in self._plugin.quick_replies: assert isinstance(reply, str) - action_data = GLib.Variant('s', reply) + action_data = GLib.Variant("s", reply) menu_item = Gio.MenuItem() menu_item.set_label(reply) - menu_item.set_attribute_value('action-data', action_data) + menu_item.set_attribute_value("action-data", action_data) self._menu.append_item(menu_item) menu_buttons = self._get_menu_buttons() for button in menu_buttons: button.connect( - 'clicked', - self._on_button_clicked, - menu_buttons.index(button)) + "clicked", self._on_button_clicked, menu_buttons.index(button) + ) def _on_button_clicked(self, _button: Gtk.MenuButton, index: int) -> None: - variant = self._menu.get_item_attribute_value( - index, 'action-data') - if variant.get_string() == 'plugin-configuration': + variant = self._menu.get_item_attribute_value(index, "action-data") + if variant.get_string() == "plugin-configuration": self._popover.popdown() self._plugin.config_dialog(app.window) return message_buffer = self._message_input.get_buffer() - message_buffer.insert_at_cursor(variant.get_string().rstrip() + ' ') + message_buffer.insert_at_cursor(variant.get_string().rstrip() + " ") self._popover.popdown() self._message_input.grab_focus() diff --git a/quick_replies/quick_replies.py b/quick_replies/quick_replies.py index 502dd01..cad6e38 100644 --- a/quick_replies/quick_replies.py +++ b/quick_replies/quick_replies.py @@ -1,5 +1,5 @@ DEFAULT_DATA = [ - 'Hello!', - 'How are you?', - 'Good bye.', + "Hello!", + "How are you?", + "Good bye.", ] diff --git a/scripts/build_repository.py b/scripts/build_repository.py index 1c26477..c6f4849 100644 --- a/scripts/build_repository.py +++ b/scripts/build_repository.py @@ -1,40 +1,38 @@ - # Keep this file python 3.7 compatible because it is executed on the server from typing import Any from typing import Iterator -import sys import json import logging +import sys from collections import defaultdict from pathlib import Path from zipfile import ZipFile - -FORMAT = '%(asctime)s %(message)s' +FORMAT = "%(asctime)s %(message)s" logging.basicConfig(format=FORMAT, level=logging.DEBUG) log = logging.getLogger() REQUIRED_KEYS: set[str] = { - 'authors', - 'description', - 'homepage', - 'config_dialog', - 'name', - 'platforms', - 'requirements', - 'short_name', - 'version' + "authors", + "description", + "homepage", + "config_dialog", + "name", + "platforms", + "requirements", + "short_name", + "version", } PACKAGE_INDEX: dict[str, Any] = { - 'metadata': { - 'repository_name': 'master', - 'image_path': 'images.zip', + "metadata": { + "repository_name": "master", + "image_path": "images.zip", }, - 'plugins': defaultdict(dict) + "plugins": defaultdict(dict), } @@ -44,53 +42,53 @@ def is_manifest_valid(manifest: dict[str, Any]) -> bool: def iter_releases(release_folder: Path) -> Iterator[dict[str, Any]]: - for path in release_folder.rglob('*.zip'): + for path in release_folder.rglob("*.zip"): with ZipFile(path) as release_zip: - if path.name == 'images.zip': + if path.name == "images.zip": continue - log.info('Check path: %s', path) + log.info("Check path: %s", path) try: - with release_zip.open('plugin-manifest.json') as file: + with release_zip.open("plugin-manifest.json") as file: manifest = json.load(file) yield manifest except Exception: - log.error('Error loading manifest') - log.exception('') + log.error("Error loading manifest") + log.exception("") def build_package_index(release_folder: Path) -> None: - log.info('Build package index') + log.info("Build package index") for manifest in iter_releases(release_folder): if not is_manifest_valid(manifest): - log.warning('Invalid manifest') + log.warning("Invalid manifest") log.warning(manifest) continue - short_name = manifest.pop('short_name') - version = manifest.pop('version') - PACKAGE_INDEX['plugins'][short_name][version] = manifest - log.info('Found manifest: %s - %s', short_name, version) + short_name = manifest.pop("short_name") + version = manifest.pop("version") + PACKAGE_INDEX["plugins"][short_name][version] = manifest + log.info("Found manifest: %s - %s", short_name, version) - path = release_folder / 'package_index.json' - with path.open('w') as f: + path = release_folder / "package_index.json" + with path.open("w") as f: json.dump(PACKAGE_INDEX, f) def build_image_zip(release_folder: Path) -> None: - log.info('Build images.zip') - with ZipFile(release_folder / 'images.zip', mode='w') as image_zip: + log.info("Build images.zip") + with ZipFile(release_folder / "images.zip", mode="w") as image_zip: for path in release_folder.iterdir(): if not path.is_dir(): continue - image = path / f'{path.name}.png' + image = path / f"{path.name}.png" if not image.exists(): continue image_zip.write(image, arcname=image.name) -if __name__ == '__main__': +if __name__ == "__main__": path = Path(sys.argv[1]) build_package_index(path) build_image_zip(path) - log.info('Finished') + log.info("Finished") diff --git a/scripts/update_translations.py b/scripts/update_translations.py index 22a0e69..34c71ea 100755 --- a/scripts/update_translations.py +++ b/scripts/update_translations.py @@ -5,25 +5,24 @@ import re import subprocess from pathlib import Path - REPO_DIR = Path(__file__).parent.parent -TRANS_DIR = REPO_DIR / 'po' -TRANS_TEMPLATE = TRANS_DIR / 'gajim_plugins.pot' -BUILD_DIR = REPO_DIR / 'plugins_translations' +TRANS_DIR = REPO_DIR / "po" +TRANS_TEMPLATE = TRANS_DIR / "gajim_plugins.pot" +BUILD_DIR = REPO_DIR / "plugins_translations" TRANSLATABLE_FILES = [ - '*.py', - '*.ui', + "*.py", + "*.ui", ] def template_is_equal(old_template_path: Path, new_template: str) -> bool: - with open(old_template_path, 'r') as f: + with open(old_template_path, "r") as f: old_template = f.read() pattern = r'"POT-Creation-Date: .*\n"' - old_template = re.sub(pattern, '', old_template, count=1) - new_template = re.sub(pattern, '', new_template, count=1) + old_template = re.sub(pattern, "", old_template, count=1) + new_template = re.sub(pattern, "", new_template, count=1) return old_template == new_template @@ -34,86 +33,77 @@ def update_translation_template() -> bool: paths += list(REPO_DIR.rglob(file_path)) cmd = [ - 'xgettext', - '-o', '-', - '-c#', - '--from-code=utf-8', - '--keyword=Q_', - '--no-location', - '--sort-output', - '--package-name=Gajim Plugins' + "xgettext", + "-o", + "-", + "-c#", + "--from-code=utf-8", + "--keyword=Q_", + "--no-location", + "--sort-output", + "--package-name=Gajim Plugins", ] for path in paths: cmd.append(str(path)) - result = subprocess.run(cmd, - cwd=REPO_DIR, - text=True, - check=True, - capture_output=True) + result = subprocess.run( + cmd, cwd=REPO_DIR, text=True, check=True, capture_output=True + ) template = result.stdout - if (TRANS_TEMPLATE.exists() and - template_is_equal(TRANS_TEMPLATE, template)): + if TRANS_TEMPLATE.exists() and template_is_equal(TRANS_TEMPLATE, template): # No new strings were discovered return False - with open(TRANS_TEMPLATE, 'w') as f: + with open(TRANS_TEMPLATE, "w") as f: f.write(template) return True def update_translation_files() -> None: - for file in TRANS_DIR.glob('*.po'): - subprocess.run(['msgmerge', - '-U', - '--sort-output', - str(file), - TRANS_TEMPLATE], - cwd=REPO_DIR, - check=True) + for file in TRANS_DIR.glob("*.po"): + subprocess.run( + ["msgmerge", "-U", "--sort-output", str(file), TRANS_TEMPLATE], + cwd=REPO_DIR, + check=True, + ) def build_translations() -> None: - for po_file in TRANS_DIR.glob('*.po'): + for po_file in TRANS_DIR.glob("*.po"): lang = po_file.stem - po_file = TRANS_DIR / f'{lang}.po' - mo_file = BUILD_DIR / f'{po_file.stem}.mo' + po_file = TRANS_DIR / f"{lang}.po" + mo_file = BUILD_DIR / f"{po_file.stem}.mo" - subprocess.run(['msgfmt', - str(po_file), - '-o', - str(mo_file)], - cwd=REPO_DIR, - check=True) + subprocess.run( + ["msgfmt", str(po_file), "-o", str(mo_file)], cwd=REPO_DIR, check=True + ) def cleanup_translations() -> None: - for po_file in TRANS_DIR.glob('*.po'): - subprocess.run(['msgattrib', - '--output-file', - str(po_file), - '--no-obsolete', - str(po_file)], - cwd=REPO_DIR, - check=True) + for po_file in TRANS_DIR.glob("*.po"): + subprocess.run( + ["msgattrib", "--output-file", str(po_file), "--no-obsolete", str(po_file)], + cwd=REPO_DIR, + check=True, + ) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Update Translations') - parser.add_argument('command', choices=['update', 'build', 'cleanup']) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Update Translations") + parser.add_argument("command", choices=["update", "build", "cleanup"]) args = parser.parse_args() - if args.command == 'cleanup': + if args.command == "cleanup": cleanup_translations() - elif args.command == 'update': + elif args.command == "update": update_translation_template() update_translation_files() - elif args.command == 'build': + elif args.command == "build": update_translation_template() update_translation_files() build_translations() diff --git a/triggers/gtk/config.py b/triggers/gtk/config.py index e382810..87357c2 100644 --- a/triggers/gtk/config.py +++ b/triggers/gtk/config.py @@ -16,29 +16,28 @@ from __future__ import annotations +from typing import Any +from typing import TYPE_CHECKING + from pathlib import Path -from typing import TYPE_CHECKING, Any + +from gi.repository import Gdk +from gi.repository import Gtk from gajim.common import app from gajim.common.helpers import play_sound_file from gajim.common.util.status import get_uf_show from gajim.plugins.helpers import get_builder from gajim.plugins.plugins_i18n import _ -from gi.repository import Gdk, Gtk if TYPE_CHECKING: from ..triggers import Triggers EVENTS: dict[str, Any] = { - 'message_received': [], + "message_received": [], } -RECIPIENT_TYPES = [ - 'contact', - 'group', - 'groupchat', - 'all' -] +RECIPIENT_TYPES = ["contact", "group", "groupchat", "all"] class ConfigDialog(Gtk.ApplicationWindow): @@ -46,7 +45,7 @@ class ConfigDialog(Gtk.ApplicationWindow): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_show_menubar(False) - self.set_title(_('Triggers Configuration')) + self.set_title(_("Triggers Configuration")) self.set_transient_for(transient) self.set_default_size(600, 800) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) @@ -54,7 +53,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self.set_destroy_with_parent(True) ui_path = Path(__file__).parent - self._ui = get_builder(str(ui_path.resolve() / 'config.ui')) + self._ui = get_builder(str(ui_path.resolve() / "config.ui")) self._plugin = plugin @@ -67,7 +66,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._initialize() self._ui.connect_signals(self) - self.connect('destroy', self._on_destroy) + self.connect("destroy", self._on_destroy) def _on_destroy(self, *args: Any) -> None: for num in list(self._plugin.config.keys()): @@ -78,31 +77,31 @@ class ConfigDialog(Gtk.ApplicationWindow): def _initialize(self) -> None: # Fill window widgets = [ - 'conditions_treeview', - 'config_box', - 'event_combobox', - 'recipient_type_combobox', - 'recipient_list_entry', - 'delete_button', - 'online_cb', - 'away_cb', - 'xa_cb', - 'dnd_cb', - 'use_sound_cb', - 'disable_sound_cb', - 'use_popup_cb', - 'disable_popup_cb', - 'tab_opened_cb', - 'not_tab_opened_cb', - 'has_focus_cb', - 'not_has_focus_cb', - 'filechooser', - 'sound_file_box', - 'up_button', - 'down_button', - 'run_command_cb', - 'command_entry', - 'one_shot_cb' + "conditions_treeview", + "config_box", + "event_combobox", + "recipient_type_combobox", + "recipient_list_entry", + "delete_button", + "online_cb", + "away_cb", + "xa_cb", + "dnd_cb", + "use_sound_cb", + "disable_sound_cb", + "use_popup_cb", + "disable_popup_cb", + "tab_opened_cb", + "not_tab_opened_cb", + "has_focus_cb", + "not_has_focus_cb", + "filechooser", + "sound_file_box", + "up_button", + "down_button", + "run_command_cb", + "command_entry", + "one_shot_cb", ] for widget in widgets: self._ui.__dict__[widget] = self._ui.get_object(widget) @@ -118,17 +117,17 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.conditions_treeview.set_model(model) # '#' Means number - col = Gtk.TreeViewColumn(_('#')) + col = Gtk.TreeViewColumn(_("#")) self._ui.conditions_treeview.append_column(col) renderer = Gtk.CellRendererText() col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'text', 0) + col.add_attribute(renderer, "text", 0) - col = Gtk.TreeViewColumn(_('Condition')) + col = Gtk.TreeViewColumn(_("Condition")) self._ui.conditions_treeview.append_column(col) renderer = Gtk.CellRendererText() col.pack_start(renderer, expand=True) - col.add_attribute(renderer, 'text', 1) + col.add_attribute(renderer, "text", 1) else: model = self._ui.conditions_treeview.get_model() @@ -137,7 +136,7 @@ class ConfigDialog(Gtk.ApplicationWindow): # Fill conditions_treeview num = 0 while num in self._config: - iter_ = model.append((num, '')) + iter_ = model.append((num, "")) path = model.get_path(iter_) self._ui.conditions_treeview.set_cursor(path) self._active_num = num @@ -154,13 +153,13 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.up_button.set_sensitive(False) filter_ = Gtk.FileFilter() - filter_.set_name(_('All Files')) - filter_.add_pattern('*') + filter_.set_name(_("All Files")) + filter_.add_pattern("*") self._ui.filechooser.add_filter(filter_) filter_ = Gtk.FileFilter() - filter_.set_name(_('Wav Sounds')) - filter_.add_pattern('*.wav') + filter_.set_name(_("Wav Sounds")) + filter_.add_pattern("*.wav") self._ui.filechooser.add_filter(filter_) self._ui.filechooser.set_filter(filter_) @@ -172,96 +171,95 @@ class ConfigDialog(Gtk.ApplicationWindow): return # event - value = self._config[self._active_num]['event'] + value = self._config[self._active_num]["event"] legacy_values = [ - 'contact_connected', - 'contact_disconnected', - 'contact_status_change'] + "contact_connected", + "contact_disconnected", + "contact_status_change", + ] if value and value not in legacy_values: - self._ui.event_combobox.set_active( - list(EVENTS.keys()).index(value)) + self._ui.event_combobox.set_active(list(EVENTS.keys()).index(value)) else: self._ui.event_combobox.set_active(-1) # recipient_type - value = self._config[self._active_num]['recipient_type'] + value = self._config[self._active_num]["recipient_type"] if value: - self._ui.recipient_type_combobox.set_active( - RECIPIENT_TYPES.index(value)) + self._ui.recipient_type_combobox.set_active(RECIPIENT_TYPES.index(value)) else: self._ui.recipient_type_combobox.set_active(-1) # recipient - value = self._config[self._active_num]['recipients'] + value = self._config[self._active_num]["recipients"] if not value: - value = '' + value = "" self._ui.recipient_list_entry.set_text(value) # status - value = self._config[self._active_num]['status'] - if value == 'all': + value = self._config[self._active_num]["status"] + if value == "all": self._ui.all_status_rb.set_active(True) else: self._ui.special_status_rb.set_active(True) values = value.split() - for val in ('online', 'away', 'xa', 'dnd'): + for val in ("online", "away", "xa", "dnd"): if val in values: - self._ui.__dict__[val + '_cb'].set_active(True) + self._ui.__dict__[val + "_cb"].set_active(True) else: - self._ui.__dict__[val + '_cb'].set_active(False) + self._ui.__dict__[val + "_cb"].set_active(False) self._on_status_radiobutton_toggled(self._ui.all_status_rb) # tab_opened - value = self._config[self._active_num]['tab_opened'] + value = self._config[self._active_num]["tab_opened"] self._ui.tab_opened_cb.set_active(True) self._ui.not_tab_opened_cb.set_active(True) - if value == 'no': + if value == "no": self._ui.tab_opened_cb.set_active(False) - elif value == 'yes': + elif value == "yes": self._ui.not_tab_opened_cb.set_active(False) # has_focus - if 'has_focus' not in self._config[self._active_num]: - self._config[self._active_num]['has_focus'] = 'both' - value = self._config[self._active_num]['has_focus'] + if "has_focus" not in self._config[self._active_num]: + self._config[self._active_num]["has_focus"] = "both" + value = self._config[self._active_num]["has_focus"] self._ui.has_focus_cb.set_active(True) self._ui.not_has_focus_cb.set_active(True) - if value == 'no': + if value == "no": self._ui.has_focus_cb.set_active(False) - elif value == 'yes': + elif value == "yes": self._ui.not_has_focus_cb.set_active(False) # sound_file - value = self._config[self._active_num]['sound_file'] + value = self._config[self._active_num]["sound_file"] if value is None: self._ui.filechooser.unselect_all() else: self._ui.filechooser.set_filename(value) # sound, popup, auto_open, systray, roster - for option in ('sound', 'popup'): + for option in ("sound", "popup"): value = self._config[self._active_num][option] - if value == 'yes': - self._ui.__dict__['use_' + option + '_cb'].set_active(True) + if value == "yes": + self._ui.__dict__["use_" + option + "_cb"].set_active(True) else: - self._ui.__dict__['use_' + option + '_cb'].set_active(False) - if value == 'no': - self._ui.__dict__['disable_' + option + '_cb'].set_active(True) + self._ui.__dict__["use_" + option + "_cb"].set_active(False) + if value == "no": + self._ui.__dict__["disable_" + option + "_cb"].set_active(True) else: - self._ui.__dict__['disable_' + option + '_cb'].set_active(False) + self._ui.__dict__["disable_" + option + "_cb"].set_active(False) # run_command - value = self._config[self._active_num]['run_command'] + value = self._config[self._active_num]["run_command"] self._ui.run_command_cb.set_active(value) # command - value = self._config[self._active_num]['command'] + value = self._config[self._active_num]["command"] self._ui.command_entry.set_text(value) # one shot - if 'one_shot' in self._config[self._active_num]: - value = self._config[self._active_num]['one_shot'] + if "one_shot" in self._config[self._active_num]: + value = self._config[self._active_num]["one_shot"] else: value = False self._ui.one_shot_cb.set_active(value) @@ -272,34 +270,34 @@ class ConfigDialog(Gtk.ApplicationWindow): if not iter_: return ind = self._ui.event_combobox.get_active() - event = '' + event = "" if ind > -1: event = self._ui.event_combobox.get_model()[ind][0] ind = self._ui.recipient_type_combobox.get_active() - recipient_type = '' + recipient_type = "" if ind > -1: recipient_type_model = self._ui.recipient_type_combobox.get_model() recipient_type = recipient_type_model[ind][0] - recipient = '' - if recipient_type != 'everybody': + recipient = "" + if recipient_type != "everybody": recipient = self._ui.recipient_list_entry.get_text() if self._ui.all_status_rb.get_active(): - status = '' + status = "" else: - status = _('and I am ') - for st in ('online', 'away', 'xa', 'dnd'): - if self._ui.__dict__[st + '_cb'].get_active(): - status += get_uf_show(st) + ' ' - model[iter_][1] = _('%(event)s (%(recipient_type)s) %(recipient)s ' - '%(status)s') % { - 'event': event, - 'recipient_type': recipient_type, - 'recipient': recipient, - 'status': status} + status = _("and I am ") + for st in ("online", "away", "xa", "dnd"): + if self._ui.__dict__[st + "_cb"].get_active(): + status += get_uf_show(st) + " " + model[iter_][1] = _( + "%(event)s (%(recipient_type)s) %(recipient)s " "%(status)s" + ) % { + "event": event, + "recipient_type": recipient_type, + "recipient": recipient, + "status": status, + } - def _on_conditions_treeview_cursor_changed(self, - widget: Gtk.TreeView - ) -> None: + def _on_conditions_treeview_cursor_changed(self, widget: Gtk.TreeView) -> None: (model, iter_) = widget.get_selection().get_selected() if not iter_: @@ -325,20 +323,20 @@ class ConfigDialog(Gtk.ApplicationWindow): model = self._ui.conditions_treeview.get_model() num = self._ui.conditions_treeview.get_model().iter_n_children(None) self._config[num] = { - 'event': 'message_received', - 'recipient_type': 'all', - 'recipients': '', - 'status': 'all', - 'tab_opened': 'both', - 'has_focus': 'both', - 'sound': '', - 'sound_file': '', - 'popup': '', - 'run_command': False, - 'command': '', - 'one_shot': False, + "event": "message_received", + "recipient_type": "all", + "recipients": "", + "status": "all", + "tab_opened": "both", + "has_focus": "both", + "sound": "", + "sound_file": "", + "popup": "", + "run_command": False, + "command": "", + "one_shot": False, } - iter_ = model.append((num, '')) + iter_ = model.append((num, "")) path = model.get_path(iter_) self._ui.conditions_treeview.set_cursor(path) self._active_num = num @@ -380,8 +378,7 @@ class ConfigDialog(Gtk.ApplicationWindow): path = model.get_path(iter_) iter_ = model.get_iter((path[0] - 1,)) model[iter_][0] = self._active_num - self._on_conditions_treeview_cursor_changed( - self._ui.conditions_treeview) + self._on_conditions_treeview_cursor_changed(self._ui.conditions_treeview) def _on_down_button_clicked(self, _button: Gtk.Button) -> None: selection = self._ui.conditions_treeview.get_selection() @@ -395,8 +392,7 @@ class ConfigDialog(Gtk.ApplicationWindow): model[iter_][0] = self._active_num + 1 iter_ = model.iter_next(iter_) model[iter_][0] = self._active_num - self._on_conditions_treeview_cursor_changed( - self._ui.conditions_treeview) + self._on_conditions_treeview_cursor_changed(self._ui.conditions_treeview) def _on_event_combobox_changed(self, combo: Gtk.ComboBox) -> None: if self._active_num < 0: @@ -405,21 +401,19 @@ class ConfigDialog(Gtk.ApplicationWindow): if active == -1: return event = list(EVENTS.keys())[active] - self._config[self._active_num]['event'] = event + self._config[self._active_num]["event"] = event for widget in EVENTS[event]: self._ui.__dict__[widget].set_sensitive(False) self._ui.__dict__[widget].set_state(False) self._set_treeview_string() - def _on_recipient_type_combobox_changed(self, - widget: Gtk.ComboBox - ) -> None: + def _on_recipient_type_combobox_changed(self, widget: Gtk.ComboBox) -> None: if self._active_num < 0: return recipient_type = RECIPIENT_TYPES[widget.get_active()] - self._config[self._active_num]['recipient_type'] = recipient_type - if recipient_type == 'all': + self._config[self._active_num]["recipient_type"] = recipient_type + if recipient_type == "all": self._ui.recipient_list_entry.set_sensitive(False) else: self._ui.recipient_list_entry.set_sensitive(True) @@ -430,19 +424,19 @@ class ConfigDialog(Gtk.ApplicationWindow): return recipients = widget.get_text() # TODO: do some check - self._config[self._active_num]['recipients'] = recipients + self._config[self._active_num]["recipients"] = recipients self._set_treeview_string() def _set_status_config(self) -> None: if self._active_num < 0: return - status = '' - for st in ('online', 'away', 'xa', 'dnd'): - if self._ui.__dict__[st + '_cb'].get_active(): - status += st + ' ' + status = "" + for st in ("online", "away", "xa", "dnd"): + if self._ui.__dict__[st + "_cb"].get_active(): + status += st + " " if status: status = status[:-1] - self._config[self._active_num]['status'] = status + self._config[self._active_num]["status"] = status self._set_treeview_string() def _on_status_radiobutton_toggled(self, _widget: Gtk.RadioButton) -> None: @@ -450,16 +444,16 @@ class ConfigDialog(Gtk.ApplicationWindow): return if self._ui.all_status_rb.get_active(): self._ui.status_expander.set_expanded(False) - self._config[self._active_num]['status'] = 'all' + self._config[self._active_num]["status"] = "all" # 'All status' clicked - for st in ('online', 'away', 'xa', 'dnd'): - self._ui.__dict__[st + '_cb'].set_sensitive(False) + for st in ("online", "away", "xa", "dnd"): + self._ui.__dict__[st + "_cb"].set_sensitive(False) else: self._ui.status_expander.set_expanded(True) self._set_status_config() # 'special status' clicked - for st in ('online', 'away', 'xa', 'dnd'): - self._ui.__dict__[st + '_cb'].set_sensitive(True) + for st in ("online", "away", "xa", "dnd"): + self._ui.__dict__[st + "_cb"].set_sensitive(True) self._set_treeview_string() @@ -476,26 +470,26 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.has_focus_cb.set_sensitive(True) self._ui.not_has_focus_cb.set_sensitive(True) if self._ui.not_tab_opened_cb.get_active(): - self._config[self._active_num]['tab_opened'] = 'both' + self._config[self._active_num]["tab_opened"] = "both" else: - self._config[self._active_num]['tab_opened'] = 'yes' + self._config[self._active_num]["tab_opened"] = "yes" else: self._ui.has_focus_cb.set_sensitive(False) self._ui.not_has_focus_cb.set_sensitive(False) self._ui.not_tab_opened_cb.set_active(True) - self._config[self._active_num]['tab_opened'] = 'no' + self._config[self._active_num]["tab_opened"] = "no" def _on_not_tab_opened_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): if self._ui.tab_opened_cb.get_active(): - self._config[self._active_num]['tab_opened'] = 'both' + self._config[self._active_num]["tab_opened"] = "both" else: - self._config[self._active_num]['tab_opened'] = 'no' + self._config[self._active_num]["tab_opened"] = "no" else: self._ui.tab_opened_cb.set_active(True) - self._config[self._active_num]['tab_opened'] = 'yes' + self._config[self._active_num]["tab_opened"] = "yes" # has_focus OR (not xor) not_has_focus must be active def _on_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None: @@ -503,87 +497,83 @@ class ConfigDialog(Gtk.ApplicationWindow): return if widget.get_active(): if self._ui.not_has_focus_cb.get_active(): - self._config[self._active_num]['has_focus'] = 'both' + self._config[self._active_num]["has_focus"] = "both" else: - self._config[self._active_num]['has_focus'] = 'yes' + self._config[self._active_num]["has_focus"] = "yes" else: self._ui.not_has_focus_cb.set_active(True) - self._config[self._active_num]['has_focus'] = 'no' + self._config[self._active_num]["has_focus"] = "no" def _on_not_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): if self._ui.has_focus_cb.get_active(): - self._config[self._active_num]['has_focus'] = 'both' + self._config[self._active_num]["has_focus"] = "both" else: - self._config[self._active_num]['has_focus'] = 'no' + self._config[self._active_num]["has_focus"] = "no" else: self._ui.has_focus_cb.set_active(True) - self._config[self._active_num]['has_focus'] = 'yes' + self._config[self._active_num]["has_focus"] = "yes" - def _on_use_it_toggled(self, - widget: Gtk.CheckButton, - opposite_widget: Gtk.CheckButton, - option: str - ) -> None: + def _on_use_it_toggled( + self, widget: Gtk.CheckButton, opposite_widget: Gtk.CheckButton, option: str + ) -> None: if widget.get_active(): if opposite_widget.get_active(): opposite_widget.set_active(False) - self._config[self._active_num][option] = 'yes' + self._config[self._active_num][option] = "yes" elif opposite_widget.get_active(): - self._config[self._active_num][option] = 'no' + self._config[self._active_num][option] = "no" else: - self._config[self._active_num][option] = '' + self._config[self._active_num][option] = "" - def _on_disable_it_toggled(self, - widget: Gtk.CheckButton, - opposite_widget: Gtk.CheckButton, - option: str - ) -> None: + def _on_disable_it_toggled( + self, widget: Gtk.CheckButton, opposite_widget: Gtk.CheckButton, option: str + ) -> None: if widget.get_active(): if opposite_widget.get_active(): opposite_widget.set_active(False) - self._config[self._active_num][option] = 'no' + self._config[self._active_num][option] = "no" elif opposite_widget.get_active(): - self._config[self._active_num][option] = 'yes' + self._config[self._active_num][option] = "yes" else: - self._config[self._active_num][option] = '' + self._config[self._active_num][option] = "" def _on_use_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._on_use_it_toggled(widget, self._ui.disable_sound_cb, 'sound') + self._on_use_it_toggled(widget, self._ui.disable_sound_cb, "sound") if widget.get_active(): self._ui.sound_file_box.set_sensitive(True) else: self._ui.sound_file_box.set_sensitive(False) def _on_sound_file_set(self, widget: Gtk.FileChooserButton) -> None: - self._config[self._active_num]['sound_file'] = widget.get_filename() + self._config[self._active_num]["sound_file"] = widget.get_filename() def _on_play_button_clicked(self, _button: Gtk.Button) -> None: play_sound_file(self._ui.filechooser.get_filename()) def _on_disable_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._on_disable_it_toggled(widget, self._ui.use_sound_cb, 'sound') + self._on_disable_it_toggled(widget, self._ui.use_sound_cb, "sound") def _on_use_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._on_use_it_toggled(widget, self._ui.disable_popup_cb, 'popup') + self._on_use_it_toggled(widget, self._ui.disable_popup_cb, "popup") def _on_disable_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._on_disable_it_toggled(widget, self._ui.use_popup_cb, 'popup') + self._on_disable_it_toggled(widget, self._ui.use_popup_cb, "popup") def _on_run_command_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._config[self._active_num]['run_command'] = widget.get_active() + self._config[self._active_num]["run_command"] = widget.get_active() if widget.get_active(): self._ui.command_entry.set_sensitive(True) else: self._ui.command_entry.set_sensitive(False) def _on_command_entry_changed(self, widget: Gtk.Entry) -> None: - self._config[self._active_num]['command'] = widget.get_text() + self._config[self._active_num]["command"] = widget.get_text() def _on_one_shot_cb_toggled(self, widget: Gtk.CheckButton) -> None: - self._config[self._active_num]['one_shot'] = widget.get_active() + self._config[self._active_num]["one_shot"] = widget.get_active() self._ui.command_entry.set_sensitive(widget.get_active()) diff --git a/triggers/triggers.py b/triggers/triggers.py index 49912de..4735cef 100644 --- a/triggers/triggers.py +++ b/triggers/triggers.py @@ -17,23 +17,33 @@ from __future__ import annotations +from typing import Any +from typing import Callable +from typing import cast +from typing import Union + import logging import subprocess from functools import partial -from typing import Any, Callable, Union, cast -from gajim.common import app, ged -from gajim.common.const import PROPAGATE_EVENT, STOP_EVENT -from gajim.common.events import MessageReceived, Notification, PresenceReceived +from nbxmpp.protocol import JID + +from gajim.common import app +from gajim.common import ged +from gajim.common.const import PROPAGATE_EVENT +from gajim.common.const import STOP_EVENT +from gajim.common.events import MessageReceived +from gajim.common.events import Notification +from gajim.common.events import PresenceReceived from gajim.common.helpers import play_sound_file from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ -from nbxmpp.protocol import JID from triggers.gtk.config import ConfigDialog -from triggers.util import RuleResult, log_result +from triggers.util import log_result +from triggers.util import RuleResult -log = logging.getLogger('gajim.p.triggers') +log = logging.getLogger("gajim.p.triggers") ProcessableEventsT = Union[MessageReceived, Notification, PresenceReceived] RuleT = dict[str, Any] @@ -42,36 +52,37 @@ RuleT = dict[str, Any] class Triggers(GajimPlugin): def init(self) -> None: self.description = _( - 'Configure Gajim’s behaviour with triggers for each contact') + "Configure Gajim’s behaviour with triggers for each contact" + ) self.config_dialog = partial(ConfigDialog, self) self.config_default_values = {} self.events_handlers = { - 'notification': (ged.PREGUI, self._on_notification), - 'message-received': (ged.PREGUI2, self._on_message_received), - 'gc-message-received': (ged.PREGUI2, self._on_message_received), + "notification": (ged.PREGUI, self._on_notification), + "message-received": (ged.PREGUI2, self._on_message_received), + "gc-message-received": (ged.PREGUI2, self._on_message_received), # 'presence-received': (ged.PREGUI, self._on_presence_received), } def _on_notification(self, event: Notification) -> bool: - log.info('Process %s, %s', event.name, event.type) - result = self._check_all(event, - self._check_rule_apply_notification, - self._apply_rule) - log.info('Result: %s', result) + log.info("Process %s, %s", event.name, event.type) + result = self._check_all( + event, self._check_rule_apply_notification, self._apply_rule + ) + log.info("Result: %s", result) return self._excecute_notification_rules(result, event) def _on_message_received(self, event: MessageReceived) -> bool: - log.info('Process %s', event.name) + log.info("Process %s", event.name) message = event.message if message.text is None: - log.info('Discard event because it has no message text') + log.info("Discard event because it has no message text") return PROPAGATE_EVENT - result = self._check_all(event, - self._check_rule_apply_msg_received, - self._apply_rule) - log.info('Result: %s', result) + result = self._check_all( + event, self._check_rule_apply_msg_received, self._apply_rule + ) + log.info("Result: %s", result) return self._excecute_message_rules(result) def _on_presence_received(self, event: PresenceReceived) -> None: @@ -86,11 +97,12 @@ class Triggers(GajimPlugin): check_func = self._check_rule_apply_status_changed self._check_all(event, check_func, self._apply_rule) - def _check_all(self, - event: ProcessableEventsT, - check_func: Callable[..., bool], - apply_func: Callable[..., Any] - ) -> RuleResult: + def _check_all( + self, + event: ProcessableEventsT, + check_func: Callable[..., bool], + apply_func: Callable[..., Any], + ) -> RuleResult: result = RuleResult() @@ -101,7 +113,7 @@ class Triggers(GajimPlugin): rule = cast(RuleT, self.config[str(num)]) if check_func(event, rule): apply_func(result, rule) - if 'one_shot' in rule and rule['one_shot']: + if "one_shot" in rule and rule["one_shot"]: to_remove.append(num) decal = 0 @@ -121,47 +133,38 @@ class Triggers(GajimPlugin): return result @log_result - def _check_rule_apply_msg_received(self, - event: MessageReceived, - rule: RuleT - ) -> bool: + def _check_rule_apply_msg_received( + self, event: MessageReceived, rule: RuleT + ) -> bool: - return self._check_rule_all('message_received', event, rule) + return self._check_rule_all("message_received", event, rule) @log_result - def _check_rule_apply_connected(self, - event: PresenceReceived, - rule: RuleT - ) -> bool: + def _check_rule_apply_connected(self, event: PresenceReceived, rule: RuleT) -> bool: - return self._check_rule_all('contact_connected', event, rule) + return self._check_rule_all("contact_connected", event, rule) @log_result - def _check_rule_apply_disconnected(self, - event: PresenceReceived, - rule: RuleT - ) -> bool: + def _check_rule_apply_disconnected( + self, event: PresenceReceived, rule: RuleT + ) -> bool: - return self._check_rule_all('contact_disconnected', event, rule) + return self._check_rule_all("contact_disconnected", event, rule) @log_result - def _check_rule_apply_status_changed(self, - event: PresenceReceived, - rule: RuleT - ) -> bool: + def _check_rule_apply_status_changed( + self, event: PresenceReceived, rule: RuleT + ) -> bool: - return self._check_rule_all('contact_status_change', event, rule) + return self._check_rule_all("contact_status_change", event, rule) @log_result - def _check_rule_apply_notification(self, - event: Notification, - rule: RuleT - ) -> bool: + def _check_rule_apply_notification(self, event: Notification, rule: RuleT) -> bool: # Check notification type - notif_type = '' - if event.type == 'incoming-message': - notif_type = 'message_received' + notif_type = "" + if event.type == "incoming-message": + notif_type = "message_received" # if event.type == 'pres': # # TODO: # if (event.base_event.old_show < 2 and @@ -175,14 +178,12 @@ class Triggers(GajimPlugin): return self._check_rule_all(notif_type, event, rule) - def _check_rule_all(self, - notif_type: str, - event: ProcessableEventsT, - rule: RuleT - ) -> bool: + def _check_rule_all( + self, notif_type: str, event: ProcessableEventsT, rule: RuleT + ) -> bool: # Check notification type - if rule['event'] != notif_type: + if rule["event"] != notif_type: return False # notification type is ok. Now check recipient @@ -205,21 +206,17 @@ class Triggers(GajimPlugin): return True @log_result - def _check_rule_recipients(self, - event: ProcessableEventsT, - rule: RuleT - ) -> bool: + def _check_rule_recipients(self, event: ProcessableEventsT, rule: RuleT) -> bool: - rule_recipients = [t.strip() for t in rule['recipients'].split(',')] - if rule['recipient_type'] == 'groupchat': + rule_recipients = [t.strip() for t in rule["recipients"].split(",")] + if rule["recipient_type"] == "groupchat": if event.jid in rule_recipients: return True return False - if (rule['recipient_type'] == 'contact' and event.jid not in - rule_recipients): + if rule["recipient_type"] == "contact" and event.jid not in rule_recipients: return False client = app.get_client(event.account) - contact = client.get_module('Contacts').get_contact(event.jid) + contact = client.get_module("Contacts").get_contact(event.jid) if contact.is_groupchat or not contact.is_in_roster: return False @@ -229,84 +226,74 @@ class Triggers(GajimPlugin): if group in rule_recipients: group_found = True break - if rule['recipient_type'] == 'group' and not group_found: + if rule["recipient_type"] == "group" and not group_found: return False return True @log_result - def _check_rule_status(self, - event: ProcessableEventsT, - rule: RuleT - ) -> bool: + def _check_rule_status(self, event: ProcessableEventsT, rule: RuleT) -> bool: - rule_statuses = rule['status'].split() + rule_statuses = rule["status"].split() client = app.get_client(event.account) - if rule['status'] != 'all' and client.status not in rule_statuses: + if rule["status"] != "all" and client.status not in rule_statuses: return False return True @log_result - def _check_rule_tab_opened(self, - event: ProcessableEventsT, - rule: RuleT - ) -> bool: + def _check_rule_tab_opened(self, event: ProcessableEventsT, rule: RuleT) -> bool: - if rule['tab_opened'] == 'both': + if rule["tab_opened"] == "both": return True tab_opened = False assert isinstance(event.jid, JID) if app.window.chat_exists(event.account, event.jid): tab_opened = True - if tab_opened and rule['tab_opened'] == 'no': + if tab_opened and rule["tab_opened"] == "no": return False - elif not tab_opened and rule['tab_opened'] == 'yes': + elif not tab_opened and rule["tab_opened"] == "yes": return False return True @log_result - def _check_rule_has_focus(self, - event: ProcessableEventsT, - rule: RuleT - ) -> bool: + def _check_rule_has_focus(self, event: ProcessableEventsT, rule: RuleT) -> bool: - if rule['has_focus'] == 'both': + if rule["has_focus"] == "both": return True - if rule['tab_opened'] == 'no': + if rule["tab_opened"] == "no": # Does not apply in this case return True assert isinstance(event.jid, JID) chat_active = app.window.is_chat_active(event.account, event.jid) - if chat_active and rule['has_focus'] == 'no': + if chat_active and rule["has_focus"] == "no": return False - elif not chat_active and rule['has_focus'] == 'yes': + elif not chat_active and rule["has_focus"] == "yes": return False return True def _apply_rule(self, result: RuleResult, rule: RuleT) -> None: - if rule['sound'] == 'no': + if rule["sound"] == "no": result.sound = False result.sound_file = None - elif rule['sound'] == 'yes': + elif rule["sound"] == "yes": result.sound = False - result.sound_file = rule['sound_file'] + result.sound_file = rule["sound_file"] - if rule['run_command']: - result.command = rule['command'] + if rule["run_command"]: + result.command = rule["command"] - if rule['popup'] == 'no': + if rule["popup"] == "no": result.show_notification = False - elif rule['popup'] == 'yes': + elif rule["popup"] == "yes": result.show_notification = True - def _excecute_notification_rules(self, - result: RuleResult, - event: Notification - ) -> bool: + def _excecute_notification_rules( + self, result: RuleResult, event: Notification + ) -> bool: if result.sound is False: event.sound = None @@ -324,7 +311,7 @@ class Triggers(GajimPlugin): if result.command is not None: try: - subprocess.Popen(f'{result.command} &', shell=True).wait() + subprocess.Popen(f"{result.command} &", shell=True).wait() except Exception: pass diff --git a/triggers/util.py b/triggers/util.py index ab712a0..a3acb21 100644 --- a/triggers/util.py +++ b/triggers/util.py @@ -25,14 +25,15 @@ if TYPE_CHECKING: from .triggers import ProcessableEventsT from .triggers import RuleT -log = logging.getLogger('gajim.p.triggers') +log = logging.getLogger("gajim.p.triggers") def log_result(func: Callable[..., Any]) -> Callable[..., bool]: def wrapper(self: Any, event: ProcessableEventsT, rule: RuleT): res = func(self, event, rule) - log.info(f'{event.name} -> {func.__name__} -> {res}') + log.info(f"{event.name} -> {func.__name__} -> {res}") return res + return wrapper