From 16b3b1887715125ccbcae1b8f5ca35a8cadfb0fa Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 1 May 2023 15:22:04 +0200 Subject: [PATCH 001/243] remove api_test --- api_test/__init__.py | 133 ------------------------------------------- api_test/plugin.svg | 1 - 2 files changed, 134 deletions(-) delete mode 100644 api_test/__init__.py delete mode 100644 api_test/plugin.svg diff --git a/api_test/__init__.py b/api_test/__init__.py deleted file mode 100644 index 5e088e50..00000000 --- a/api_test/__init__.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- - -"""This is a simple python template extension. - -This extension should show the API in a comprehensible way. Use the module docstring to provide a \ -description of the extension. The docstring should have three paragraphs: A brief description in \ -the first line, an optional elaborate description of the plugin, and finally the synopsis of the \ -extension. - -Synopsis: [delay|throw] """ - -# Copyright (c) 2022-2023 Manuel Schneider - -from albert import * -import os -from time import sleep - -md_iid = "0.5" -md_version = "1.3" -md_name = "API Test" -md_description = "Test the python API 0.5" -md_license = "BSD-3" -md_url = "https://github.com/albertlauncher/python/tree/master/api_test" -md_maintainers = "@manuelschneid3r" - - -class Plugin(QueryHandler): - def id(self): - return "test"; - - def name(self): - return "somename"; - - def description(self): - return "somedesc"; - - def initialize(self): - info("initialize") - - def finalize(self): - info("finalize") - - def extensions(self): - return [self.e] - - def handleQuery(self, query): - # Note that when storing a reference to query, e.g. in a closure, you must not use - # query.isValid. Apart from the query being invalid anyway it will crash the application. - # The Python type holds a pointer to the C++ type used for isValid(). The C++ type will be - # deleted when the query is finished. Therefore getting isValid will result in a SEGFAULT. - - if query.string.startswith("delay"): - sleep(2) - return query.add(Item(id=md_id, - text="Delayed test item", - subtext="Query string: %s" % query.string, - icon=[os.path.dirname(__file__)+"/plugin.svg"])) - - if query.string.startswith("throw"): - raise ValueError('EXPLICITLY REQUESTED TEST EXCEPTION!') - - info(query.string) - info(query.trigger) - info(str(query.isValid)) - - critical(query.string) - warning(query.string) - debug(query.string) - debug(query.string) - - results = [] - - item = Item() - - item.icon = ['xdg:albert'] - item.text = 'Python item containing %s' % query.string - item.subtext = 'Python description' - item.completion = 'Completion test' - info(item.icon) - info(item.text) - info(item.subtext) - info(item.completion) - results.append(item) - - item = Item(id=md_id, - text="This is the primary text", - subtext="This is the subtext, some kind of description", - completion='Hellooohooo!', - icon=[os.path.dirname(__file__)+"/plugin.svg"], - actions=[ - Action( - id="clip", - text="setClipboardText (ClipAction)", - callable=lambda: setClipboardText(text=configLocation()) - ), - Action( - id="url", - text="openUrl (UrlAction)", - callable=lambda: openUrl(url="https://www.google.de") - ), - Action( - id="run", - text="runDetachedProcess (ProcAction)", - callable=lambda: runDetachedProcess( - cmdln=["espeak", "hello"], - workdir="~" - ) - ), - Action( - id="term", - text="runTerminal (TermAction)", - callable=lambda: runTerminal( - script="[ -e issue ] && cat issue | echo /etc/issue not found.", - workdir="/etc", - close_on_exit=False - ) - ), - Action( - id="notify", - text="sendTrayNotification", - callable=lambda: sendTrayNotification( - title="Title", - msg="Message" - ) - ) - ]) - results.append(item) - - info(configLocation()) - info(cacheLocation()) - info(dataLocation()) - - query.add(results) diff --git a/api_test/plugin.svg b/api_test/plugin.svg deleted file mode 100644 index 6cbefc6d..00000000 --- a/api_test/plugin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 68e11eb334b74a8cee5a8ce023394fc1d97a410f Mon Sep 17 00:00:00 2001 From: Thomas Queste Date: Mon, 1 May 2023 15:27:30 +0200 Subject: [PATCH 002/243] [locate:1.7] Fix lambda capture Fix: https://github.com/albertlauncher/python/issues/167 --- locate/__init__.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/locate/__init__.py b/locate/__init__.py index d0c14ab0..180e76a5 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -11,17 +11,17 @@ import os import pathlib import shlex -import re import subprocess md_iid = "0.5" -md_version = "1.6" +md_version = "1.7" md_name = "Locate" -md_description = "Find ond open files using locate" +md_description = "Find and open files using locate" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/locate" md_bin_dependencies = "locate" + class Plugin(QueryHandler): def id(self): @@ -57,9 +57,11 @@ def handleQuery(self, query): return result = subprocess.run(['locate', *args], stdout=subprocess.PIPE, text=True) - if not query.isValid: return + if not query.isValid: + return lines = sorted(result.stdout.splitlines(), reverse=True) - if not query.isValid: return + if not query.isValid: + return for path in lines: basename = os.path.basename(path) @@ -70,7 +72,7 @@ def handleQuery(self, query): subtext=path, icon=self.icons, actions=[ - Action("open", "Open", lambda: openUrl("file://%s" % path)) + Action("open", "Open", lambda p=path: openUrl("file://%s" % p)) ] ) ) @@ -86,9 +88,3 @@ def handleQuery(self, query): ] ) ) - - - - - - From 962a832cc35377d5c719147800368a3e83703646 Mon Sep 17 00:00:00 2001 From: Oskar Haarklou Veileborg Date: Mon, 1 May 2023 15:30:00 +0200 Subject: [PATCH 003/243] [python_eval:1.3] Fix type of result in item subtext --- python_eval/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/python_eval/__init__.py b/python_eval/__init__.py index 5834792b..6f962eb3 100644 --- a/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -7,7 +7,7 @@ import os md_iid = "0.5" -md_version = "1.2" +md_version = "1.3" md_name = "Python Eval" md_description = "Evaluate Python code" md_license = "BSD-3" @@ -39,18 +39,20 @@ def handleQuery(self, query): stripped = query.string.strip() if stripped: try: - result = str(eval(stripped)) + result = eval(stripped) except Exception as ex: - result = str(ex) + result = ex + + result_str = str(result) query.add(Item( id=md_id, - text=str(result), + text=result_str, subtext=type(result).__name__, - completion=query.trigger + result, + completion=query.trigger + result_str, icon=[self.iconPath], actions = [ - Action("copy", "Copy result to clipboard", lambda r=str(result): setClipboardText(r)), - Action("exec", "Execute python code", lambda r=str(result): exec(stripped)), + Action("copy", "Copy result to clipboard", lambda r=result_str: setClipboardText(r)), + Action("exec", "Execute python code", lambda r=result_str: exec(stripped)), ] )) From 7e07e375852527cdf5b472a606b360b4ddee0137 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 17 May 2023 10:40:13 +0200 Subject: [PATCH 004/243] Add telegram issue notifier workflow --- .github/workflows/telegram_notify_issues.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/telegram_notify_issues.yml diff --git a/.github/workflows/telegram_notify_issues.yml b/.github/workflows/telegram_notify_issues.yml new file mode 100644 index 00000000..533f66b9 --- /dev/null +++ b/.github/workflows/telegram_notify_issues.yml @@ -0,0 +1,16 @@ +name: Telegram Notifications + +on: + issues: + types: [opened, reopened, deleted, closed] + +jobs: + notify: + + runs-on: ubuntu-latest + + steps: + - name: Send notifications to Telegram + run: curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} -d text="${MESSAGE}" >> /dev/null + env: + MESSAGE: "Issue ${{ github.event.action }}: \n${{ github.event.issue.html_url }}" From c5f707534ef64e303851042e15ea23b1796f670f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 3 May 2023 15:52:50 +0200 Subject: [PATCH 005/243] [all] Adopt iid v1.0 --- arch_wiki/__init__.py | 8 ++-- aur/__init__.py | 8 ++-- bitwarden/__init__.py | 8 ++-- copyq/__init__.py | 8 ++-- dice_roll/__init__.py | 8 ++-- docker/__init__.py | 86 +++++++++++++++++++--------------- emoji/__init__.py | 8 ++-- goldendict/__init__.py | 10 ++-- googletrans/__init__.py | 8 ++-- jetbrains_projects/__init__.py | 10 ++-- kill/__init__.py | 8 ++-- locate/__init__.py | 8 ++-- pacman/__init__.py | 10 ++-- pass/__init__.py | 8 ++-- pomodoro/__init__.py | 8 ++-- python_eval/__init__.py | 8 ++-- tex_to_unicode/__init__.py | 8 ++-- timer/__init__.py | 8 ++-- unit_converter/__init__.py | 8 ++-- virtualbox/__init__.py | 8 ++-- vpn/__init__.py | 8 ++-- wikipedia/__init__.py | 8 ++-- youtube/__init__.py | 10 ++-- 23 files changed, 141 insertions(+), 129 deletions(-) diff --git a/arch_wiki/__init__.py b/arch_wiki/__init__.py index f6474431..0c531506 100644 --- a/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -7,8 +7,8 @@ import json import os -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_name = "ArchLinux Wiki" md_description = "Search ArchLinux Wiki articles" md_license = "BSD-3" @@ -16,7 +16,7 @@ md_maintainers = "@manuelschneid3r" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): icon = [os.path.dirname(__file__) + "/ArchWiki.svg"] baseurl = 'https://wiki.archlinux.org/api.php' @@ -35,7 +35,7 @@ def description(self): def defaultTrigger(self): return "awiki " - def handleQuery(self, query): + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: diff --git a/aur/__init__.py b/aur/__init__.py index 9a5f3a5b..6d9e6c13 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -14,8 +14,8 @@ import json import os -md_iid = "0.5" -md_version = "1.6" +md_iid = '1.0' +md_version = "1.7" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "BSD-3" @@ -23,7 +23,7 @@ md_maintainers = "@manuelschneid3r" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): aur_url = "https://aur.archlinux.org/packages/" baseurl = 'https://aur.archlinux.org/rpc/' @@ -55,7 +55,7 @@ def initialize(self): info("No supported AUR helper found.") self.install_cmdline = None - def handleQuery(self, query): + def handleTriggerQuery(self, query): for number in range(50): sleep(0.01) if not query.isValid: diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 3f482cfa..0185358b 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = "0.5" -md_version = "1.1" +md_iid = '1.0' +md_version = "1.2" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "BSD-3" @@ -15,7 +15,7 @@ md_credits = "Original author: @tylio" md_bin_dependencies = ["rbw"] -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -51,7 +51,7 @@ def _get_passwords(self): return passwords - def handleQuery(self, query): + def handleTriggerQuery(self, query): if query.string.strip().lower() == "unlock": query.add( Item( diff --git a/copyq/__init__.py b/copyq/__init__.py index fa1104c0..7c241b55 100644 --- a/copyq/__init__.py +++ b/copyq/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_name = "CopyQ" md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" @@ -45,7 +45,7 @@ """ -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -61,7 +61,7 @@ def synopsis(self): def defaultTrigger(self): return "cq " - def handleQuery(self, query): + def handleTriggerQuery(self, query): items = [] q_string = query.string diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index 4606d541..944e054c 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -14,8 +14,8 @@ Example: "roll 2d6 3d8 1d20" """ -md_iid = "0.5" -md_version = "1.0" +md_iid = '1.0' +md_version = "1.1" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -128,7 +128,7 @@ def get_items(query_string: str) -> list[albert.Item]: return results -class Plugin(albert.QueryHandler): +class Plugin(albert.TriggerQueryHandler): """A plugin to roll dice""" def id(self) -> str: @@ -146,7 +146,7 @@ def synopsis(self) -> str: def defaultTrigger(self) -> str: return "roll " - def handleQuery(self, query: albert.Query) -> None: + def handleTriggerQuery(self, query: albert.TriggerQuery) -> None: query_string = query.string.strip() try: items = get_items(query_string) diff --git a/docker/__init__.py b/docker/__init__.py index a6f2ec28..eb8fb412 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -8,8 +8,8 @@ import pathlib import docker -md_iid = "0.5" -md_version = "1.3" +md_iid = "1.0" +md_version = "1.4" md_name = "Docker" md_description = "Control your docker instance" md_license = "BSD-3" @@ -41,43 +41,55 @@ def initialize(self): if not self.client: raise "Failed to initialize client." + def handleGlobalQuery(self, query): + rank_items = [] - def handleQuery(self, query): for container in self.client.containers.list(all=True): + if query.string in container.name: + # Create dynamic actions + if container.status == 'running': + actions = [ + Action("stop", "Stop container", lambda c=container: c.stop()), + Action("restart", "Restart container", lambda c=container: c.restart()) + ] + else: + actions = [ + Action("start", "Start container", lambda c=container: c.start()) + ] + actions.extend([ + Action("logs", "Logs", lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), + Action("remove", "Remove (forced, with volumes)", lambda c=container: c.remove(v=True, force=True)), + Action("copy-id", "Copy id to clipboard", lambda id=container.id: setClipboardText(id)) + ]) - # Create dynamic actions - if container.status == 'running': - actions = [ - Action("stop", "Stop container", lambda c=container: c.stop()), - Action("restart", "Restart container", lambda c=container: c.restart()) - ] - else: - actions = [ - Action("start", "Start container", lambda c=container: c.start()) - ] - actions.extend([ - Action("logs", "Logs", lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), - Action("remove", "Remove (forced, with volumes)", lambda c=container: c.remove(v=True, force=True)), - Action("copy-id", "Copy id to clipboard", lambda id=container.id: setClipboardText(id)) - ]) - - query.add(Item( - id=container.id, - text="%s (%s)" % (container.name, ", ".join(container.image.tags)), - subtext=container.id, - icon=self.icon_running if container.status == 'running' else self.icon_stopped, - actions=actions - )) + rank_items.append(RankItem( + item=Item( + id=container.id, + text="%s (%s)" % (container.name, ", ".join(container.image.tags)), + subtext="Container: %s" % container.id, + icon=self.icon_running if container.status == 'running' else self.icon_stopped, + actions=actions + ), + score=0 # len(query.string)/len(container.name) + )) for image in reversed(self.client.images.list()): - query.add(Item( - id=image.short_id, - text=str(image.tags), - subtext=image.id, - icon=self.icon_stopped, - actions=[ - Action("run", "Run with command: %s" % query.string, - lambda i=image, s=query.string: client.containers.run(i, s)), - Action("rmi", "Remove image", lambda i=image: i.remove()) - ] - )) + if any([query.string in tag for tag in image.tags]): + rank_items.append(RankItem( + item=Item( + id=image.short_id, + text=", ".join(image.tags), + subtext="Image: %s" % image.id, + icon=self.icon_stopped, + actions=[ + Action("run", "Run with command: %s" % query.string, + lambda i=image, s=query.string: client.containers.run(i, s)), + Action("rmi", "Remove image", lambda i=image: i.remove()) + ] + ), + score=0 + )) + + + return rank_items + diff --git a/emoji/__init__.py b/emoji/__init__.py index e8df5a88..9b4304ea 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -13,8 +13,8 @@ from albert import Action, Item, QueryHandler, cacheLocation, setClipboardText -md_iid = "0.5" -md_version = "1.0" +md_iid = '1.0' +md_version = "1.1" md_name = "Emoji Picker" md_description = "Find emojis by name" md_license = "GPL-3.0" @@ -60,7 +60,7 @@ def schedule_create_missing_icons(emojis): return executor -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return __name__ @@ -126,7 +126,7 @@ def matched_emojis(self, query_tokens): yield emoji - def handleQuery(self, query): + def handleTriggerQuery(self, query): query_tokens = query.string.strip().lower().split() if not query_tokens: return diff --git a/goldendict/__init__.py b/goldendict/__init__.py index 6f9c516f..c1bbd3db 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,7 +1,7 @@ -from albert import Action, Item, Query, QueryHandler, runDetachedProcess # pylint: disable=import-error +from albert import Action, Item, TriggerQuery, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error -md_iid = '0.5' -md_version = '1.1' +md_iid = '1.0' +md_version = '1.2' md_name = 'GoldenDict' md_description = 'Searches in GoldenDict' md_url = 'https://github.com/albertlauncher/python/' @@ -12,7 +12,7 @@ ICON_PATH = '/usr/share/pixmaps/goldendict.png' -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self) -> str: return __name__ @@ -28,7 +28,7 @@ def defaultTrigger(self) -> str: def synopsis(self) -> str: return 'query' - def handleQuery(self, query: Query) -> None: + def handleTriggerQuery(self, query: TriggerQuery) -> None: query_str = query.string.strip() if not query_str: return diff --git a/googletrans/__init__.py b/googletrans/__init__.py index c89f7c54..65e3873d 100644 --- a/googletrans/__init__.py +++ b/googletrans/__init__.py @@ -10,8 +10,8 @@ from time import sleep import os -md_iid = "0.5" -md_version = "1.0" +md_iid = '1.0' +md_version = "1.1" md_name = "Google Translate" md_description = "Translate sentences using googletrans" md_license = "BSD-3" @@ -19,7 +19,7 @@ md_lib_dependencies = "googletrans==3.1.0a0" md_maintainers = "@manuelschneid3r" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -41,7 +41,7 @@ def initialize(self): self.translator = Translator() self.lang = getdefaultlocale()[0][0:2] - def handleQuery(self, query): + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: for number in range(50): diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 48f3b5d4..43ca93cc 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -15,8 +15,8 @@ from xml.etree import ElementTree from albert import * -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "GPL-3" @@ -82,7 +82,7 @@ def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: return [] -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): executables = [] def id(self): @@ -161,7 +161,7 @@ def initialize(self): ] self.editors = [e for e in editors if e.binary is not None] - def handleQuery(self, query: Query): + def handleTriggerQuery(self, query: TriggerQuery): editor_project_pairs = [] for editor in self.editors: projects = editor.list_projects() @@ -174,7 +174,7 @@ def handleQuery(self, query: Query): query.add([self._make_item(editor, project, query) for editor, project in editor_project_pairs]) - def _make_item(self, editor: Editor, project: Project, query: Query) -> Item: + def _make_item(self, editor: Editor, project: Project, query: TriggerQuery) -> Item: return Item( id="%s-%s-%s" % (editor.binary, project.path, project.last_opened), text=project.name, diff --git a/kill/__init__.py b/kill/__init__.py index 4ed37d4b..7aec13c8 100644 --- a/kill/__init__.py +++ b/kill/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = "0.5" -md_version = "1.1" +md_iid = '1.0' +md_version = "1.2" md_name = "Kill Process" md_description = "Kill processes" md_license = "BSD-3" @@ -15,7 +15,7 @@ md_credits = "Original idea by Benedict Dudel & Manuel Schneider" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): icon_path = "xdg:process-stop" def id(self): @@ -33,7 +33,7 @@ def initialize(self): def defaultTrigger(self): return "kill " - def handleQuery(self, query): + def handleTriggerQuery(self, query): if not query.isValid: return results = [] diff --git a/locate/__init__.py b/locate/__init__.py index 180e76a5..40fef9bb 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -13,8 +13,8 @@ import shlex import subprocess -md_iid = "0.5" -md_version = "1.7" +md_iid = '1.0' +md_version = "1.8" md_name = "Locate" md_description = "Find and open files using locate" md_license = "BSD-3" @@ -22,7 +22,7 @@ md_bin_dependencies = "locate" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -48,7 +48,7 @@ def initialize(self): str(pathlib.Path(__file__).parent / "locate.svg") ] - def handleQuery(self, query): + def handleTriggerQuery(self, query): if len(query.string) > 2: try: diff --git a/pacman/__init__.py b/pacman/__init__.py index a15efefe..0b2ee192 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -9,10 +9,10 @@ from time import sleep import pathlib -from albert import Action, Item, QueryHandler, runTerminal, openUrl +from albert import Action, Item, TriggerQueryHandler, runTerminal, openUrl -md_iid = "0.5" -md_version = "1.6" +md_iid = '1.0' +md_version = "1.7" md_name = "PacMan" md_description = "Search, install and remove packages" md_license = "BSD-3" @@ -20,7 +20,7 @@ md_bin_dependencies = ["pacman", "expac"] -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): pkgs_url = "https://www.archlinux.org/packages/" @@ -46,7 +46,7 @@ def initialize(self): str(pathlib.Path(__file__).parent / "arch.svg") ] - def handleQuery(self, query): + def handleTriggerQuery(self, query): stripped = query.string.strip() # Update item on empty queries diff --git a/pass/__init__.py b/pass/__init__.py index 7bd3b62c..0a76a51f 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -4,8 +4,8 @@ import os from albert import * -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_name = "Pass" md_description = "Manage passwords in pass" md_bin_dependencies = ["pass"] @@ -16,7 +16,7 @@ PASS_DIR = os.environ.get("PASSWORD_STORE_DIR", os.path.join(HOME_DIR, ".password-store/")) ICON = ["xdg:dialog-password"] -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -33,7 +33,7 @@ def synopsis(self): def defaultTrigger(self): return "pass " - def handleQuery(self, query): + def handleTriggerQuery(self, query): if query.string.strip().startswith("generate"): self.generatePassword(query) else: diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 24cef355..53faba9b 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -11,8 +11,8 @@ import time import os -md_iid = "0.5" -md_version = "1.1" +md_iid = '1.0' +md_version = "1.2" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "BSD-3" @@ -66,7 +66,7 @@ def isActive(self): return self.timer is not None -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): icon = [os.path.dirname(__file__) + "/pomodoro.svg"] default_pomodoro_duration = 25 @@ -92,7 +92,7 @@ def initialize(self): def synopsis(self): return "[duration [break duration [long break duration [count]]]]" - def handleQuery(self, query): + def handleTriggerQuery(self, query): item = Item( id=md_id, icon=self.icon, diff --git a/python_eval/__init__.py b/python_eval/__init__.py index 6f962eb3..71a480ff 100644 --- a/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -6,8 +6,8 @@ from math import * import os -md_iid = "0.5" -md_version = "1.3" +md_iid = '1.0' +md_version = "1.4" md_name = "Python Eval" md_description = "Evaluate Python code" md_license = "BSD-3" @@ -15,7 +15,7 @@ md_maintainers = "@manuelschneid3r" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self): return md_id @@ -35,7 +35,7 @@ def synopsis(self): def initialize(self): self.iconPath = os.path.dirname(__file__)+"/python.svg" - def handleQuery(self, query): + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: try: diff --git a/tex_to_unicode/__init__.py b/tex_to_unicode/__init__.py index e0e38eb5..4fbdffed 100644 --- a/tex_to_unicode/__init__.py +++ b/tex_to_unicode/__init__.py @@ -9,8 +9,8 @@ from albert import * from pylatexenc.latex2text import LatexNodes2Text -md_iid = "0.5" -md_version = "1.0" +md_iid = '1.0' +md_version = "1.1" md_name = "TeX to Unicode" md_description = "Convert TeX mathmode commands to unicode characters" md_license = "GPL-3.0" @@ -19,7 +19,7 @@ md_maintainers = "@DenverCoder1" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def id(self) -> str: return md_id @@ -57,7 +57,7 @@ def _create_item(self, text: str, subtext: str, can_copy: bool) -> Item: actions=actions, ) - def handleQuery(self, query: Query) -> None: + def handleTriggerQuery(self, query: Query) -> None: stripped = query.string.strip() if not stripped: diff --git a/timer/__init__.py b/timer/__init__.py index a6ce8ae9..5fc96bbb 100644 --- a/timer/__init__.py +++ b/timer/__init__.py @@ -18,8 +18,8 @@ import os import subprocess -md_iid = "0.5" -md_version = "1.4" +md_iid = '1.0' +md_version = "1.5" md_name = "Timer" md_description = "Set up timers" md_license = "BSD-2" @@ -37,7 +37,7 @@ def __init__(self, interval, name, callback): self.start() -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): def initialize(self): self.icons = [os.path.dirname(__file__)+"/time.svg"] @@ -83,7 +83,7 @@ def defaultTrigger(self): def synopsis(self): return '[[hrs:]mins:]secs [name]' - def handleQuery(self, query): + def handleTriggerQuery(self, query): if not query.isValid: return diff --git a/unit_converter/__init__.py b/unit_converter/__init__.py index 3a4a840d..d25f547f 100644 --- a/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -29,8 +29,8 @@ import pint -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_name = "Unit Converter" md_description = "Convert between units" md_license = "MIT" @@ -309,7 +309,7 @@ def convert(self, amount: float, from_unit: str, to_unit: str) -> ConversionResu ) -class Plugin(albert.QueryHandler): +class Plugin(albert.TriggerQueryHandler): """The plugin class""" unit_convert_regex = re.compile( @@ -370,7 +370,7 @@ def synopsis(self) -> str: def defaultTrigger(self) -> str: return "convert " - def handleQuery(self, query: albert.Query) -> None: + def handleTriggerQuery(self, query: albert.TriggerQuery) -> None: query_string = query.string.strip() match = self.unit_convert_regex.fullmatch(query_string) if match: diff --git a/virtualbox/__init__.py b/virtualbox/__init__.py index 7b789696..f084bcfe 100644 --- a/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = "0.5" -md_version = "1.3" +md_iid = '1.0' +md_version = "1.4" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" md_license = "BSD-3" @@ -47,7 +47,7 @@ def pauseVm(vm): with vm.create_session(LockType.shared) as session: session.console.pause() -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): iconUrls = ["xdg:virtualbox", ":unknown"] def id(self): @@ -65,7 +65,7 @@ def synopsis(self): def defaultTrigger(self): return "vbox " - def handleQuery(self, query): + def handleTriggerQuery(self, query): items = [] pattern = query.string.strip().lower() try: diff --git a/vpn/__init__.py b/vpn/__init__.py index 44af6857..3d6fae20 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -2,8 +2,8 @@ from collections import namedtuple import subprocess -md_iid = "0.5" -md_version = "1.2" +md_iid = '1.0' +md_version = "1.3" md_id = "vpn" md_name = "VPN" md_description = "Manage NetworkManager VPN connections" @@ -14,7 +14,7 @@ md_bin_dependencies = ["nmcli"] -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): iconPath = ['xdg:network-wired'] @@ -56,7 +56,7 @@ def buildItem(self,con): ) - def handleQuery(self,query): + def handleTriggerQuery(self,query): if query.isValid: connections = self.getVPNConnections() if query.string: diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index 99f39a93..b4a9c921 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -10,8 +10,8 @@ import json import os -md_iid = "0.5" -md_version = "1.5" +md_iid = '1.0' +md_version = "1.6" md_name = "Wikipedia" md_description = "Search Wikipedia articles." md_license = "BSD-3" @@ -19,7 +19,7 @@ md_maintainers = "@manuelschneid3r" -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): iconPath = ":wikipedia" baseurl = 'https://en.wikipedia.org/w/api.php' @@ -63,7 +63,7 @@ def initialize(self): critical('Error getting languages (%s). Defaulting to EN.' % error) - def handleQuery(self, query): + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: # avoid rate limiting diff --git a/youtube/__init__.py b/youtube/__init__.py index 598422f8..17f5bc9e 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -8,11 +8,11 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen -from albert import Action, Item, Query, QueryHandler, critical, info, openUrl # pylint: disable=import-error +from albert import Action, Item, TriggerQuery, TriggerQueryHandler, critical, info, openUrl # pylint: disable=import-error -md_iid = '0.5' -md_version = '1.3' +md_iid = '1.0' +md_version = '1.4' md_name = 'YouTube' md_description = 'Query and open YouTube videos and channels' md_url = 'https://github.com/albertlauncher/python/' @@ -111,7 +111,7 @@ def results_to_items(results: dict) -> list[Item]: return items -class Plugin(QueryHandler): +class Plugin(TriggerQueryHandler): temp_dir = None def id(self) -> str: @@ -137,7 +137,7 @@ def defaultTrigger(self) -> str: def synopsis(self) -> str: return 'query' - def handleQuery(self, query: Query) -> None: + def handleTriggerQuery(self, query: TriggerQuery) -> None: query_str = query.string.strip() if not query_str: return From 62c76a769b87dc85fae5f7ba8c3dd4d47b5ae776 Mon Sep 17 00:00:00 2001 From: Thomas Queste Date: Sat, 27 May 2023 15:14:24 +0200 Subject: [PATCH 006/243] [emoji:v1.2] Set correct import for iid 1.0 --- emoji/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 9b4304ea..6e79c0c9 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -10,11 +10,10 @@ from concurrent.futures import ThreadPoolExecutor from itertools import islice from pathlib import Path - -from albert import Action, Item, QueryHandler, cacheLocation, setClipboardText +from albert import * md_iid = '1.0' -md_version = "1.1" +md_version = "1.2" md_name = "Emoji Picker" md_description = "Find emojis by name" md_license = "GPL-3.0" From 4940cc2efa82ed58346669e9e31476d31f8e9569 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 13 Jun 2023 11:53:05 +0200 Subject: [PATCH 007/243] adjust telegram notifications --- .../workflows/telegram_notify_comments.yml | 25 +++++++++++++++++++ .github/workflows/telegram_notify_issues.yml | 14 ++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/telegram_notify_comments.yml diff --git a/.github/workflows/telegram_notify_comments.yml b/.github/workflows/telegram_notify_comments.yml new file mode 100644 index 00000000..a7658abd --- /dev/null +++ b/.github/workflows/telegram_notify_comments.yml @@ -0,0 +1,25 @@ +name: Telegram Notifications + +on: + + issue_comment: + types: [created] + +jobs: + notify: + + runs-on: ubuntu-latest + + steps: + - name: Send notifications to Telegram + run: > + curl -s + -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage + -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} + -d text="${MESSAGE}" + -d parse_mode=HTML + -d disable_web_page_preview=true + >> /dev/null + env: + MESSAGE: "${{ github.event.comment.user.login }} on ${{ github.event.repository.name }}#${{ github.event.issue.number }}: ${{ github.event.issue.title }}%0A${{ github.event.comment.body }}" + diff --git a/.github/workflows/telegram_notify_issues.yml b/.github/workflows/telegram_notify_issues.yml index 533f66b9..dfcc0ab4 100644 --- a/.github/workflows/telegram_notify_issues.yml +++ b/.github/workflows/telegram_notify_issues.yml @@ -2,7 +2,7 @@ name: Telegram Notifications on: issues: - types: [opened, reopened, deleted, closed] + types: [opened, reopened] jobs: notify: @@ -11,6 +11,14 @@ jobs: steps: - name: Send notifications to Telegram - run: curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} -d text="${MESSAGE}" >> /dev/null + run: > + curl -s + -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage + -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} + -d text="${MESSAGE}" + -d parse_mode=HTML + -d disable_web_page_preview=true + >> /dev/null env: - MESSAGE: "Issue ${{ github.event.action }}: \n${{ github.event.issue.html_url }}" + MESSAGE: "New issue:%0A${{ github.event.repository.name }}#${{ github.event.issue.number }}: ${{ github.event.issue.title }}" + From 926e119ecf2dd2aa66209e047b41f1466d7b7b2f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 18 Jun 2023 09:41:21 +0200 Subject: [PATCH 008/243] [zeal] Port to iid v1.0 --- .archive/zeal/__init__.py | 29 ---------------------------- zeal/__init__.py | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 29 deletions(-) delete mode 100644 .archive/zeal/__init__.py create mode 100644 zeal/__init__.py diff --git a/.archive/zeal/__init__.py b/.archive/zeal/__init__.py deleted file mode 100644 index 83db8f85..00000000 --- a/.archive/zeal/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Open and search in Zeal offline docs. - - Synopsis: """ - -# Copyright (c) 2022 Manuel Schneider - -from subprocess import run -from albert import * - -__title__ = "Zeal" -__version__ = "0.4.0" -__triggers__ = "zl " -__authors__ = "Manuel S." -__exec_deps__ = ["zeal"] - -iconPath = iconLookup('zeal') - -def handleQuery(query): - if query.isTriggered: - return Item( - id=__title__, - icon=iconPath, - text=__title__, - subtext="Look up %s" % __title__, - actions=[ProcAction("Start query in %s" % __title__, - ["zeal", query.string])] - ) diff --git a/zeal/__init__.py b/zeal/__init__.py new file mode 100644 index 00000000..5677ecba --- /dev/null +++ b/zeal/__init__.py @@ -0,0 +1,40 @@ +"""Search in Zeal offline docs.""" + +from subprocess import run +from albert import * + +md_iid = '1.0' +md_version = '1.1' +md_name = 'Zeal' +md_description = 'Search in Zeal docs' +md_url = 'https://github.com/albertlauncher/python/zeal' +md_bin_dependencies = ['zeal'] + + +class Plugin(TriggerQueryHandler): + iconUrl = "xdg:zeal" + + def id(self): + return md_id + + def name(self): + return md_name + + def description(self): + return md_description + + def defaultTrigger(self): + return 'z ' + + def handleTriggerQuery(self, query): + stripped = query.string.strip() + if stripped: + query.add( + Item( + id=md_name, + text=md_name, + subtext=f"Search '{stripped}' in Zeal", + icon=[Plugin.iconUrl], + actions=[Action("zeal", "Search in Zeal", lambda s=stripped: runDetachedProcess(['zeal', s]))] + ) + ) From 271e0d6021941497e9f61071a67a03422d595283 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 18 Jun 2023 14:29:14 +0200 Subject: [PATCH 009/243] [coingecko] Add extension --- .archive/coinmarketcap/__init__.py | 150 ----------------------- .archive/coinmarketcap/emblem-money.svg | 1 - coingecko/__init__.py | 154 ++++++++++++++++++++++++ coingecko/coingecko.png | Bin 0 -> 21568 bytes 4 files changed, 154 insertions(+), 151 deletions(-) delete mode 100644 .archive/coinmarketcap/__init__.py delete mode 100644 .archive/coinmarketcap/emblem-money.svg create mode 100644 coingecko/__init__.py create mode 100644 coingecko/coingecko.png diff --git a/.archive/coinmarketcap/__init__.py b/.archive/coinmarketcap/__init__.py deleted file mode 100644 index ae024db4..00000000 --- a/.archive/coinmarketcap/__init__.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Show and access crypto currencies on CoinmMarketCap.com. - -The values of "Change" are the hourly, daily and weekly changes of the price in percent. "Cap" is \ -the market capitalisation in USD. Volume is the volume of the last 24 hours in USD. - -Synopsis: [filter]""" - -from albert import * -from threading import Thread, Event -from locale import format as lformat -from urllib import request -from urllib.parse import urlencode -import re -import os -import json - -__title__ = "CoinMarketCap" -__version__ = "0.4.4" -__triggers__ = "cmc " -__authors__ = "Manuel S." - -iconPath = os.path.dirname(__file__)+"/emblem-money.svg" -thread = None -coins = None - - -class Coin(): - def __init__(self, identifier, name, symbol, rank, price, - cap, vol, change_hour, change_day, change_week): - self.identifier = identifier - self.name = name - self.symbol = symbol - self.rank = rank - self.price = price - self.cap = cap - self.vol = vol - self.change_hour = change_hour - self.change_day = change_day - self.change_week = change_week - - -class UpdateThread(Thread): - def __init__(self): - super().__init__() - self._stopevent = Event() - - def run(self): - - while True: - url = "%s?%s" % ("https://api.coinmarketcap.com/v1/ticker/", urlencode({'limit': 0})) - req = request.Request(url) - with request.urlopen(req) as response: - if self._stopevent.is_set(): - return - - def colorize_float(value: str): - if value is None: - return value - elif float(value) < 0: - return "%s" % value - elif float(value) > 0: - return "%s" % value - else: - return value - - # Get coin data - data = json.loads(response.read().decode('utf-8')) - newCoins = [] - for coindata in data: - cap = coindata['market_cap_usd'] - cap = lformat("%d", float(cap), True) if cap else "?" - vol = coindata['24h_volume_usd'] - vol = lformat("%d", float(vol), True) if vol else "?" - price = coindata['price_usd'] - price_precision = "%.2f" if float(price) > 1 else "%.6f" - price = lformat(price_precision, float(price), True) if price else "?" - if "," in price: - price = price.rstrip("0").rstrip(",") - newCoins.append(Coin(identifier=coindata['id'], - name=coindata['name'], - symbol=coindata['symbol'], - rank=coindata['rank'], - price=price, - cap=cap, - vol=vol, - change_hour=colorize_float(coindata['percent_change_1h']), - change_day=colorize_float(coindata['percent_change_24h']), - change_week=colorize_float(coindata['percent_change_7d']))) - global coins - coins = newCoins - - self._stopevent.wait(900) # Sleep 15 min, wakeup on stop event - if self._stopevent.is_set(): - return - - def stop(self): - self._stop_event.set() - - -def initialize(): - thread = UpdateThread() - thread.start() - - -def finalize(): - if thread is not None: - thread.stop() - thread.join() - - -def handleQuery(query): - if not query.isTriggered or coins is None: - return - - stripped = query.string.strip().lower() - items = [] - if stripped: - pattern = re.compile(stripped, re.IGNORECASE) - for coin in coins: - if coin.name.lower().startswith(stripped) or coin.symbol.lower().startswith(stripped): - url = "https://coinmarketcap.com/currencies/%s/" % coin.identifier - items.append(Item( - id=__title__, - icon=iconPath, - text="#%s %s (%s) %s$" % (coin.rank, pattern.sub(lambda m: "%s" % m.group(0), coin.name), - pattern.sub(lambda m: "%s" % m.group(0), coin.symbol), coin.price), - subtext="Change: %s/%s/%s, Cap: %s, Volume: %s" % (coin.change_hour, coin.change_day, coin.change_week, coin.cap, coin.vol), - completion=coin.price, - actions=[ - UrlAction("Show on CoinMarketCap website", url), - ClipAction('Copy URL to clipboard', url) - ] - )) - else: - for coin in coins: - url = "https://coinmarketcap.com/currencies/%s/" % coin.identifier - items.append(Item( - id=__title__, - icon=iconPath, - text="#%s %s (%s) %s$" % (coin.rank, coin.name, coin.symbol, coin.price), - subtext="Change: %s/%s/%s, Cap: %s, Volume: %s" % (coin.change_hour, coin.change_day, coin.change_week, coin.cap, coin.vol), - completion=coin.price, - actions=[ - UrlAction("Show on CoinMarketCap website", url), - ClipAction('Copy URL to clipboard', url) - ] - )) - return items diff --git a/.archive/coinmarketcap/emblem-money.svg b/.archive/coinmarketcap/emblem-money.svg deleted file mode 100644 index 256f9cd8..00000000 --- a/.archive/coinmarketcap/emblem-money.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/coingecko/__init__.py b/coingecko/__init__.py new file mode 100644 index 00000000..2d0ff769 --- /dev/null +++ b/coingecko/__init__.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +"""Show and access crypto currencies on CoinGecko.com.""" + +from albert import * +from time import time +from urllib import request +from json import load, loads, dumps +from pathlib import Path +from threading import Thread, Event + +md_iid = "1.0" +md_version = "1.0" +md_name = "CoinGecko" +md_description = "Access CoinGecko" +md_license = "BSD-3" +md_url = "https://github.com/albertlauncher/python/tree/master/coingecko" + + +class CoinFetcherThread(Thread): + def __init__(self, callback): + super().__init__() + self._stop_event = Event() + self.callback = callback + + def _fetchCoins(self): + url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250" + debug(f"Fetching data from {url}") + try: + response = request.urlopen(url, timeout=5) + if response.getcode() == 200: + json_data = loads(response.read().decode('utf-8')) + with open(Plugin.coinCacheFilePath, 'w') as f: + f.write(dumps(json_data)) + else: + warning("Request failed with status code:", response.getcode()) + except Exception as e: + warning("Request failed:", str(e)) + + def run(self): + while True: + # update if older than 1h + if not Plugin.coinCacheFilePath.is_file() or (time() - Plugin.coinCacheFilePath.lstat().st_mtime) > 3600: + self._fetchCoins() + self.callback() + self._stop_event.wait(300) # Check every 5 mins, wakeup on stop event + if self._stop_event.is_set(): + return + + def stop(self): + self._stop_event.set() + + +class CoinItem(AbstractItem): + def __init__(self, + identifier: str, + name: str, + symbol: str, + rank: int, + price: float, + cap: float, + vol: float, + change24h: float): + AbstractItem.__init__(self) + self.identifier = identifier + self.name = name + self.symbol = symbol + self.rank = rank + self.price = price + self.cap = cap + self.vol = vol + self.change24h = change24h + + def id(self): + return self.identifier + + def text(self): + return f"{self.name} {self.price} {self.symbol}/$" + + def subtext(self): + return f"#{self.rank}, 24h: {self.change24h}%, Cap: {self.cap:n} $, Vol: {self.vol:n} $" + + def completion(self): + return str(self.price) + + def icon(self): + return [Plugin.iconPath] + + def actions(self): + return [ + Action("show", f"Show {self.name} on CoinGecko", + lambda id=self.identifier: openUrl(Plugin.coinsUrl + id)), + Action("url", "Copy URL to clipboard", + lambda id=self.identifier: setClipboardText(Plugin.coinsUrl + id)) + ] + + +class Plugin(IndexQueryHandler): + iconPath = str(Path(__file__).parents[0] / "coingecko.png") + coinsUrl = "https://www.coingecko.com/en/coins/" + coinCacheFilePath = Path(cacheLocation()) / md_id / "coins.json" + + def initialize(self): + self.items = [] + self.mtime = 0 + self.thread = CoinFetcherThread(self.updateIndexItems) + self.thread.start() + + def finalize(self): + self.thread.stop() + self.thread.join() + + def id(self): + return md_id + + def name(self): + return md_name + + def description(self): + return md_description + + def defaultTrigger(self): + return "cg " + + def updateIndexItems(self): + mtime = Plugin.coinCacheFilePath.lstat().st_mtime + if self.coinCacheFilePath.is_file() and mtime > self.mtime: + self.mtime = mtime + with open(self.coinCacheFilePath) as f: + self.items.clear() + for json_object in load(f): + self.items.append(CoinItem( + identifier = json_object['id'], + name = json_object['name'], + symbol = json_object['symbol'].upper(), + rank = json_object['market_cap_rank'], + price = json_object['current_price'], + cap = json_object['market_cap'], + vol = json_object['total_volume'], + change24h = json_object['price_change_percentage_24h'] + )) + + index_items = [] + for item in self.items: + index_items.append(IndexItem(item=item, string=item.name)) + index_items.append(IndexItem(item=item, string=item.symbol)) + self.setIndexItems(index_items) + + # override default trigger handling to sort by rank + def handleTriggerQuery(self, query): + qs = query.string.strip().lower() + for item in self.items: + if qs in item.name.lower() or qs in item.symbol.lower(): + query.add(item) diff --git a/coingecko/coingecko.png b/coingecko/coingecko.png new file mode 100644 index 0000000000000000000000000000000000000000..ad08ef142b2851fc18639264c11c82d8e7794dde GIT binary patch literal 21568 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 E1yzWTIsgCw literal 0 HcmV?d00001 From b0053a90bdf3be5e3576c47d0b66623889385526 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 19 Jun 2023 18:10:52 +0200 Subject: [PATCH 010/243] [jb] proper id --- jetbrains_projects/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 43ca93cc..7cf16638 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -86,7 +86,7 @@ class Plugin(TriggerQueryHandler): executables = [] def id(self): - return __name__ + return md_id def name(self): return md_name From 6a76444233548984d87efbd313b1853babe0321b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 19 Jun 2023 18:16:24 +0200 Subject: [PATCH 011/243] [wiki:1.7] Add icon. Provide fallback item. --- wikipedia/__init__.py | 30 +++++++++++++++++++++--------- wikipedia/wikipedia.png | Bin 0 -> 38692 bytes 2 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 wikipedia/wikipedia.png diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index b4a9c921..6896c01c 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -11,18 +11,18 @@ import os md_iid = '1.0' -md_version = "1.6" +md_version = "1.7" md_name = "Wikipedia" md_description = "Search Wikipedia articles." md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/wikipedia" -md_maintainers = "@manuelschneid3r" class Plugin(TriggerQueryHandler): - iconPath = ":wikipedia" + iconPath = os.path.dirname(__file__) + "/wikipedia.png" baseurl = 'https://en.wikipedia.org/w/api.php' + searchUrl = 'https://%s.wikipedia.org/wiki/Special:Search/%s' user_agent = "org.albert.wikipedia" limit = 20 @@ -38,7 +38,6 @@ def description(self): def defaultTrigger(self): return "wiki " - def initialize(self): params = { 'action': 'query', @@ -48,21 +47,21 @@ def initialize(self): 'format': 'json' } + self.local_lang_code = getdefaultlocale()[0][0:2] + get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) req = request.Request(get_url, headers={'User-Agent': self.user_agent}) try: with request.urlopen(req, timeout=5) as response: data = json.loads(response.read().decode('utf-8')) languages = [lang['code'] for lang in data['query']['languages']] - local_lang_code = getdefaultlocale()[0][0:2] - if local_lang_code in languages: - self.baseurl = self.baseurl.replace("en", local_lang_code) + if self.local_lang_code in languages: + self.baseurl = self.baseurl.replace("en", self.local_lang_code) except timeout: critical('Error getting languages - socket timed out. Defaulting to EN.') except Exception as error: critical('Error getting languages (%s). Defaulting to EN.' % error) - def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: @@ -100,10 +99,23 @@ def handleTriggerQuery(self, query): Action("open", "Open article on Wikipedia", lambda u=url: openUrl(u)), Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) ])) - + if not results: + results.append(self._createFallbackItem(stripped)) query.add(results) else: query.add(Item(id=md_id, text=md_name, subtext="Enter a query to search on Wikipedia", icon=[self.iconPath])) + + def _createFallbackItem(self, query_string): + return Item( + id=md_id, + text=md_name, + subtext="Search '%s' on Wiki" % query_string, + icon=[self.iconPath], + actions=[ + Action("wiki_search", "Search on Wikipedia", + lambda url=Plugin.searchUrl % (self.local_lang_code, query_string): openUrl(url)) + ] + ) \ No newline at end of file diff --git a/wikipedia/wikipedia.png b/wikipedia/wikipedia.png new file mode 100644 index 0000000000000000000000000000000000000000..a2a40bbd3a962e9e111267d634cb0f3bc6152811 GIT binary patch literal 38692 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 Date: Tue, 20 Jun 2023 11:24:11 +0200 Subject: [PATCH 012/243] Add stub file --- albert.pyi | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 albert.pyi diff --git a/albert.pyi b/albert.pyi new file mode 100644 index 00000000..0ef2e93a --- /dev/null +++ b/albert.pyi @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +"""Albert Python module interface v1.0""" + +from enum import Enum +from typing import Any +from typing import Callable +from typing import List +from typing import Optional +from typing import Union + +class Action: + """Action object for items.""" + def __init__(self, + id: str, + text: str, + callable: Callable): + """ + Args: + id: The identifier of the action + text: The title of the action + callable: The callable invoked on activation + """ + + +class AbstractItem: + """The abstract item base class. Serves as result item. Represents albert::Item interface class.""" + + +class Item(AbstractItem): + """Standard result item. Represents albert::StandardItem.""" + + def __init__(self, + id: str = '', + text: str = '', + subtext: str = '', + completion: Optional[str] = '', + icon: List[str] = [], + actions: List[Action] = []): + ... + + id: str + """Per extension unique identifier. Must not be empty.""" + + text: str + """The primary text of the item.""" + + subtext: str + """The secondary text of the item. This text should have informative character.""" + + completion: str + """ + The completion string of the item. This string will be used to replace the + input line when the user hits the Tab key on an item. Note that the + semantics may vary depending on the context. + """ + + icon: List[str] + """ + Icon urls used for the icon lookup. Supported url schemes: + * 'xdg:' performs freedesktop icon theme specification lookup (linux only). + * 'qfip:' uses QFileIconProvider to get the icon for the file. + * ':' is a QResource path. + * '' is interpreted as path to a local image file. + """ + + actions: List[Action] + """The actions of the item.""" + + +class Extension: + """Abstract bae class for all extensions.""" + + @abstractmethod + def id(self) -> str: + """The unique identifier of the extension.""" + + @abstractmethod + def name(self) -> str: + """The human readable name of the extension.""" + + @abstractmethod + def description(self) -> str: + """Brief description of the service provided.""" + + +class TriggerQuery: + """Represents a triggered, exclusive query execution.""" + + @property + def trigger(self) -> str: + """The trigger that has been used to start this extension.""" + + @property + def string(self) -> str: + """The actual query string (without the trigger).""" + + @property + def isValid(self) -> bool: + """This flag indicates that this the query is still valid. Cancel query processing if it is not.""" + + @overload + def add(self, item: AbstractItem): + """Add a single result item.""" + + @overload + def add(self, item: List[AbstractItem]): + """Add a list of result items.""" + + +class TriggerQueryHandler(Extension): + """Abstract class to be subclassed to implement such an extension.""" + + @abstractmethod + def synopsis(self) -> str: + """Implement to return a synopsis, displayed on empty query. Defaults to empty.""" + + @abstractmethod + def defaultTrigger(self) -> str: + """Implement to set a default trigger. Defaults to Extension::id().""" + + @abstractmethod + def allowTriggerRemap(self) -> bool: + """Implement to set trigger remapping permissions. Defaults to false.""" + + @abstractmethod + def handleTriggerQuery(self, query: TriggerQuery) -> None: + """Implement to handle the triggered query.""" + + +class GlobalQuery: + """Represents a triggered, exclusive query execution.""" + + @property + def string(self) -> str: + """The actual query string (without the trigger).""" + + @property + def isValid(self) -> bool: + """This flag indicates that this the query is still valid. Cancel query processing if it is not.""" + + +class RankItem: + """Result item with score for use in GlobalQueryHandler.""" + + def __init__(self, item: AbstractItem, score: float): + ... + + item: AbstractItem + """The result item.""" + + score: float + """The score of the item. From 0 to 1. Modulus applied""" + + +class GlobalQueryHandler(Extension): + """Abstract class to be subclassed to implement such an extension.""" + + @abstractmethod + def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: + """Implement to handle the global query.""" + + +class QueryHandler(TriggerQueryHandler, GlobalQueryHandler): + """ + Abstract convenience class to be subclassed to implement such an extension. + Combines Trigger- and GlobalQueryHandler. Implements `handleTriggerQuery` by + getting, sorting and adding the results of the handleGlobalQuery to the query. + """ + + def handleTriggerQuery(self, query: TriggerQuery) -> None: + """Calls `handleGlobalQuery` and sorts and adds the results to the query.""" + + +class IndexItem: + """Index item with index string for use in IndexQueryHandler.""" + + def __init__(self, item: AbstractItem, string: str): + ... + + item: AbstractItem + """The indexed item.""" + + string: str + """The index string used to look up this item.""" + + +class IndexQueryHandler(QueryHandler): + """ + Abstract convenience class to be subclassed to implement such an extension. + Maintains an index and does matching and scoring for you. + """ + + def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: + """Handles a global query by using the internal index.""" + + def setIndexItems(self, indexItems: List[RankItem]) -> None: + """Handles a global query by using the internal index.""" + + @abstractmethod + def updateIndexItems(self) -> None: + """Implement to populate the index. Use `setIndexItems`.""" + + +def debug(arg: Any) -> None: + """ + Log a message to stdout at the "debug" log level. Note that debug is + effectively a NOP in release builds + Args: + arg: The object to be logged. + """ + + +def info(arg: Any) -> None: + """ + Log a message to stdout at the "info" log level. + Args: + arg: The object to be logged. + """ + + +def warning(arg: Any) -> None: + """ + Log a message to stdout at the "warning" log level. + Args: + arg: The object to be logged. + """ + + +def critical(arg: Any) -> None: + """ + Log a message to stdout at the "critical" log level. + Args: + arg: The object to be logged. + """ + + +def cacheLocation() -> str: + """ + Returns: + The writable cache location of the app. + """ + + +def configLocation() -> str: + """ + Returns: + The writable config location of the app. + """ + + +def dataLocation() -> str: + """ + Returns: + The writable data location of the app. + """ + + +def setClipboardText(text: str='') -> None: + """ + Set the system clipboard text. + Args: + text: The text used to set the clipboard + """ + + +def openUrl(url:str='') -> None: + """ + Open an URL using QDesktopServices::openUrl. + Args: + url: The URL to open + """ + + +def runDetachedProcess(cmdln: List[str] = [], workdir: str = '') -> None: + """ + Run a detached process. + Args: + cmdln: The commandline to run in the terminal (argv) + workdir: The working directory used to run the terminal + """ + + +def runTerminal(script: str='', workdir: str = '', close_on_exit: bool = False) -> None: + """ + Run a script the user shell in the user specified terminal. + Args: + script: The script to be executed. + workdir: The working directory used to run the process + close_on_exit: Close the terminal on exit. Otherwise exec $SHELL. + """ + + +def sendTrayNotification(title: str='', msg: str = '', ms: int = 10000) -> None: + """ + Send a tray notification. + Args: + title: The notification title + msg: The notification body + ms: The display time (if supported by the system) + """ + From f67cf4177a31167a5eb52e460a970467a9b0ebe0 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 20 Jun 2023 12:16:48 +0200 Subject: [PATCH 013/243] Add interface info to stub file --- albert.pyi | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/albert.pyi b/albert.pyi index 0ef2e93a..8a682d6f 100644 --- a/albert.pyi +++ b/albert.pyi @@ -1,6 +1,45 @@ #!/usr/bin/env python3 -"""Albert Python module interface v1.0""" +""" +Albert Python interface specification v1.0 + +A Python plugin module is required to have the metadata described below and contain a +class named `Plugin` which will be instantiated when the plugin is loaded. + + +# Metadata + +Mandatory metadata variables: + +md_iid: str Interface version (.) +md_version: str Plugin version (.) +md_name: str Human readable name +md_description: str A brief, imperative description. (Like "Launch apps" or "Open files") + +Optional metadata variables: + +md_id Identifier overwrite. [a-zA-Z0-9_]. Defaults to module name. + Note `__name__` gets `albert.` prepended to avoid conflicts. +__doc__ The docstring of the module is used as long description/readme of the extension. +md_license: str Short form e.g. BSD-2-Clause or GPL-3.0 +md_url: str Browsable source, issues etc +md_maintainers: [str|List(str)] Active maintainer(s). Preferrably using mentionable Github usernames. +md_bin_dependencies: [str|List(str)] Required executable(s). Have to match the name of the executable in $PATH. +md_lib_dependencies: [str|List(str)] Required Python package(s). Have to match the PyPI package name. +md_credits: [str|List(str)] Third party credit(s) and license notes + + +# The Plugin class + +* The plugin class is the entry point for a plugin and instantiated on plugin initialization. +* Implement extensions by subclassing (one!) extension class provided by the built-in `albert` module. + Due to the differences in type systems multiple inheritance of extensions is not supported. + If the Plugin class inherits an extension it will be automatically registered. +* Define an "extensions() -> List[Extension]" instance function if you want to provide multiple extensions. +* Define initialize() and/or finalize() instance functions if needed. + Do not use the constructor, since PyBind11 imposes some inconvenient boilerplate on them. +""" + from enum import Enum from typing import Any From 49b2f18905c0ab9aa77d3a322ee12fb552f344fc Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 20 Jun 2023 16:18:45 +0200 Subject: [PATCH 014/243] [jb:1.4] Add idea.sh to the list of possible binaries --- jetbrains_projects/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 7cf16638..871ef63e 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -16,7 +16,7 @@ from albert import * md_iid = '1.0' -md_version = "1.3" +md_version = "1.4" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "GPL-3" @@ -130,7 +130,7 @@ def initialize(self): name="IntelliJ IDEA", icon=plugin_dir / "idea.svg", config_dir_prefix="JetBrains/IntelliJIdea", - binaries=["idea", "idea-ultimate", "idea-ce-eap", "idea-ue-eap", "intellij-idea-ce", + binaries=["idea", "idea.sh", "idea-ultimate", "idea-ce-eap", "idea-ue-eap", "intellij-idea-ce", "intellij-idea-ce-eap", "intellij-idea-ue-bundled-jre", "intellij-idea-ultimate-edition", "intellij-idea-community-edition-jre", "intellij-idea-community-edition-no-jre"]), Editor( From e0ce45ead9b3f2712ca8672ef4f9f7d7ed2e2069 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 20 Jun 2023 16:25:03 +0200 Subject: [PATCH 015/243] [wiki:1.8] Add fallback provider --- wikipedia/__init__.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index 6896c01c..58c468ce 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -11,13 +11,29 @@ import os md_iid = '1.0' -md_version = "1.7" +md_version = "1.8" md_name = "Wikipedia" md_description = "Search Wikipedia articles." md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/wikipedia" +class FallbackProvider(FallbackHandler): + + def id(self): + return f"{md_id}_fb" + + def name(self): + return md_name + + def description(self): + return md_description + + def fallbacks(self, query_string): + stripped = query_string.strip() + return [Plugin.createFallbackItem(query_string)] if stripped else [] + + class Plugin(TriggerQueryHandler): iconPath = os.path.dirname(__file__) + "/wikipedia.png" @@ -26,6 +42,9 @@ class Plugin(TriggerQueryHandler): user_agent = "org.albert.wikipedia" limit = 20 + def extensions(self): + return [self, FallbackProvider()] + def id(self): return md_id @@ -47,7 +66,7 @@ def initialize(self): 'format': 'json' } - self.local_lang_code = getdefaultlocale()[0][0:2] + Plugin.local_lang_code = getdefaultlocale()[0][0:2] get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) req = request.Request(get_url, headers={'User-Agent': self.user_agent}) @@ -100,7 +119,7 @@ def handleTriggerQuery(self, query): Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) ])) if not results: - results.append(self._createFallbackItem(stripped)) + results.append(Plugin.createFallbackItem(stripped)) query.add(results) else: query.add(Item(id=md_id, @@ -108,14 +127,15 @@ def handleTriggerQuery(self, query): subtext="Enter a query to search on Wikipedia", icon=[self.iconPath])) - def _createFallbackItem(self, query_string): + @staticmethod + def createFallbackItem(query_string): return Item( id=md_id, text=md_name, subtext="Search '%s' on Wiki" % query_string, - icon=[self.iconPath], + icon=[Plugin.iconPath], actions=[ Action("wiki_search", "Search on Wikipedia", - lambda url=Plugin.searchUrl % (self.local_lang_code, query_string): openUrl(url)) + lambda url=Plugin.searchUrl % (Plugin.local_lang_code, query_string): openUrl(url)) ] ) \ No newline at end of file From 40bcdaea0e25cc1b47ab101c212e3ded58710080 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 20 Jun 2023 16:27:05 +0200 Subject: [PATCH 016/243] [stub] Add FallbackHandler. --- albert.pyi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/albert.pyi b/albert.pyi index 8a682d6f..3253bf6f 100644 --- a/albert.pyi +++ b/albert.pyi @@ -123,6 +123,13 @@ class Extension: """Brief description of the service provided.""" +class FallbackHandler(Extension): + """Base class for a fallback providing extensions.""" + @abstractmethod + def fallbacks(self, query: str) -> List[AbstractItem]: + """Implement to handle the fallback query.""" + + class TriggerQuery: """Represents a triggered, exclusive query execution.""" From d5f2ef3b54449bc04f7ac0481d4e756365af8501 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 20 Jun 2023 16:27:20 +0200 Subject: [PATCH 017/243] [stub] Minor documentation changes --- albert.pyi | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/albert.pyi b/albert.pyi index 3253bf6f..de91781b 100644 --- a/albert.pyi +++ b/albert.pyi @@ -155,7 +155,7 @@ class TriggerQuery: class TriggerQueryHandler(Extension): - """Abstract class to be subclassed to implement such an extension.""" + """Base class for a triggered query handling extensions.""" @abstractmethod def synopsis(self) -> str: @@ -200,7 +200,7 @@ class RankItem: class GlobalQueryHandler(Extension): - """Abstract class to be subclassed to implement such an extension.""" + """Base class for a global query handling extensions.""" @abstractmethod def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: @@ -209,9 +209,8 @@ class GlobalQueryHandler(Extension): class QueryHandler(TriggerQueryHandler, GlobalQueryHandler): """ - Abstract convenience class to be subclassed to implement such an extension. - Combines Trigger- and GlobalQueryHandler. Implements `handleTriggerQuery` by - getting, sorting and adding the results of the handleGlobalQuery to the query. + Convenience base class that combines Trigger- and GlobalQueryHandler. Implements `handleTriggerQuery` + by getting, sorting and adding the results of the handleGlobalQuery to the query. """ def handleTriggerQuery(self, query: TriggerQuery) -> None: @@ -233,8 +232,7 @@ class IndexItem: class IndexQueryHandler(QueryHandler): """ - Abstract convenience class to be subclassed to implement such an extension. - Maintains an index and does matching and scoring for you. + Convenience base class that combines maintains an index and does matching and scoring for you. """ def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: From 330999a7ddcd513291b2dfee981eda9fe920450a Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 21 Jun 2023 13:07:31 +0200 Subject: [PATCH 018/243] [stub] Minor documentation changes --- albert.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/albert.pyi b/albert.pyi index de91781b..db8c7c16 100644 --- a/albert.pyi +++ b/albert.pyi @@ -196,7 +196,7 @@ class RankItem: """The result item.""" score: float - """The score of the item. From 0 to 1. Modulus applied""" + """The score of the item (0,1]. No checks applied for performance.""" class GlobalQueryHandler(Extension): From 574a9be00c63488949e6cf3298ff6cf0bfa2c4b4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 22 Jun 2023 12:00:24 +0200 Subject: [PATCH 019/243] [stub] iid:1.0 Add extension.cache-, config- and dataLocation --- albert.pyi | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/albert.pyi b/albert.pyi index db8c7c16..4ebe4e27 100644 --- a/albert.pyi +++ b/albert.pyi @@ -122,6 +122,33 @@ class Extension: def description(self) -> str: """Brief description of the service provided.""" + def cacheDir(self) -> str: + """ + The recommended cache location for this extension. + Creates the directory if necessary. + Since iid v1.0. + Returns: + The writable cache location of the extension. + """ + + def configDir(self) -> str: + """ + The recommended config location for this extension. + Creates the directory if necessary. + Since iid v1.0. + Returns: + The writable config location of the extension. + """ + + def dataDir(self) -> str: + """ + The recommended data location for this extension. + Creates the directory if necessary. + Since iid v1.0. + Returns: + The writable data location of the extension. + """ + class FallbackHandler(Extension): """Base class for a fallback providing extensions.""" @@ -281,22 +308,31 @@ def critical(arg: Any) -> None: def cacheLocation() -> str: """ + Deprecated: Use Extension.cacheLocation instead + + Note that this is the _app_ cache location. Returns: - The writable cache location of the app. + The writable app cache location. """ def configLocation() -> str: """ + Deprecated: Use Extension.configLocation instead + + Note that this is the _app_ config location. Returns: - The writable config location of the app. + The writable app config location. """ def dataLocation() -> str: """ + Deprecated: Use Extension.dataLocation instead + + Note that this is the _app_ data location. Returns: - The writable data location of the app. + The writable app data location. """ From 6ed1f9caf659237c10b37b7cd575df8b0074a7bb Mon Sep 17 00:00:00 2001 From: Asger Hautop Drewsen Date: Thu, 22 Jun 2023 20:47:31 +0200 Subject: [PATCH 020/243] [mathematica] iid:1.0 port --- .archive/mathematica_eval/__init__.py | 42 --------------- mathematica_eval/__init__.py | 77 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 42 deletions(-) delete mode 100644 .archive/mathematica_eval/__init__.py create mode 100644 mathematica_eval/__init__.py diff --git a/.archive/mathematica_eval/__init__.py b/.archive/mathematica_eval/__init__.py deleted file mode 100644 index 6941121e..00000000 --- a/.archive/mathematica_eval/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Evaluate Mathematica expressions. - -Synopsis: [expr]""" - -# Copyright (c) 2022 Manuel Schneider - -import subprocess -from tempfile import NamedTemporaryFile - -from albert import ClipAction, Item, iconLookup - -__title__ = 'Mathematica eval' -__version__ = '0.4.0' -__triggers__ = 'mma ' -__authors__ = 'Asger Hautop Drewsen' -__exec_deps__ = ['wolframscript'] - -ICON_PATH = iconLookup('wolfram-mathematica') - -def handleQuery(query): - if not query.isTriggered: - return - - item = Item(icon=ICON_PATH) - stripped = query.string.strip() - - if stripped: - with NamedTemporaryFile() as f: - f.write(bytes(stripped, 'utf-8')) - f.flush() - output = subprocess.check_output(['wolframscript', '-print', '-f', f.name]) - result = str(output.strip(), 'utf-8') - item.text = result - item.subtext = 'Result' - item.addAction(ClipAction('Copy result to clipboard', result)) - else: - item.text = '' - item.subtext = 'Type a Mathematica expression' - - return item diff --git a/mathematica_eval/__init__.py b/mathematica_eval/__init__.py new file mode 100644 index 00000000..e988fc67 --- /dev/null +++ b/mathematica_eval/__init__.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import subprocess +from tempfile import NamedTemporaryFile +from threading import Lock + +from albert import (Action, Item, TriggerQuery, TriggerQueryHandler, + setClipboardText) + +md_iid = "1.0" +md_version = "1.0" +md_name = "Mathematica Eval" +md_description = "Evaluate Mathemtica code" +md_license = "GPL-3.0" +md_url = "https://github.com/albertlauncher/python/tree/master/mathematica_eval" +md_maintainers = "@tyilo" +md_bin_dependencies = ["wolframscript"] + + +class Plugin(TriggerQueryHandler): + def id(self) -> str: + return md_id + + def name(self) -> str: + return md_name + + def description(self) -> str: + return md_description + + def defaultTrigger(self) -> str: + return "mma " + + def synopsis(self) -> str: + return "" + + def handleTriggerQuery(self, query: TriggerQuery) -> None: + stripped = query.string.strip() + if not stripped: + return + + with NamedTemporaryFile("w") as f: + f.write(stripped) + f.flush() + process = subprocess.Popen( + ["wolframscript", "-print", "-f", f.name], + encoding="utf-8", + stdout=subprocess.PIPE, + ) + + while True: + if not query.isValid: + process.kill() + return + + try: + output, _ = process.communicate(timeout=0.1) + break + except subprocess.TimeoutExpired: + pass + + result_str = output.strip() + + query.add( + Item( + id=md_id, + text=result_str, + completion=query.trigger + result_str, + icon=["xdg:wolfram-mathematica"], + actions=[ + Action( + "copy", + "Copy result to clipboard", + lambda r=result_str: setClipboardText(r), + ), + ], + ) + ) From 1529fba03bb47cf83f29a3d0dcc025c1e355f296 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 22 Jun 2023 20:56:24 +0200 Subject: [PATCH 021/243] [stub] iid:1.0 Drop deprecated cache/config/dataLocation --- albert.pyi | 38 ++++---------------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/albert.pyi b/albert.pyi index 4ebe4e27..91f368e9 100644 --- a/albert.pyi +++ b/albert.pyi @@ -122,7 +122,7 @@ class Extension: def description(self) -> str: """Brief description of the service provided.""" - def cacheDir(self) -> str: + def cacheLocation(self) -> str: """ The recommended cache location for this extension. Creates the directory if necessary. @@ -131,7 +131,7 @@ class Extension: The writable cache location of the extension. """ - def configDir(self) -> str: + def configLocation(self) -> str: """ The recommended config location for this extension. Creates the directory if necessary. @@ -140,7 +140,7 @@ class Extension: The writable config location of the extension. """ - def dataDir(self) -> str: + def dataLocation(self) -> str: """ The recommended data location for this extension. Creates the directory if necessary. @@ -306,36 +306,6 @@ def critical(arg: Any) -> None: """ -def cacheLocation() -> str: - """ - Deprecated: Use Extension.cacheLocation instead - - Note that this is the _app_ cache location. - Returns: - The writable app cache location. - """ - - -def configLocation() -> str: - """ - Deprecated: Use Extension.configLocation instead - - Note that this is the _app_ config location. - Returns: - The writable app config location. - """ - - -def dataLocation() -> str: - """ - Deprecated: Use Extension.dataLocation instead - - Note that this is the _app_ data location. - Returns: - The writable app data location. - """ - - def setClipboardText(text: str='') -> None: """ Set the system clipboard text. @@ -363,7 +333,7 @@ def runDetachedProcess(cmdln: List[str] = [], workdir: str = '') -> None: def runTerminal(script: str='', workdir: str = '', close_on_exit: bool = False) -> None: """ - Run a script the user shell in the user specified terminal. + Run a script in the users shell and terminal. Args: script: The script to be executed. workdir: The working directory used to run the process From 6fe50e9d7b63f04328ab3800f7c21b3eaa9140bc Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 22 Jun 2023 20:56:53 +0200 Subject: [PATCH 022/243] [coingecko] Adjust to latest changes --- coingecko/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 2d0ff769..61b0019b 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -18,10 +18,11 @@ class CoinFetcherThread(Thread): - def __init__(self, callback): + def __init__(self, callback, path: Path): super().__init__() self._stop_event = Event() self.callback = callback + self.path = path def _fetchCoins(self): url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250" @@ -30,17 +31,17 @@ def _fetchCoins(self): response = request.urlopen(url, timeout=5) if response.getcode() == 200: json_data = loads(response.read().decode('utf-8')) - with open(Plugin.coinCacheFilePath, 'w') as f: + with open(self.path, 'w') as f: f.write(dumps(json_data)) else: - warning("Request failed with status code:", response.getcode()) + warning(f"Request failed with status code: {response.getcode()}") except Exception as e: - warning("Request failed:", str(e)) + warning(f"Request failed: {str(e)}") def run(self): while True: # update if older than 1h - if not Plugin.coinCacheFilePath.is_file() or (time() - Plugin.coinCacheFilePath.lstat().st_mtime) > 3600: + if not self.path.is_file() or (time() - self.path.lstat().st_mtime) > 3600: self._fetchCoins() self.callback() self._stop_event.wait(300) # Check every 5 mins, wakeup on stop event @@ -98,12 +99,12 @@ def actions(self): class Plugin(IndexQueryHandler): iconPath = str(Path(__file__).parents[0] / "coingecko.png") coinsUrl = "https://www.coingecko.com/en/coins/" - coinCacheFilePath = Path(cacheLocation()) / md_id / "coins.json" def initialize(self): self.items = [] self.mtime = 0 - self.thread = CoinFetcherThread(self.updateIndexItems) + self.coinCacheFilePath = Path(self.cacheLocation()) / "coins.json" + self.thread = CoinFetcherThread(self.updateIndexItems, self.coinCacheFilePath) self.thread.start() def finalize(self): @@ -123,7 +124,7 @@ def defaultTrigger(self): return "cg " def updateIndexItems(self): - mtime = Plugin.coinCacheFilePath.lstat().st_mtime + mtime = self.coinCacheFilePath.lstat().st_mtime if self.coinCacheFilePath.is_file() and mtime > self.mtime: self.mtime = mtime with open(self.coinCacheFilePath) as f: From 98320216746c23cd7e3aa1e7c0018e62c33cb667 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 22 Jun 2023 21:19:44 +0200 Subject: [PATCH 023/243] [emoji:1.3] Adjust to cache/config/data changes --- emoji/__init__.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 6e79c0c9..2467b19e 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -1,9 +1,5 @@ # -*- coding: utf-8 -*- -"""Find emojis by name. - -Synopsis: """ - import json import re import subprocess @@ -13,7 +9,7 @@ from albert import * md_iid = '1.0' -md_version = "1.2" +md_version = "1.3" md_name = "Emoji Picker" md_description = "Find emojis by name" md_license = "GPL-3.0" @@ -25,14 +21,6 @@ EXTENSION_DIR = Path(__file__).parent ALIASES_PATH = EXTENSION_DIR / "aliases.json" EMOJI_PATH = EXTENSION_DIR / "emoji-test.txt" -ICON_DIR = Path(cacheLocation()) / "emojis" - - -ICON_DIR.mkdir(exist_ok=True, parents=True) - - -def icon_path(emoji): - return ICON_DIR / f"{emoji}.png" def convert_to_png(emoji, output_path): @@ -49,10 +37,10 @@ def convert_to_png(emoji, output_path): ) -def schedule_create_missing_icons(emojis): +def schedule_create_missing_icons(emojis, icon_dir_path: str): executor = ThreadPoolExecutor() for emoji in emojis: - path = icon_path(emoji["emoji"]) + path = icon_dir_path / f"{emoji['emoji']}.png" if not path.exists(): executor.submit(convert_to_png, emoji["emoji"], path) @@ -76,6 +64,8 @@ def synopsis(self): return "" def initialize(self): + self.icon_dir_path = Path(cacheLocation()) + line_re = re.compile( r""" ^ @@ -112,7 +102,7 @@ def initialize(self): e["search_tokens"] = search_tokens self.emojis.append(e) - self.icon_executor = schedule_create_missing_icons(self.emojis) + self.icon_executor = schedule_create_missing_icons(self.emojis, self.icon_dir_path) def finalize(self): self.icon_executor.shutdown(wait=True, cancel_futures=True) @@ -136,7 +126,7 @@ def handleTriggerQuery(self, query): id=f"emoji_{emoji['emoji']}", text=f"{emoji['emoji']} {emoji['name']}", subtext=emoji["modifiers"] or "", - icon=[str(icon_path(emoji["emoji"]))], + icon=[str(self.icon_dir_path / f"{emoji['emoji']}.png")], actions=[ Action( "copy", From 1d3a1869b25a2ac147c52c779186856355794c45 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 22 Jun 2023 21:22:01 +0200 Subject: [PATCH 024/243] [stub] Fix abstract methods --- albert.pyi | 3 --- 1 file changed, 3 deletions(-) diff --git a/albert.pyi b/albert.pyi index 91f368e9..f3ede40b 100644 --- a/albert.pyi +++ b/albert.pyi @@ -184,15 +184,12 @@ class TriggerQuery: class TriggerQueryHandler(Extension): """Base class for a triggered query handling extensions.""" - @abstractmethod def synopsis(self) -> str: """Implement to return a synopsis, displayed on empty query. Defaults to empty.""" - @abstractmethod def defaultTrigger(self) -> str: """Implement to set a default trigger. Defaults to Extension::id().""" - @abstractmethod def allowTriggerRemap(self) -> bool: """Implement to set trigger remapping permissions. Defaults to false.""" From ab402b25cb191d6ec93a348884e6249f13291a29 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 23 Jun 2023 16:11:23 +0200 Subject: [PATCH 025/243] [stub] Abstract item documentation --- albert.pyi | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/albert.pyi b/albert.pyi index f3ede40b..12845d24 100644 --- a/albert.pyi +++ b/albert.pyi @@ -65,9 +65,50 @@ class Action: class AbstractItem: """The abstract item base class. Serves as result item. Represents albert::Item interface class.""" + @property + @abstractmethod + def id(self) -> str: + """Per extension unique identifier. Must not be empty.""" + + @property + @abstractmethod + def text(self) -> str: + """The primary text of the item.""" + + @property + @abstractmethod + def subtext(self) -> str: + """The secondary text of the item. This text should have informative character.""" + + @property + @abstractmethod + def icon(self) -> List[str]: + """ + Icon urls used for the icon lookup. Supported url schemes: + * 'xdg:' performs freedesktop icon theme specification lookup (linux only). + * 'qfip:' uses QFileIconProvider to get the icon for the file. + * ':' is a QResource path. + * '' is interpreted as path to a local image file. + """ + + @property + def completion(self) -> str: + """ + The completion string of the item. This string will be used to replace the + input line when the user hits the Tab key on an item. Note that the + semantics may vary depending on the context. Default empty. + """ + + @property + def actions(self) -> List[Action]: + """The actions of the item. Default empty.""" class Item(AbstractItem): - """Standard result item. Represents albert::StandardItem.""" + """ + Standard result item. + Represents albert::StandardItem. + See AbstractItem for more information + """ def __init__(self, id: str = '', @@ -108,7 +149,7 @@ class Item(AbstractItem): class Extension: - """Abstract bae class for all extensions.""" + """Abstract base class for all extensions.""" @abstractmethod def id(self) -> str: From 9daf769316bb768970c50e035efc2aa9b2f9db60 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 23 Jun 2023 23:04:38 +0200 Subject: [PATCH 026/243] [jb] Fix md_url --- jetbrains_projects/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 871ef63e..10bb9aa4 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -20,7 +20,7 @@ md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "GPL-3" -md_url = "https://github.com/tomsquest/albert-jetbrains-projects-plugin" +md_url = "https://github.com/albertlauncher/python/" md_maintainers = ["@mqus", "@tomsquest"] From 2f6a6d8a8a6397d1acfbd07e6fed595d309f9f16 Mon Sep 17 00:00:00 2001 From: Naman Sood Date: Tue, 27 Jun 2023 06:09:45 -0400 Subject: [PATCH 027/243] [emoji] Fix #179. Call cacheLocation as method of self. --- emoji/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 2467b19e..52f7fb5e 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -64,7 +64,7 @@ def synopsis(self): return "" def initialize(self): - self.icon_dir_path = Path(cacheLocation()) + self.icon_dir_path = Path(self.cacheLocation()) line_re = re.compile( r""" From d7fc7e3a9c4f78bb5d179919cb3bb80578499517 Mon Sep 17 00:00:00 2001 From: Oskar Haarklou Veileborg Date: Tue, 27 Jun 2023 12:11:47 +0200 Subject: [PATCH 028/243] [emoji] Proper type hinting --- emoji/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 52f7fb5e..12943ff4 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -37,7 +37,7 @@ def convert_to_png(emoji, output_path): ) -def schedule_create_missing_icons(emojis, icon_dir_path: str): +def schedule_create_missing_icons(emojis, icon_dir_path: Path): executor = ThreadPoolExecutor() for emoji in emojis: path = icon_dir_path / f"{emoji['emoji']}.png" From 538ec37b326e6c93c825065a3deb6281f80c1793 Mon Sep 17 00:00:00 2001 From: Oskar Haarklou Veileborg Date: Tue, 27 Jun 2023 12:12:22 +0200 Subject: [PATCH 029/243] [tex_to_unicode] Fix crash due to wrong type annotation --- tex_to_unicode/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tex_to_unicode/__init__.py b/tex_to_unicode/__init__.py index 4fbdffed..6393ed43 100644 --- a/tex_to_unicode/__init__.py +++ b/tex_to_unicode/__init__.py @@ -57,7 +57,7 @@ def _create_item(self, text: str, subtext: str, can_copy: bool) -> Item: actions=actions, ) - def handleTriggerQuery(self, query: Query) -> None: + def handleTriggerQuery(self, query: TriggerQuery) -> None: stripped = query.string.strip() if not stripped: From 0f024aacbe2c95e1a413852a0409bd8de0b0c53a Mon Sep 17 00:00:00 2001 From: Oskar Haarklou Veileborg Date: Tue, 27 Jun 2023 12:13:00 +0200 Subject: [PATCH 030/243] [stub] Add missing imports to stub file --- albert.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/albert.pyi b/albert.pyi index 12845d24..3b9e4f99 100644 --- a/albert.pyi +++ b/albert.pyi @@ -41,12 +41,14 @@ md_credits: [str|List(str)] Third party credit(s) and license notes """ +from abc import abstractmethod from enum import Enum from typing import Any from typing import Callable from typing import List from typing import Optional from typing import Union +from typing import overload class Action: """Action object for items.""" From 67690b762b3e7ea6ad5f0022bd17ca48e2978ce5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:39:03 +0200 Subject: [PATCH 031/243] Interface v2.0 --- albert.pyi | 419 ++++++++++++++++++++++++----------------------------- 1 file changed, 191 insertions(+), 228 deletions(-) diff --git a/albert.pyi b/albert.pyi index 3b9e4f99..f9b824bf 100644 --- a/albert.pyi +++ b/albert.pyi @@ -1,47 +1,46 @@ -#!/usr/bin/env python3 - """ -Albert Python interface specification v1.0 -A Python plugin module is required to have the metadata described below and contain a -class named `Plugin` which will be instantiated when the plugin is loaded. +# Albert Python interface v2.0 + + +The Python interface is a subset of the internal C++ interface exposed to Python with some minor adjustments. A Python +plugin is required to contain the mandatory metadata and a plugin class, both described below. To get started read the +top level classes and function names in this file. Most of them are self explanatory. In case of questions see the C++ +documentation at https://albertlauncher.github.io/reference/namespacealbert.html -# Metadata +## Mandatory metadata variables -Mandatory metadata variables: +md_iid: str | Interface version (.) +md_version: str | Plugin version (.) +md_name: str | Human readable name +md_description: str | A brief, imperative description. (Like "Launch apps" or "Open files") -md_iid: str Interface version (.) -md_version: str Plugin version (.) -md_name: str Human readable name -md_description: str A brief, imperative description. (Like "Launch apps" or "Open files") -Optional metadata variables: +## Optional metadata variables: -md_id Identifier overwrite. [a-zA-Z0-9_]. Defaults to module name. - Note `__name__` gets `albert.` prepended to avoid conflicts. -__doc__ The docstring of the module is used as long description/readme of the extension. -md_license: str Short form e.g. BSD-2-Clause or GPL-3.0 -md_url: str Browsable source, issues etc -md_maintainers: [str|List(str)] Active maintainer(s). Preferrably using mentionable Github usernames. -md_bin_dependencies: [str|List(str)] Required executable(s). Have to match the name of the executable in $PATH. -md_lib_dependencies: [str|List(str)] Required Python package(s). Have to match the PyPI package name. -md_credits: [str|List(str)] Third party credit(s) and license notes +md_id | Identifier overwrite. [a-zA-Z0-9_]. Defaults to module name. +__doc__ | The docstring of the module is used as long description/readme of the extension. +md_license: str | Short form e.g. BSD-2-Clause or GPL-3.0 +md_url: str | Browsable source, issues etc +md_maintainers: [str|List(str)] | Active maintainer(s). Preferrably using mentionable Github usernames. +md_bin_dependencies: [str|List(str)] | Required executable(s). Have to match the name of the executable in $PATH. +md_lib_dependencies: [str|List(str)] | Required Python package(s). Have to match the PyPI package name. +md_credits: [str|List(str)] | Third party credit(s) and license notes -# The Plugin class +## The Plugin class + +The plugin class is the entry point for a Python plugin. It is instantiated on plugin initialization and has to subclass +PluginInstance. Implement extension(s) by subclassing _one_ extension class (TriggerQueryHandler etc…) provided by the +built-in `albert` module and pass the list of your extensions to the PluginInstance init function. Due to the +differences in type systems multiple inheritance of extensions is not supported. (Python does not support virtual +inheritance, which is used in the C++ space to inherit from 'Extension'). For more details see -* The plugin class is the entry point for a plugin and instantiated on plugin initialization. -* Implement extensions by subclassing (one!) extension class provided by the built-in `albert` module. - Due to the differences in type systems multiple inheritance of extensions is not supported. - If the Plugin class inherits an extension it will be automatically registered. -* Define an "extensions() -> List[Extension]" instance function if you want to provide multiple extensions. -* Define initialize() and/or finalize() instance functions if needed. - Do not use the constructor, since PyBind11 imposes some inconvenient boilerplate on them. """ -from abc import abstractmethod +from abc import abstractmethod, ABC from enum import Enum from typing import Any from typing import Callable @@ -50,303 +49,267 @@ from typing import Optional from typing import Union from typing import overload + +class PluginInstance(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_plugin_instance.html""" + + def __init__(self, extensions: List[Extension] = []): + ... + + @property + def id(self) -> str: + ... + + @property + def name(self) -> str: + ... + + @property + def description(self) -> str: + ... + + @property + def cacheLocation(self) -> pathlib.Path: + ... + + @property + def configLocation(self) -> pathlib.Path: + ... + + @property + def dataLocation(self) -> pathlib.Path: + ... + + @property + def extensions(self) -> List[Extension]: + ... + + def initialize(self): + ... + + def finalize(self): + ... + + class Action: - """Action object for items.""" + """https://albertlauncher.github.io/reference/classalbert_1_1_action.html""" + def __init__(self, id: str, text: str, callable: Callable): - """ - Args: - id: The identifier of the action - text: The title of the action - callable: The callable invoked on activation - """ + ... -class AbstractItem: - """The abstract item base class. Serves as result item. Represents albert::Item interface class.""" +class Item(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_item.html""" - @property @abstractmethod def id(self) -> str: - """Per extension unique identifier. Must not be empty.""" + ... - @property @abstractmethod def text(self) -> str: - """The primary text of the item.""" + ... - @property @abstractmethod def subtext(self) -> str: - """The secondary text of the item. This text should have informative character.""" + ... - @property @abstractmethod - def icon(self) -> List[str]: - """ - Icon urls used for the icon lookup. Supported url schemes: - * 'xdg:' performs freedesktop icon theme specification lookup (linux only). - * 'qfip:' uses QFileIconProvider to get the icon for the file. - * ':' is a QResource path. - * '' is interpreted as path to a local image file. - """ + def inputActionText(self) -> str: + ... - @property - def completion(self) -> str: - """ - The completion string of the item. This string will be used to replace the - input line when the user hits the Tab key on an item. Note that the - semantics may vary depending on the context. Default empty. - """ + @abstractmethod + def iconUrls(self) -> List[str]: + """See https://albertlauncher.github.io/reference/classalbert_1_1_icon_provider.html""" - @property + @abstractmethod def actions(self) -> List[Action]: - """The actions of the item. Default empty.""" + ... -class Item(AbstractItem): - """ - Standard result item. - Represents albert::StandardItem. - See AbstractItem for more information - """ + +class StandardItem(Item): + """https://albertlauncher.github.io/reference/structalbert_1_1_standard_item.html""" def __init__(self, id: str = '', text: str = '', subtext: str = '', - completion: Optional[str] = '', - icon: List[str] = [], - actions: List[Action] = []): + iconUrls: List[str] = [], + actions: List[Action] = [], + inputActionText: Optional[str] = ''): ... id: str - """Per extension unique identifier. Must not be empty.""" - text: str - """The primary text of the item.""" - subtext: str - """The secondary text of the item. This text should have informative character.""" - - completion: str - """ - The completion string of the item. This string will be used to replace the - input line when the user hits the Tab key on an item. Note that the - semantics may vary depending on the context. - """ - - icon: List[str] - """ - Icon urls used for the icon lookup. Supported url schemes: - * 'xdg:' performs freedesktop icon theme specification lookup (linux only). - * 'qfip:' uses QFileIconProvider to get the icon for the file. - * ':' is a QResource path. - * '' is interpreted as path to a local image file. - """ - + iconUrls: List[str] actions: List[Action] - """The actions of the item.""" + inputActionText: str -class Extension: - """Abstract base class for all extensions.""" +class Extension(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_extension.html""" - @abstractmethod + @property def id(self) -> str: - """The unique identifier of the extension.""" + ... - @abstractmethod + @property def name(self) -> str: - """The human readable name of the extension.""" + ... - @abstractmethod + @property def description(self) -> str: - """Brief description of the service provided.""" - - def cacheLocation(self) -> str: - """ - The recommended cache location for this extension. - Creates the directory if necessary. - Since iid v1.0. - Returns: - The writable cache location of the extension. - """ - - def configLocation(self) -> str: - """ - The recommended config location for this extension. - Creates the directory if necessary. - Since iid v1.0. - Returns: - The writable config location of the extension. - """ - - def dataLocation(self) -> str: - """ - The recommended data location for this extension. - Creates the directory if necessary. - Since iid v1.0. - Returns: - The writable data location of the extension. - """ - - -class FallbackHandler(Extension): - """Base class for a fallback providing extensions.""" + ... + + +class FallbackHandler(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_fallback_handler.html""" + @abstractmethod - def fallbacks(self, query: str) -> List[AbstractItem]: - """Implement to handle the fallback query.""" + def fallbacks(self, query: str ) ->List[Item]: + ... -class TriggerQuery: - """Represents a triggered, exclusive query execution.""" +class TriggerQuery(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_trigger_query_handler_1_1_trigger_query.html""" @property def trigger(self) -> str: - """The trigger that has been used to start this extension.""" + ... @property def string(self) -> str: - """The actual query string (without the trigger).""" + ... @property def isValid(self) -> bool: - """This flag indicates that this the query is still valid. Cancel query processing if it is not.""" + ... @overload - def add(self, item: AbstractItem): - """Add a single result item.""" + def add(self, item: Item): + ... @overload - def add(self, item: List[AbstractItem]): - """Add a list of result items.""" + def add(self, item: List[Item]): + ... class TriggerQueryHandler(Extension): - """Base class for a triggered query handling extensions.""" + """https://albertlauncher.github.io/reference/classalbert_1_1_trigger_query_handler.html""" + def __init__(self, + id: str, + name: str, + description: str, + synopsis: str = '', + defaultTrigger: str = f'{id} ', + allowTriggerRemap: str = true, + supportsFuzzyMatching: bool = False): + ... + + @property def synopsis(self) -> str: - """Implement to return a synopsis, displayed on empty query. Defaults to empty.""" + ... + + @property + def trigger(self) -> str: + ... + @property def defaultTrigger(self) -> str: - """Implement to set a default trigger. Defaults to Extension::id().""" + ... + @property def allowTriggerRemap(self) -> bool: - """Implement to set trigger remapping permissions. Defaults to false.""" - - @abstractmethod - def handleTriggerQuery(self, query: TriggerQuery) -> None: - """Implement to handle the triggered query.""" - - -class GlobalQuery: - """Represents a triggered, exclusive query execution.""" + ... @property - def string(self) -> str: - """The actual query string (without the trigger).""" + def supportsFuzzyMatching(self) -> bool: + ... @property - def isValid(self) -> bool: - """This flag indicates that this the query is still valid. Cancel query processing if it is not.""" + def fuzzyMatching(self) -> bool: + ... + + @fuzzyMatching.setter + def setFuzzyMatching(self, enabled: bool): + ... + + @abstractmethod + def handleTriggerQuery(self, query: TriggerQuery): + ... class RankItem: - """Result item with score for use in GlobalQueryHandler.""" + """https://albertlauncher.github.io/reference/classalbert_1_1_rank_item.html""" - def __init__(self, item: AbstractItem, score: float): + def __init__(self, item: Item, score: float): ... - item: AbstractItem - """The result item.""" - + item: Item score: float - """The score of the item (0,1]. No checks applied for performance.""" -class GlobalQueryHandler(Extension): - """Base class for a global query handling extensions.""" +class GlobalQuery(ABC): + """https://albertlauncher.github.io/reference/classalbert_1_1_global_query_handler_1_1_global_query.html""" + + @property + def string(self) -> str: + ... + + @property + def isValid(self) -> bool: + ... + + +class GlobalQueryHandler(TriggerQueryHandler): + """https://albertlauncher.github.io/reference/classalbert_1_1_global_query_handler.html""" @abstractmethod def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: - """Implement to handle the global query.""" - + ... -class QueryHandler(TriggerQueryHandler, GlobalQueryHandler): - """ - Convenience base class that combines Trigger- and GlobalQueryHandler. Implements `handleTriggerQuery` - by getting, sorting and adding the results of the handleGlobalQuery to the query. - """ + def applyUsageScore(self, rank_items: List[RankItem]): + ... - def handleTriggerQuery(self, query: TriggerQuery) -> None: - """Calls `handleGlobalQuery` and sorts and adds the results to the query.""" + def handleTriggerQuery(self, query: TriggerQuery): + ... class IndexItem: - """Index item with index string for use in IndexQueryHandler.""" + """https://albertlauncher.github.io/reference/classalbert_1_1_index_item.html""" def __init__(self, item: AbstractItem, string: str): ... item: AbstractItem - """The indexed item.""" - string: str - """The index string used to look up this item.""" -class IndexQueryHandler(QueryHandler): - """ - Convenience base class that combines maintains an index and does matching and scoring for you. - """ - - def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: - """Handles a global query by using the internal index.""" - - def setIndexItems(self, indexItems: List[RankItem]) -> None: - """Handles a global query by using the internal index.""" +class IndexQueryHandler(GlobalQueryHandler): + """https://albertlauncher.github.io/reference/classalbert_1_1_index_query_handler.html""" @abstractmethod - def updateIndexItems(self) -> None: - """Implement to populate the index. Use `setIndexItems`.""" - - -def debug(arg: Any) -> None: - """ - Log a message to stdout at the "debug" log level. Note that debug is - effectively a NOP in release builds - Args: - arg: The object to be logged. - """ - - -def info(arg: Any) -> None: - """ - Log a message to stdout at the "info" log level. - Args: - arg: The object to be logged. - """ + def updateIndexItems(self): + ... + def setIndexItems(self, indexItems: List[RankItem]): + ... -def warning(arg: Any) -> None: - """ - Log a message to stdout at the "warning" log level. - Args: - arg: The object to be logged. - """ + def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: + ... -def critical(arg: Any) -> None: - """ - Log a message to stdout at the "critical" log level. - Args: - arg: The object to be logged. - """ +def debug(arg: Any):... +def info(arg: Any):... +def warning(arg: Any):... +def critical(arg: Any):... -def setClipboardText(text: str='') -> None: +def setClipboardText(text: str=''): """ Set the system clipboard text. Args: @@ -354,7 +317,7 @@ def setClipboardText(text: str='') -> None: """ -def openUrl(url:str='') -> None: +def openUrl(url: str = ''): """ Open an URL using QDesktopServices::openUrl. Args: @@ -362,7 +325,7 @@ def openUrl(url:str='') -> None: """ -def runDetachedProcess(cmdln: List[str] = [], workdir: str = '') -> None: +def runDetachedProcess(cmdln: List[str] = [], workdir: str = ''): """ Run a detached process. Args: @@ -371,7 +334,7 @@ def runDetachedProcess(cmdln: List[str] = [], workdir: str = '') -> None: """ -def runTerminal(script: str='', workdir: str = '', close_on_exit: bool = False) -> None: +def runTerminal(script: str = '', workdir: str = '', close_on_exit: bool = False): """ Run a script in the users shell and terminal. Args: @@ -381,7 +344,7 @@ def runTerminal(script: str='', workdir: str = '', close_on_exit: bool = False) """ -def sendTrayNotification(title: str='', msg: str = '', ms: int = 10000) -> None: +def sendTrayNotification(title: str = '', msg: str = '', ms: int = 10000): """ Send a tray notification. Args: From 2a1dbc4889051decc48bcce074962258ac1c373a Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:32:30 +0200 Subject: [PATCH 032/243] [wiki] Interface v2.0 --- wikipedia/__init__.py | 84 ++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index 58c468ce..b44aac5c 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -8,56 +8,46 @@ from time import sleep from urllib import request, parse import json -import os +from pathlib import Path -md_iid = '1.0' -md_version = "1.8" +md_iid = '2.0' +md_version = "1.9" md_name = "Wikipedia" -md_description = "Search Wikipedia articles." +md_description = "Search Wikipedia articles" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/wikipedia" -class FallbackProvider(FallbackHandler): - - def id(self): - return f"{md_id}_fb" - - def name(self): - return md_name - - def description(self): - return md_description +class WikiFallbackHandler(FallbackHandler): + def __init__(self): + FallbackHandler.__init__(self, + id=f"{md_id}_fb", + name=f"{md_name} fallback", + description="Wikipedia fallback search") def fallbacks(self, query_string): stripped = query_string.strip() return [Plugin.createFallbackItem(query_string)] if stripped else [] -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): - iconPath = os.path.dirname(__file__) + "/wikipedia.png" baseurl = 'https://en.wikipedia.org/w/api.php' searchUrl = 'https://%s.wikipedia.org/wiki/Special:Search/%s' user_agent = "org.albert.wikipedia" limit = 20 + iconUrls = [f"file:{Path(__file__).parent}/wikipedia.png"] - def extensions(self): - return [self, FallbackProvider()] - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='wiki ') + self.wiki_fb = WikiFallbackHandler() + PluginInstance.__init__(self, extensions=[self, self.wiki_fb]) - def defaultTrigger(self): - return "wiki " - def initialize(self): params = { 'action': 'query', 'meta': 'siteinfo', @@ -110,32 +100,36 @@ def handleTriggerQuery(self, query): summary = data[2][i] url = data[3][i] - results.append(Item(id=md_id, - text=title, - subtext=summary if summary else url, - icon=[self.iconPath], - actions=[ - Action("open", "Open article on Wikipedia", lambda u=url: openUrl(u)), - Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) - ])) + results.append(StandardItem(id=md_id, + text=title, + subtext=summary if summary else url, + iconUrls=self.iconUrls, + actions=[ + Action("open", "Open article on Wikipedia", lambda u=url: openUrl(u)), + Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) + ])) if not results: results.append(Plugin.createFallbackItem(stripped)) query.add(results) else: - query.add(Item(id=md_id, - text=md_name, - subtext="Enter a query to search on Wikipedia", - icon=[self.iconPath])) + query.add( + StandardItem( + id=md_id, + text=md_name, + subtext="Enter a query to search on Wikipedia", + iconUrls=self.iconUrls + ) + ) @staticmethod def createFallbackItem(query_string): - return Item( + return StandardItem( id=md_id, text=md_name, subtext="Search '%s' on Wiki" % query_string, - icon=[Plugin.iconPath], + iconUrls=Plugin.iconUrls, actions=[ Action("wiki_search", "Search on Wikipedia", lambda url=Plugin.searchUrl % (Plugin.local_lang_code, query_string): openUrl(url)) ] - ) \ No newline at end of file + ) From 58c471366637dc0df6e82807dd35c6a81f5451f4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:32:54 +0200 Subject: [PATCH 033/243] [zeal] Interface v2.0 --- zeal/__init__.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/zeal/__init__.py b/zeal/__init__.py index 5677ecba..843eee45 100644 --- a/zeal/__init__.py +++ b/zeal/__init__.py @@ -1,40 +1,33 @@ """Search in Zeal offline docs.""" -from subprocess import run from albert import * -md_iid = '1.0' -md_version = '1.1' +md_iid = '2.0' +md_version = '1.2' md_name = 'Zeal' md_description = 'Search in Zeal docs' md_url = 'https://github.com/albertlauncher/python/zeal' md_bin_dependencies = ['zeal'] -class Plugin(TriggerQueryHandler): - iconUrl = "xdg:zeal" - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return 'z ' +class Plugin(PluginInstance, TriggerQueryHandler): + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='z ') + PluginInstance.__init__(self, extensions=[self]) def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: query.add( - Item( + StandardItem( id=md_name, text=md_name, subtext=f"Search '{stripped}' in Zeal", - icon=[Plugin.iconUrl], + iconUrls=["xdg:zeal"], actions=[Action("zeal", "Search in Zeal", lambda s=stripped: runDetachedProcess(['zeal', s]))] ) ) From d14fdb4e23ac9bab5f30e5eebc93136a4ab8a36f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:46:40 +0200 Subject: [PATCH 034/243] [arch_wiki] Interface v2.0 --- arch_wiki/__init__.py | 73 +++++++++++++--------------- arch_wiki/{ArchWiki.svg => arch.svg} | 0 2 files changed, 34 insertions(+), 39 deletions(-) rename arch_wiki/{ArchWiki.svg => arch.svg} (100%) diff --git a/arch_wiki/__init__.py b/arch_wiki/__init__.py index 0c531506..cc70f54e 100644 --- a/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -1,49 +1,44 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2022-2023 Manuel Schneider -from albert import * +import json +from pathlib import Path from time import sleep from urllib import request, parse -import json -import os -md_iid = '1.0' -md_version = "1.3" +from albert import * + +md_iid = '2.0' +md_version = "1.4" md_name = "ArchLinux Wiki" md_description = "Search ArchLinux Wiki articles" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/awiki" -md_maintainers = "@manuelschneid3r" -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): - icon = [os.path.dirname(__file__) + "/ArchWiki.svg"] baseurl = 'https://wiki.archlinux.org/api.php' search_url = "https://wiki.archlinux.org/index.php?search=%s" user_agent = "org.albert.extension.python.archwiki" - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "awiki " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='awiki ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: # avoid rate limiting - for number in range(50): + for _ in range(50): sleep(0.01) if not query.isValid: - return; + return results = [] @@ -65,25 +60,25 @@ def handleTriggerQuery(self, query): summary = data[2][i] url = data[3][i] - results.append(Item(id=md_id, - text=title, - subtext=summary if summary else url, - icon=self.icon, - actions=[ - Action("open", "Open article", lambda u=url: openUrl(u)), - Action("copy", "Copy URL", lambda u=url: setClipboardText(u)) - ])) + results.append(StandardItem(id=md_id, + text=title, + subtext=summary if summary else url, + iconUrls=self.iconUrls, + actions=[ + Action("open", "Open article", lambda u=url: openUrl(u)), + Action("copy", "Copy URL", lambda u=url: setClipboardText(u)) + ])) if results: query.add(results) else: - query.add(Item(id=md_id, - text="Search '%s'" % query.string, - subtext="No results. Start online search on Arch Wiki", - icon=self.icon, - actions=[Action("search", "Open search", lambda s=query.string: self.search_url % s)])) + query.add(StandardItem(id=md_id, + text="Search '%s'" % query.string, + subtext="No results. Start online search on Arch Wiki", + iconUrls=self.iconUrls, + actions=[Action("search", "Open search", lambda s=query.string: self.search_url % s)])) else: - query.add(Item(id=md_id, - text=md_name, - icon=self.icon, - subtext="Enter a query to search on the Arch Wiki")) + query.add(StandardItem(id=md_id, + text=md_name, + iconUrls=self.iconUrls, + subtext="Enter a query to search on the Arch Wiki")) diff --git a/arch_wiki/ArchWiki.svg b/arch_wiki/arch.svg similarity index 100% rename from arch_wiki/ArchWiki.svg rename to arch_wiki/arch.svg From 27b6995994b169f75d02e9dea7a7b019e734415c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:51:41 +0200 Subject: [PATCH 035/243] [aur] Interface v2.0 --- aur/__init__.py | 60 +++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/aur/__init__.py b/aur/__init__.py index 6d9e6c13..a2bcb1e2 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -6,42 +6,38 @@ quickly install the packages. If you are missing your favorite AUR helper tool send a PR. """ -from albert import * -from shutil import which +import json from datetime import datetime -from urllib import request, parse +from pathlib import Path +from shutil import which from time import sleep -import json -import os +from urllib import request, parse + +from albert import * -md_iid = '1.0' -md_version = "1.7" +md_iid = '2.0' +md_version = "1.8" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/aur" -md_maintainers = "@manuelschneid3r" +# md_platforms = ["Linux"] -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): aur_url = "https://aur.archlinux.org/packages/" baseurl = 'https://aur.archlinux.org/rpc/' - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "aur " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='aur ') + PluginInstance.__init__(self, extensions=[self]) - def initialize(self): - self.icon = [os.path.dirname(__file__)+"/arch.svg"] + self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] if which("yaourt"): self.install_cmdline = "yaourt -S aur/%s" @@ -56,7 +52,7 @@ def initialize(self): self.install_cmdline = None def handleTriggerQuery(self, query): - for number in range(50): + for _ in range(50): sleep(0.01) if not query.isValid: return @@ -75,11 +71,11 @@ def handleTriggerQuery(self, query): with request.urlopen(req) as response: data = json.loads(response.read().decode()) if data['type'] == "error": - query.add(Item( + query.add(StandardItem( id=md_id, text="Error", subtext=data['error'], - icon=self.icon + iconUrls=self.iconUrls )) else: results = [] @@ -89,13 +85,13 @@ def handleTriggerQuery(self, query): for entry in results_json: name = entry['Name'] - item = Item( - id = md_id, - icon = self.icon, - text = f"{entry['Name']} {entry['Version']}" + item = StandardItem( + id=md_id, + iconUrls=self.iconUrls, + text=f"{entry['Name']} {entry['Version']}" ) - subtext = f"☆{entry['NumVotes']}" + subtext = f"⭐{entry['NumVotes']}" if entry['Maintainer'] is None: subtext += ', Unmaintained!' if entry['OutOfDate']: @@ -136,10 +132,10 @@ def handleTriggerQuery(self, query): query.add(results) else: - query.add(Item( + query.add(StandardItem( id=md_id, text=md_name, subtext="Enter a query to search the AUR", - icon=self.icon, + iconUrls=self.iconUrls, actions=[Action("open-aur", "Open AUR packages website", lambda: openUrl(self.aur_url))] )) From 2277ff74d521431f6a00fea9fa7c9a12c3c4cafe Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:52:39 +0200 Subject: [PATCH 036/243] [bitwarden] Interface v2.0 --- bitwarden/__init__.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 0185358b..78d39f51 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -import os +from pathlib import Path from subprocess import run, CalledProcessError from albert import * -md_iid = '1.0' -md_version = "1.2" +md_iid = '2.0' +md_version = "1.3" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "BSD-3" @@ -15,21 +15,17 @@ md_credits = "Original author: @tylio" md_bin_dependencies = ["rbw"] -class Plugin(TriggerQueryHandler): - def id(self): - return md_id - def name(self): - return md_name +class Plugin(PluginInstance, TriggerQueryHandler): - def description(self): - return md_description - - def defaultTrigger(self): - return "bw " - - def initialize(self): - self.icon = [os.path.dirname(__file__) + "/bw.svg"] + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='bw ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] def _get_passwords(self): field_names = ["id", "name", "user", "folder"] @@ -54,17 +50,17 @@ def _get_passwords(self): def handleTriggerQuery(self, query): if query.string.strip().lower() == "unlock": query.add( - Item( + StandardItem( id="unlock", text="Unlock Bitwarden Vault", - icon=self.icon, + iconUrls=self.iconUrls, actions=[ Action( id="unlock", text="Unlocking Bitwarden Vault", callable=lambda: runTerminal( - script="rbw stop-agent && rbw unlock", - close_on_exit=True + script="rbw stop-agent && rbw unlock", + close_on_exit=True ) ) ] @@ -105,11 +101,11 @@ def handleTriggerQuery(self, query): except CalledProcessError as err: code = run (["echo"], capture_output=True,encoding="utf-8", check=True) query.add( - Item( + StandardItem( id=p["id"], text=p["path"], subtext=p["user"], - icon=self.icon, + iconUrls=self.iconUrls, actions=[ Action( id="copy", From 8c688ecfaf5e39b3fbf5e6e1bc6ebf442ec352fd Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:52:59 +0200 Subject: [PATCH 037/243] [coingecko] Interface v2.0 --- coingecko/__init__.py | 94 +++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 61b0019b..328f5e6b 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -9,8 +9,8 @@ from pathlib import Path from threading import Thread, Event -md_iid = "1.0" -md_version = "1.0" +md_iid = "2.0" +md_version = "1.1" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "BSD-3" @@ -52,7 +52,7 @@ def stop(self): self._stop_event.set() -class CoinItem(AbstractItem): +class NameItem(StandardItem): def __init__(self, identifier: str, name: str, @@ -62,48 +62,40 @@ def __init__(self, cap: float, vol: float, change24h: float): - AbstractItem.__init__(self) - self.identifier = identifier + StandardItem.__init__( + self, + id=identifier, + text=f"{name} {price} {symbol}/$", + subtext=f"#{rank}, 24h: {change24h}%, Cap: {cap:n} $, Vol: {vol:n} $", + inputActionText=str(price), + iconUrls=Plugin.iconUrls, + actions=[ + Action("show", f"Show {name} on CoinGecko", + lambda id=identifier: openUrl(Plugin.coinsUrl + id)), + Action("url", "Copy URL to clipboard", + lambda id=identifier: setClipboardText(Plugin.coinsUrl + id)) + ] + ) self.name = name self.symbol = symbol - self.rank = rank - self.price = price - self.cap = cap - self.vol = vol - self.change24h = change24h - def id(self): - return self.identifier - def text(self): - return f"{self.name} {self.price} {self.symbol}/$" +class Plugin(PluginInstance, IndexQueryHandler): - def subtext(self): - return f"#{self.rank}, 24h: {self.change24h}%, Cap: {self.cap:n} $, Vol: {self.vol:n} $" - - def completion(self): - return str(self.price) - - def icon(self): - return [Plugin.iconPath] - - def actions(self): - return [ - Action("show", f"Show {self.name} on CoinGecko", - lambda id=self.identifier: openUrl(Plugin.coinsUrl + id)), - Action("url", "Copy URL to clipboard", - lambda id=self.identifier: setClipboardText(Plugin.coinsUrl + id)) - ] - - -class Plugin(IndexQueryHandler): - iconPath = str(Path(__file__).parents[0] / "coingecko.png") coinsUrl = "https://www.coingecko.com/en/coins/" + iconUrls = [f"file:{Path(__file__).parent}/coingecko.png"] + + def __init__(self): + IndexQueryHandler.__init__( + self, md_id, md_name, md_description, + defaultTrigger='cg ', + synopsis='< symbol | name >' + ) + PluginInstance.__init__(self, extensions=[self]) - def initialize(self): self.items = [] self.mtime = 0 - self.coinCacheFilePath = Path(self.cacheLocation()) / "coins.json" + self.coinCacheFilePath = self.cacheLocation / "coins.json" self.thread = CoinFetcherThread(self.updateIndexItems, self.coinCacheFilePath) self.thread.start() @@ -111,18 +103,6 @@ def finalize(self): self.thread.stop() self.thread.join() - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "cg " - def updateIndexItems(self): mtime = self.coinCacheFilePath.lstat().st_mtime if self.coinCacheFilePath.is_file() and mtime > self.mtime: @@ -130,15 +110,15 @@ def updateIndexItems(self): with open(self.coinCacheFilePath) as f: self.items.clear() for json_object in load(f): - self.items.append(CoinItem( - identifier = json_object['id'], - name = json_object['name'], - symbol = json_object['symbol'].upper(), - rank = json_object['market_cap_rank'], - price = json_object['current_price'], - cap = json_object['market_cap'], - vol = json_object['total_volume'], - change24h = json_object['price_change_percentage_24h'] + self.items.append(NameItem( + identifier=json_object['id'], + name=json_object['name'], + symbol=json_object['symbol'].upper(), + rank=json_object['market_cap_rank'], + price=json_object['current_price'], + cap=json_object['market_cap'], + vol=json_object['total_volume'], + change24h=json_object['price_change_percentage_24h'] )) index_items = [] From e3eb2bd3e5e3f7d8a08139887965d9fe0c290dc7 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:53:19 +0200 Subject: [PATCH 038/243] [copyq] Interface v2.0 --- copyq/__init__.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/copyq/__init__.py b/copyq/__init__.py index 7c241b55..ae418eb5 100644 --- a/copyq/__init__.py +++ b/copyq/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = '1.0' -md_version = "1.3" +md_iid = '2.0' +md_version = "1.4" md_name = "CopyQ" md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" @@ -45,21 +45,16 @@ """ -class Plugin(TriggerQueryHandler): - def id(self): - return md_id +class Plugin(PluginInstance, TriggerQueryHandler): - def name(self): - return md_name - - def description(self): - return md_description - - def synopsis(self): - return "" - - def defaultTrigger(self): - return "cq " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis="", + defaultTrigger='cq ') + PluginInstance.__init__(self, extensions=[self]) def handleTriggerQuery(self, query): items = [] @@ -81,9 +76,9 @@ def handleTriggerQuery(self, query): lambda: runDetachedProcess(["copyq", script % row]) ) items.append( - Item( + StandardItem( id=md_id, - icon=["xdg:copyq"], + iconUrls=["xdg:copyq"], text=text, subtext="%s: %s" % (row, ", ".join(json_obj["mimetypes"])), actions=[ From 8252377dd282310f59997ec5ed51adbec5b06898 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:55:03 +0200 Subject: [PATCH 039/243] [dice_roll] Interface v2.0 --- dice_roll/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index 944e054c..5a338b75 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -14,8 +14,8 @@ Example: "roll 2d6 3d8 1d20" """ -md_iid = '1.0' -md_version = "1.1" +md_iid = '2.0' +md_version = "1.2" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -39,7 +39,7 @@ def get_icon_path(num_sides: int | None) -> str: if num_sides is None: icon = "dice" # return the path to the icon - return str(icons_path / f"{icon}.svg") + return str("file:" + icons_path / f"{icon}.svg") def roll_dice(num_dice: int, num_sides: int) -> tuple[int, list[int]]: @@ -57,10 +57,9 @@ def roll_dice(num_dice: int, num_sides: int) -> tuple[int, list[int]]: def get_item_from_rolls( - rolls: list[int], - sum_rolls: int, - num_sides: int | None = None, -) -> albert.Item: + rolls: list[int], + sum_rolls: int, + num_sides: int | None = None) -> albert.Item: """Creates an Albert Item from a list of rolls, the total, and the number of sides. If num_sides is not provided, an "Overall Total" summary item is created. @@ -72,9 +71,9 @@ def get_item_from_rolls( Returns: albert.Item: The item to be added to the list of results. """ - return albert.Item( + return albert.StandardItem( id=get_icon_path(num_sides), - icon=[get_icon_path(num_sides)], + iconUrls=[get_icon_path(num_sides)], text=( f"Rolled {len(rolls)}d{num_sides} - Total: {sum_rolls}" if num_sides @@ -132,7 +131,7 @@ class Plugin(albert.TriggerQueryHandler): """A plugin to roll dice""" def id(self) -> str: - return __name__ + return md_id def name(self) -> str: return md_name From 534faef2bb1edddc8c42908130ff2665c46c293b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:55:31 +0200 Subject: [PATCH 040/243] [docker] Interface v2.0 --- docker/__init__.py | 93 +++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index eb8fb412..547762f2 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -2,39 +2,34 @@ Docker wrapper (prototype) """ -# Copyright (c) 2022-2023 Manuel Schneider +from pathlib import Path -from albert import * -import pathlib import docker +from albert import * -md_iid = "1.0" -md_version = "1.4" +md_iid = "2.0" +md_version = "1.5" md_name = "Docker" -md_description = "Control your docker instance" +md_description = "Manage docker images and containers" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/docker" md_bin_dependencies = "docker" md_lib_dependencies = "docker" -class Plugin(QueryHandler): - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description +class Plugin(PluginInstance, GlobalQueryHandler): - def defaultTrigger(self): - return "d " + def __init__(self): + GlobalQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='d ', + synopsis='') + PluginInstance.__init__(self, extensions=[self]) - def initialize(self): - self.icon_running = [str(pathlib.Path(__file__).parent / "running.png")] - self.icon_stopped = [str(pathlib.Path(__file__).parent / "stopped.png")] + self.icon_urls_running = [f"file:{Path(__file__).parent}/running.png"] + self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] self.client = docker.from_env() if not self.client: self.client = docker.DockerClient(base_url='unix://var/run/docker.sock') @@ -48,48 +43,44 @@ def handleGlobalQuery(self, query): if query.string in container.name: # Create dynamic actions if container.status == 'running': - actions = [ - Action("stop", "Stop container", lambda c=container: c.stop()), - Action("restart", "Restart container", lambda c=container: c.restart()) - ] + actions = [Action("stop", "Stop container", lambda c=container: c.stop()), + Action("restart", "Restart container", lambda c=container: c.restart())] else: - actions = [ - Action("start", "Start container", lambda c=container: c.start()) - ] + actions = [Action("start", "Start container", lambda c=container: c.start())] actions.extend([ - Action("logs", "Logs", lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), - Action("remove", "Remove (forced, with volumes)", lambda c=container: c.remove(v=True, force=True)), - Action("copy-id", "Copy id to clipboard", lambda id=container.id: setClipboardText(id)) + Action("logs", "Logs", + lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), + Action("remove", "Remove (forced, with volumes)", + lambda c=container: c.remove(v=True, force=True)), + Action("copy-id", "Copy id to clipboard", + lambda id=container.id: setClipboardText(id)) ]) rank_items.append(RankItem( - item=Item( + item=StandardItem( id=container.id, text="%s (%s)" % (container.name, ", ".join(container.image.tags)), subtext="Container: %s" % container.id, - icon=self.icon_running if container.status == 'running' else self.icon_stopped, + iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, actions=actions ), - score=0 # len(query.string)/len(container.name) + score=len(query.string)/len(container.name) )) for image in reversed(self.client.images.list()): - if any([query.string in tag for tag in image.tags]): - rank_items.append(RankItem( - item=Item( - id=image.short_id, - text=", ".join(image.tags), - subtext="Image: %s" % image.id, - icon=self.icon_stopped, - actions=[ - Action("run", "Run with command: %s" % query.string, - lambda i=image, s=query.string: client.containers.run(i, s)), - Action("rmi", "Remove image", lambda i=image: i.remove()) - ] - ), - score=0 - )) - + for tag in sorted(image.tags, key=len): # order by resulting score + if query.string in tag: + rank_items.append(RankItem( + item=StandardItem( + id=image.short_id, + text=", ".join(image.tags), + subtext="Image: %s" % image.id, + iconUrls=self.icon_urls_stopped, + actions=[Action("run", "Run with command: %s" % query.string, + lambda i=image, s=query.string: client.containers.run(i, s)), + Action("rmi", "Remove image", lambda i=image: i.remove())] + ), + score=len(query.string)/len(tag) + )) return rank_items - From c3bf76b097f376a768310e836918de3923ad39a2 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:56:07 +0200 Subject: [PATCH 041/243] [emoji] Drop old linux emoji implementation --- emoji/__init__.py | 138 -- emoji/aliases.json | 1 - emoji/emoji-test.txt | 5024 ------------------------------------------ emoji/scrape_aliases | 68 - 4 files changed, 5231 deletions(-) delete mode 100644 emoji/__init__.py delete mode 100644 emoji/aliases.json delete mode 100644 emoji/emoji-test.txt delete mode 100755 emoji/scrape_aliases diff --git a/emoji/__init__.py b/emoji/__init__.py deleted file mode 100644 index 12943ff4..00000000 --- a/emoji/__init__.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -import re -import subprocess -from concurrent.futures import ThreadPoolExecutor -from itertools import islice -from pathlib import Path -from albert import * - -md_iid = '1.0' -md_version = "1.3" -md_name = "Emoji Picker" -md_description = "Find emojis by name" -md_license = "GPL-3.0" -md_url = "https://github.com/albertlauncher/python/tree/master/emoji" -md_maintainers = "@tyilo" -md_bin_dependencies = ["convert"] - - -EXTENSION_DIR = Path(__file__).parent -ALIASES_PATH = EXTENSION_DIR / "aliases.json" -EMOJI_PATH = EXTENSION_DIR / "emoji-test.txt" - - -def convert_to_png(emoji, output_path): - subprocess.run( - [ - "convert", - "-pointsize", - "64", - "-background", - "transparent", - f"pango:{emoji}", - output_path, - ] - ) - - -def schedule_create_missing_icons(emojis, icon_dir_path: Path): - executor = ThreadPoolExecutor() - for emoji in emojis: - path = icon_dir_path / f"{emoji['emoji']}.png" - if not path.exists(): - executor.submit(convert_to_png, emoji["emoji"], path) - - return executor - - -class Plugin(TriggerQueryHandler): - def id(self): - return __name__ - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return ": " - - def synopsis(self): - return "" - - def initialize(self): - self.icon_dir_path = Path(self.cacheLocation()) - - line_re = re.compile( - r""" - ^ - (?P .*\S) - \s*;\s* - (?P \S+) - \s*\#\s* - (?P \S+) - \s* - (?P E\d+.\d+) - \s* - (?P [^:]+) - (?: : \s* (?P .+))? - \n - $ - """, - re.VERBOSE, - ) - - with ALIASES_PATH.open("r") as f: - aliases = json.load(f) - - self.emojis = [] - with EMOJI_PATH.open("r") as f: - for line in f: - if m := line_re.match(line): - e = m.groupdict() - if e["status"] == "fully-qualified": - search_tokens = [e["name"]] - if e["modifiers"]: - search_tokens.append(e["modifiers"]) - e["aliases"] = [a.lower() for a in aliases.get(e["name"], [])] - search_tokens += e["aliases"] - e["search_tokens"] = search_tokens - self.emojis.append(e) - - self.icon_executor = schedule_create_missing_icons(self.emojis, self.icon_dir_path) - - def finalize(self): - self.icon_executor.shutdown(wait=True, cancel_futures=True) - - def matched_emojis(self, query_tokens): - for emoji in self.emojis: - for w in query_tokens: - if w not in " ".join(emoji["search_tokens"]): - break - - yield emoji - - def handleTriggerQuery(self, query): - query_tokens = query.string.strip().lower().split() - if not query_tokens: - return - - for emoji in islice(self.matched_emojis(query_tokens), 100): - query.add( - Item( - id=f"emoji_{emoji['emoji']}", - text=f"{emoji['emoji']} {emoji['name']}", - subtext=emoji["modifiers"] or "", - icon=[str(self.icon_dir_path / f"{emoji['emoji']}.png")], - actions=[ - Action( - "copy", - "Copy to clipboard", - lambda r=emoji["emoji"]: setClipboardText(r), - ), - ], - ) - ) diff --git a/emoji/aliases.json b/emoji/aliases.json deleted file mode 100644 index 55b0874d..00000000 --- a/emoji/aliases.json +++ /dev/null @@ -1 +0,0 @@ -{"person walking": ["Walker", "Walking"], "grinning face with sweat": ["Exercise", "Happy Sweat"], "slightly smiling face": ["Slightly Happy", "This Is Fine"], "face with tears of joy": ["Laughing", "Laughing Crying", "Laughing Tears", "LOL"], "upside-down face": ["Sarcasm", "Silly"], "grinning squinting face": ["><", "Big Grin", "Closed-Eyes Smile", "Laughing", "XD"], "smiling face with hearts": ["In Love Face"], "winking face": ["Wink", "Wink Face", "Winky Face"], "rolling on the floor laughing": ["ROFL"], "smiling face with heart-eyes": ["Heart Eyes", "Heart Face"], "face blowing a kiss": ["Blow A Kiss", "Blowing Kiss", "Kissing"], "kissing face": [":-*", "Duck Face", "Kissy Face", "Whistling"], "star-struck": ["Excited", "Star Eyes", "Starry Eyed", "Wow Face"], "smiling face with halo": ["Angel", "Halo"], "kissing face with closed eyes": ["Kiss Face", "Kissy Face"], "smiling face with smiling eyes": ["^^", "Happy Face", "Smile", "Smiley Face"], "face with tongue": ["Cheeky", "Tongue Face", "Tongue-Out"], "face savoring food": ["Goofy", "Hungry"], "smiling face": ["Happy Face", "Smiley Face", "Smiling"], "winking face with tongue": ["Crazy", "Crazy Face"], "kissing face with smiling eyes": ["Kiss Face", "Kissy", "Whistle", "Whistling"], "shushing face": ["Hush", "Quiet", "Shh"], "zany face": ["Crazy Eyes", "Excited", "Wild"], "squinting face with tongue": ["Tongue Out"], "thinking face": ["Chin Thumb", "Thinker", "Throwing Shade"], "zipper-mouth face": ["Lips Sealed", "Sealed Lips", "Zip It"], "face with raised eyebrow": ["Colbert", "The Rock"], "expressionless face": ["Face With Straight Mouth", "Straight Face"], "neutral face": ["Face With Straight Mouth", "Straight Faced"], "smirking face": ["Flirting", "Sexual Face", "Smug Face", "Suggestive Smile"], "face with rolling eyes": ["Eye Roll"], "grimacing face": ["Awkward", "Eek", "Foot In Mouth", "Nervous", "Snapchat Mutual #1 Best Friend"], "face in clouds": ["Brain Fog", "Forgetful", "Haze"], "face without mouth": ["Blank Face", "Mouthless", "Silence", "Silent"], "pensive face": ["Pensive", "Sad", "Sadface", "Sorrowful"], "money-mouth face": ["Dollar Sign Eyes", "Money Face", "Rich"], "face exhaling": ["Sigh"], "lying face": ["Liar", "Long Nose", "Pinocchio"], "sleepy face": ["Side-Tear", "Snot Bubble"], "sleeping face": ["Sleep Face", "Snoring", "Zzz Face"], "face with medical mask": ["Coronavirus", "COVID-19", "Mask Face", "Surgical Mask"], "face with thermometer": ["Ill", "Sick"], "relieved face": ["Content", "Pleased"], "drooling face": ["Drool"], "face with head-bandage": ["Bandaged Head", "Clumsy", "Injured"], "nauseated face": ["Disgust", "Green Face", "Vomit"], "sneezing face": ["Gesundheit"], "woozy face": ["Drunk Face"], "unamused face": ["Dissatisfied", "Meh", "Side-Eye", "Unimpressed"], "exploding head": ["Mind Blown"], "cowboy hat face": ["Cowboy"], "smiling face with sunglasses": ["Cool", "Mutual Best Friends (Snapchat)", "Sunglasses"], "nerd face": ["Nerdy"], "face with spiral eyes": ["Dizzy Face"], "confused face": ["Nonplussed", "Puzzled", ":S"], "face vomiting": ["Spew", "Throwing Up", "Vomit"], "frowning face": ["Megafrown"], "slightly frowning face": ["Slightly Sad"], "face with open mouth": ["Open Mouth", "Surprised"], "astonished face": ["Drunk Face", "Gasping Face", "Shocked Face"], "pleading face": ["Begging", "Glossy Eyes", "Simp"], "hushed face": ["Surprise", "Surprised Face"], "frowning face with open mouth": ["Yawning"], "worried face": ["Sad", "Sadface"], "sad but relieved face": ["Eyebrow Sweat"], "loudly crying face": ["Bawling", "Crying", "Sad Tears", "Sobbing"], "crying face": ["Crying", "Tear"], "flushed face": [":$", "Blushing Face", "Embarrassed", "Shame"], "person running": ["Jogging", "Run"], "fearful face": ["Scared", "Surprised"], "anguished face": ["Pained Face"], "anxious face with sweat": ["Blue Face", "Concerned Face", "Nervous Face"], "face screaming in fear": ["Home Alone", "Scream", "Screaming Face"], "man juggling": ["Male Juggler"], "synagogue": ["Jewish", "Synagog", "Temple"], "card index": ["Index Card", "Rolodex", "System Card"], "shinto shrine": ["Kami-no-michi"], "confounded face": ["Quivering Mouth", "Scrunched Face"], "raising hands": ["Arms In The Air", "Banzai", "Festivus Miracle", "Hallelujah", "Praise Hands", "Two Hands"], "chart decreasing": ["Down Pointing Graph", "Negative Chart"], "chart increasing": ["Positive Chart", "Up Pointing Graph"], "fountain": ["Park", "Water Feature", "Water Fountain"], "woman juggling": ["Female Juggler"], "bar chart": ["Bar Graph"], "kaaba": ["Mecca"], "man cook": ["Male Chef"], "tent": ["Camping Tent"], "round pushpin": ["Dropped Pin", "Map Pin", "Pin", "Red Pin"], "foggy": ["Fog", "Foggy City", "Fog Bridge"], "pushpin": ["Thumb Tack"], "night with stars": ["City At Night", "Starry Night"], "sunrise over mountains": ["Morning", "Sunrise"], "woman cook": ["Female Chef"], "paperclip": ["Clippy"], "cityscape at dusk": ["Dusk City", "Orange Sky City"], "sunrise": ["Sunset"], "person in lotus position": ["Meditation", "Yoga"], "triangular ruler": ["Triangle Ruler"], "sunset": ["City Sunset"], "scissors": ["Cutting"], "hot springs": ["Onsen", "Steam"], "carousel horse": ["Carnival", "Fairground", "Merry Go Round"], "file cabinet": ["Filing Cabinet"], "open hands": ["Hug", "Jazz Hands"], "locked": ["Closed Lock", "Padlock"], "bridge at night": ["Bridge", "Golden Gate Bridge"], "roller coaster": ["Rollercoaster", "Theme Park"], "unlocked": ["Open Padlock", "Unlock"], "locked with key": ["Lock And Key"], "palms up together": ["Dua"], "wastebasket": ["Garbage Can", "Rubbish Bin", "Trash Can", "Wastepaper Basket"], "ferris wheel": ["Big Wheel", "Fairground", "Observation Wheel"], "barber pole": ["Barber Shop", "Barber's Stripes", "Hairdresser"], "circus tent": ["Big Top", "Circus"], "locked with pen": ["Lock And Pen", "Lock With Fountain Pen"], "pick": ["Pickaxe"], "key": ["Gold Key"], "train": ["Diesel Train", "Electric Train", "Passenger Train", "Regular Train"], "locomotive": ["Railway Locomotive", "Steam Train"], "hammer": ["Claw Hammer", "Handyman", "Tool"], "railway car": ["Railcar", "Railroad Car", "Railway Carriage", "Railway Wagon"], "handshake": ["Shaking Hands"], "metro": ["Subway", "Tube", "Underground"], "dagger": ["Knife Weapon"], "station": ["Train Platform", "Train Station"], "bullet train": ["Bullet Train", "Shinkansen"], "mountain railway": ["Funicular", "Train And Mountain"], "person taking bath": ["Bathing", "Hot Bath"], "bow and arrow": ["Archery"], "oncoming bus": ["Front Of Bus"], "bus": ["School Bus"], "wrench": ["Spanner"], "nut and bolt": ["Bolt", "Screw"], "trolleybus": ["Electric Bus", "Trolley Bus"], "minibus": ["Minivan", "People-Mover"], "fire engine": ["Fire Department", "Fire Truck"], "taxi": ["New York Taxi", "Side Of Taxi", "Taxicab"], "balance scale": ["Scales of Justice"], "police car": ["Cop Car", "Side Of Police Car"], "oncoming police car": ["\ud83d\ude93 Front Of Police Car", "\ud83d\ude93 Cop Car"], "link": ["Chain", "Hyperlink", "Linked Chain"], "clamp": ["Clamp", "Table Vice", "WinZip"], "people holding hands": ["Gender Inclusive Couple", "Gender Neutral Couple", "Gender Nonconforming Couple"], "oncoming taxi": ["Front Of Taxi", "Taxicab"], "automobile": ["Car", "Red Car", "Side Of Car"], "oncoming automobile": ["Front Of Car"], "sport utility vehicle": ["Campervan", "Motorhome", "RV"], "articulated lorry": ["Green Truck", "Truck"], "tractor": ["Farm", "Farming"], "racing car": ["F1", "Formula One"], "microscope": ["Magnify", "Science"], "motorcycle": ["Motorbike", "Motorcycle"], "telescope": ["Stargazing"], "syringe": ["Vaccination"], "auto rickshaw": ["Tuk Tuk"], "pill": ["Capsule", "Drugs", "Tablet"], "motor scooter": ["Motor Bike", "Motor Cycle", "Vespa"], "drop of blood": ["Blood Donation", "Menstruation", "Period"], "bicycle": ["Bike", "Push Bike"], "man office worker": ["Businessman", "CEO"], "roller skate": ["Inline Skate", "Roller Derby"], "adhesive bandage": ["Band Aid", "Plaster"], "door": ["Doorway", "Front Door"], "fuel pump": ["Gas Pump", "Petrol Pump"], "bed": ["Bedroom"], "police car light": ["Emergency Light", "Flashing Light", "Police Siren", "Siren"], "motorway": ["Highway", "Interstate", "Road"], "couch and lamp": ["Lounge", "Settee", "Sofa"], "horizontal traffic light": ["Traffic Light"], "folded hands": ["Namaste", "Please", "Prayer", "Thank You"], "toilet": ["Bathroom", "Loo", "Restroom"], "stop sign": ["Stop Sign"], "woman office worker": ["Businesswoman", "CEO"], "vertical traffic light": ["Traffic Light"], "bathtub": ["Bubble Bath"], "ring buoy": ["Life Preserver", "Life Ring"], "sailboat": ["Dinghy", "Yacht"], "anchor": ["Admiralty Pattern Anchor", "Fisherman"], "construction": ["Black And Yellow Striped Sign", "Roadwork", "Roadwork Sign"], "shower": ["Shower Head"], "speedboat": ["Motorboat", "Powerboat"], "broom": ["Brush", "Sweep"], "nail polish": ["Fingers", "Manicure", "Nonchalant"], "ship": ["Cruise", "Cruise Ship"], "airplane": ["Aeroplane", "Plane"], "roll of paper": ["Toilet Paper"], "airplane arrival": ["Aeroplane Landing", "Plane Landing"], "selfie": ["Phone Camera", "Selfie Hand"], "small airplane": ["Small Aeroplane", "Small Plane"], "seat": ["Aeroplane Seat", "Airplane Seat", "Bus Seat", "Train Seat"], "cigarette": ["Cigarette", "Smoke"], "women holding hands": ["Lesbian Couple"], "airplane departure": ["Aeroplane Taking Off", "Plane Taking Off"], "shopping cart": ["Shopping Cart"], "coffin": ["Casket", "Funeral"], "aerial tramway": ["Cable Car", "Gondola", "Ropeway"], "funeral urn": ["Vase"], "nazar amulet": ["Evil Eye Talisman", "Nazar Boncu\u011fu"], "flexed biceps": ["Feats of Strength", "Flexing Arm Muscles", "Muscle", "Strong"], "rocket": ["Rocket Ship", "Space Shuttle"], "placard": ["Lawn Sign", "Protest Sign", "Sign", "Sign on Post"], "flying saucer": ["UFO"], "man technologist": ["Male Blogger"], "moai": ["Easter Island", "Human Rock Carving", "Moai", "Moyai Statue"], "luggage": ["Suitcase"], "alarm clock": ["Alarm", "Clock"], "woman technologist": ["Female Blogger"], "litter in bin sign": ["Person With Trash", "Put Litter In Trash"], "potable water": ["Thirst", "Thirsty", "Water Tap"], "watch": ["Apple Watch", "Timepiece", "Wrist Watch"], "restroom": ["Bathroom Sign", "Toilet Sign"], "wheelchair symbol": ["Accessible Bathroom"], "baby symbol": ["Baby Change Station", "Baby Change Symbol", "Nursery"], "water closet": ["Toilet WC", "WC"], "passport control": ["Border Control"], "warning": ["Alert Symbol"], "children crossing": ["Kids Crossing", "School Crossing"], "man singer": ["Aladdin Sane", "Bowie"], "ear": ["Ears", "Hearing", "Listening"], "prohibited": ["Banned", "Circle Backslash", "No", "Red Circle Crossed", "Restricted"], "left luggage": ["Bag With Key", "Locked Suitcase"], "non-potable water": ["No Drinking Water", "No Water"], "no mobile phones": ["No Cell Phones", "No Phones", "No Smartphones"], "no bicycles": ["No Bikes Sign"], "radioactive": ["International Radiation Symbol", "Nuclear"], "no one under eighteen": ["NSFW"], "no pedestrians": ["No People", "No Walking"], "nose": ["Smelling", "Sniffing", "Stinky"], "up arrow": ["Arrow Pointing Up", "Up Arrow"], "left arrow": ["Arrow Pointing Left", "Left Arrow"], "down arrow": ["Arrow Pointing Down", "Down Arrow"], "up-right arrow": ["Diagonal Up-Right Arrow"], "right arrow": ["Arrow Pointing Right", "Right Arrow"], "up-left arrow": ["Diagonal Up-Left Arrow"], "down-right arrow": ["Diagonal Down-Right Arrow"], "down-left arrow": ["Diagonal Down-Left Arrow"], "left-right arrow": ["Horizontal Arrows", "Sideways Arrows"], "right arrow curving left": ["Email Reply", "Left Curved Arrow"], "up-down arrow": ["Vertical Arrows"], "right arrow curving down": ["Curved Down Arrow"], "woman and man holding hands": ["Heterosexual Couple", "Straight Couple"], "eyes": ["Eyeballs", "Shifty Eyes", "Wide Eyes"], "new moon": ["Dark Moon", "Shadow Moon", "Solar Eclipse"], "tongue": ["Tongue Out"], "eye": ["Single Eye"], "left arrow curving right": ["Email Forward", "Right Curved Arrow"], "baby": ["Child", "Toddler"], "mouth": ["Kissing Lips", "Lips"], "counterclockwise arrows button": ["Refresh", "Rotate", "Switch"], "place of worship": ["Religious Building"], "new moon face": ["Creepy Moon", "Dark Moon Face", "Molester Moon"], "bread": ["Loaf Of Bread"], "pancakes": ["Cr\u00eapes", "Hotcakes"], "baguette bread": ["French Bread"], "cut of meat": ["Meat", "Steak"], "cheese wedge": ["Cheese"], "poultry leg": ["Drumstick", "Turkey Leg"], "chestnut": ["Acorn", "Nut"], "hamburger": ["Burger", "Cheeseburger"], "pizza": ["Pepperoni Pizza", "Pizza"], "meat on bone": ["Barbecue", "BBQ", "Manga Meat"], "flatbread": ["Arepa"], "bacon": ["Rashers"], "tamale": ["Tamal"], "french fries": ["Chips", "Fries", "McDonald's Fries"], "burrito": ["Wrap"], "stuffed flatbread": ["Doner Kebab", "Gyro", "Shawarma"], "shallow pan of food": ["Paella"], "bowl with spoon": ["Cereal Bowl"], "cooking": ["Breakfast", "Fried Egg", "Frying Pan"], "popcorn": ["Popping Corn"], "pot of food": ["Bowl Of Food", "Soup", "Stew"], "canned food": ["Can of Food", "Tin Can", "Tinned Food"], "rice cracker": ["Cracker"], "rice ball": ["Onigiri"], "steaming bowl": ["Noodles", "Noodles With Chopsticks", "Ramen"], "hot dog": ["Hotdog", "Sausage"], "bento box": ["Lunch Box"], "curry rice": ["Curry", "Indian Food"], "sushi": ["Sashimi", "Seafood"], "fried shrimp": ["Fried Prawn", "Shrimp Tempura"], "oden": ["Kebab", "Skewer"], "fish cake with swirl": ["Fishcake", "Pink Swirl"], "cooked rice": ["Boiled Rice", "Bowl Of Rice", "Rice", "Steamed Rice"], "dumpling": ["Empanada", "Pierogi"], "takeout box": ["Chinese Food Box", "Oyster Pail"], "roasted sweet potato": ["Goguma", "Sweet Potato", "Yam"], "dango": ["Dessert Stick", "Pink White Green Balls"], "shrimp": ["Prawn"], "soft ice cream": ["Mr. Whippy", "Soft Serve"], "green salad": ["Salad"], "shaved ice": ["Snow Cone"], "ice cream": ["Bowl Of Ice Cream", "Dessert"], "doughnut": ["Donut"], "birthday cake": ["Birthday", "Cake", "Cake With Candles"], "shortcake": ["Cake", "Piece Of Cake", "Strawberry Shortcake"], "spaghetti": ["Pasta"], "cupcake": ["Fairy Cake"], "candy": ["Lolly", "Sweet"], "lollipop": ["Lollypop", "Sucker"], "custard": ["Creme Caramel", "Dessert", "Flan", "Pudding"], "teacup without handle": ["Green Tea", "Matcha", "Matcha Green Tea"], "chocolate bar": ["Candy Bar", "Chocolate"], "wine glass": ["Alcohol", "Red Wine", "Wine"], "cookie": ["Biscuit", "Chocolate Chip Cookie"], "honey pot": ["Honey", "Pot"], "sake": ["Bottle", "Rice Wine"], "glass of milk": ["Milk"], "bottle with popping cork": ["Celebration", "Champagne", "Sparkling Wine"], "hot beverage": ["Coffee", "Espresso", "Hot Chocolate", "Tea"], "tropical drink": ["Fruit Punch", "Tiki Drink"], "beer mug": ["Beer", "Beer Stein"], "cocktail glass": ["Cocktail", "Martini"], "bubble tea": ["Boba"], "tumbler glass": ["Bourbon", "Liquor", "Rum", "Whiskey", "Whisky"], "clinking glasses": ["Celebration", "Champagne Glasses", "Cheers"], "cup with straw": ["Milkshake", "Smoothie", "Soda Pop", "Soft Drink"], "clinking beer mugs": ["Beers", "Cheers"], "beverage box": ["Juice Box"], "mate": ["Chimarr\u00e3o", "Cimarr\u00f3n", "Yerba Mate"], "fork and knife with plate": ["Dinner"], "baby bottle": ["Bottle Feeding"], "kitchen knife": ["Butchers Knife", "Kitchen Knife", "Knife"], "amphora": ["Jar", "Vase"], "family": ["Parents With Child"], "globe with meridians": ["Internet", "World Wide Web", "WWW"], "fork and knife": ["Cutlery", "Knife And Fork", "Silverware"], "mount fuji": ["Fuji-san", "Snow-capped Mountain"], "stadium": ["Grandstand", "Sport Stadium"], "camping": ["Campsite"], "building construction": ["Crane"], "derelict house": ["Abandoned House", "Haunted House", "Old House"], "house": ["Home"], "office building": ["City Building", "High-Rise Building"], "house with garden": ["House And Tree"], "hospital": ["Emergency Room", "Medical", "Red Cross"], "hotel": ["Accommodation", "H Building"], "bank": ["Bakkureru", "Bank Branch", "BK"], "department store": ["Shopping Center", "Shops"], "factory": ["Industrial", "Industry", "Pollution", "Smog"], "convenience store": ["24-Hour Store", "7-Eleven\u00ae", "Corner Shop", "Kwik-E-Mart"], "love hotel": ["Heart Hospital", "Love Heart Hotel"], "school": ["Clock Tower", "Elementary School", "High School", "Middle School"], "post office": ["Post Office"], "church": ["Church Building", "Cross"], "wedding": ["Church Heart", "Church Wedding", "Marriage"], "mosque": ["Domed Roof", "Minaret"], "clutch bag": ["Clutch", "Small Bag"], "castle": ["Castle", "Turrets"], "pinched fingers": ["Finger Purse", "Ma Che Vuoi"], "thong sandal": ["Flip Flops", "Jandals", "Thongs"], "running shoe": ["Runner", "Sneaker", "Trainer"], "backpack": ["Backpack", "Bag", "School Bag"], "grinning face with big eyes": ["Grinning Face", "Happy", "Happy Face", "Smiley Face"], "grinning face with smiling eyes": ["Grinning Face", "Happy Face", "Smiley Face"], "ballet shoes": ["Pointe Shoe"], "victory hand": ["Air Quotes", "Peace", "Peace Sign", "V Sign"], "high-heeled shoe": ["High Heels", "Stiletto"], "crown": ["King", "Queen", "Royal"], "top hat": ["Formal Wear", "Groom"], "person facepalming": ["Facepalm", "Hitting Head", "Picard", "SMH"], "graduation cap": ["College", "Graduate", "Mortar Board", "Square Academic Cap", "University"], "crossed fingers": ["Fingers Crossed", "Good Luck"], "billed cap": ["Baseball Cap"], "lipstick": ["Lip Gloss", "Makeup"], "prayer beads": ["Dhikr Beads", "Rosary Beads"], "gem stone": ["Diamond", "Gem", "Jewel"], "person lifting weights": ["Bodybuilder"], "mage": ["Sorcerer", "Sorceress", "Witch", "Wizard"], "speaker low volume": ["Volume"], "ring": ["Diamond Ring", "Engagement Ring"], "speaker medium volume": ["Reduce Volume"], "muted speaker": ["Mute Volume"], "speaker high volume": ["Increase Volume"], "megaphone": ["Megaphone"], "person shrugging": ["\u00af\\_(\u30c4)_/\u00af", "Shrug", "Shruggie"], "loudspeaker": ["Announcement", "PA System"], "man mage": ["Wizard"], "bell": ["Liberty Bell", "Ringer", "Wedding Bell"], "postal horn": ["Bugle", "French Horn"], "bell with slash": ["Notifications", "Ringer Disabled"], "musical score": ["Sheet Music", "Treble Clef"], "musical note": ["Beamed Pair Of Eighth Notes", "Beamed Pair Of Quavers", "Music Note"], "musical notes": ["Music", "Music Notes", "Singing"], "microphone": ["Karaoke", "Singing"], "sign of the horns": ["Devil Fingers", "Heavy Metal", "Rock On"], "headphone": ["Earphone", "Headphones", "iPod"], "woman mage": ["Witch"], "person biking": ["Bicycle", "Bike", "Cyclist", "Person On Bike"], "radio": ["Digital Radio", "Wireless"], "saxophone": ["Jazz", "Sax"], "guitar": ["Acoustic Guitar", "Bass Guitar", "Electric Guitar"], "call me hand": ["Phone Hand", "Shaka"], "trumpet": ["Horn", "Jazz"], "musical keyboard": ["Piano"], "violin": ["String Quartet", "World\u2019s Smallest Violin"], "man biking": ["Male Cyclist"], "backhand index pointing left": ["Pointing Left"], "woman biking": ["Female Cyclist"], "mobile phone with arrow": ["Phone Call", "Phone With Arrow", "Pointing To Phone"], "mobile phone": ["Cell Phone", "iPhone", "Smartphone"], "telephone": ["Rotary Phone", "Telephone"], "man health worker": ["Male Doctor", "Male Nurse"], "pager": ["Beeper", "Bleeper"], "fax machine": ["Facsimile", "Fax"], "telephone receiver": ["Handset", "Phone"], "person mountain biking": ["Mountain Bike", "Person On Bike"], "backhand index pointing right": ["Pointing Right"], "vampire": ["Dracula"], "low battery": ["No Battery", "Red Battery"], "woman health worker": ["Female Doctor", "Female Nurse"], "laptop": ["Laptop", "Notebook"], "electric plug": ["AC Adaptor", "Power Cable", "Power Plug"], "desktop computer": ["iMac"], "battery": ["AA Battery", "Phone Battery"], "floppy disk": ["3.5\u2033 Disk", "Disk"], "man vampire": ["Dracula"], "optical disk": ["CD", "CD-ROM", "Compact Disc"], "backhand index pointing up": ["Middle Finger", "Pointing Up"], "movie camera": ["Film Camera", "Hollywood", "Movie"], "middle finger": ["Dito Medio", "Flipping The Bird", "Middle Finger", "Rude Finger"], "dvd": ["DVD-ROM", "DVD Video"], "clapper board": ["Clapboard", "Director", "Film Slate"], "camera": ["Digital Camera"], "person cartwheeling": ["Gymnast", "Gymnastics"], "merperson": ["Merboy", "Mergirl", "Mermaid", "Merman"], "television": ["TV"], "videocassette": ["VCR", "VHS", "Video Tape"], "backhand index pointing down": ["Pointing Down"], "video camera": ["Camcorder"], "magnifying glass tilted left": ["Magnifier", "Magnifying Glass", "Search Icon"], "light bulb": ["Idea", "Light Bulb"], "magnifying glass tilted right": ["Magnifier", "Magnifying Glass", "Search Icon"], "flashlight": ["Flashlight", "Torch"], "diya lamp": ["Oil Lamp"], "index pointing up": ["Pointing Up", "Secret"], "open book": ["Book", "Novel"], "red paper lantern": ["Asian Lantern", "Japanese Lantern", "Red Lantern"], "closed book": ["Red Book"], "elf": ["Legolas"], "books": ["Pile Of Books", "Stack Of Books"], "ledger": ["Binder", "Spiral Bound Book", "Yellow Book"], "people wrestling": ["Wrestling"], "notebook": ["Black And White Book"], "page with curl": ["Curled Page", "Curly Page"], "scroll": ["Degree", "Parchment"], "page facing up": ["Printed Page"], "thumbs up": ["Like", "Thumbs Up", "Yes"], "rolled-up newspaper": ["Newspaper Delivery"], "bookmark": ["Price Tag", "Tag"], "yen banknote": ["\u00a51000 Note", "Yen Note"], "dollar banknote": ["$1 Note", "American Dollar", "Dollar Bill"], "thumbs down": ["Bad", "Dislike", "No"], "money bag": ["Moneybags", "Rich"], "pound banknote": ["\u00a320 Note", "Pound Note", "Twenty Quid Note"], "euro banknote": ["\u20ac100 Note", "Euro"], "genie": ["Djinni", "Jinni"], "money with wings": ["Flying Money", "Losing Money"], "credit card": ["AMEX", "Diners Club", "Mastercard", "VISA Card"], "raised fist": ["Fist Pump"], "envelope": ["\u2709 Letter"], "envelope with arrow": ["Down Arrow Envelope", "Insert In Envelope"], "incoming envelope": ["Envelope With Lines", "Fast Envelope"], "e-mail": ["Email"], "chart increasing with yen": ["Yen Exchange Rate", "Yen Graph"], "outbox tray": ["Outbox"], "person getting massage": ["Head Massage", "Massaging"], "package": ["Box", "Parcel"], "inbox tray": ["Inbox"], "oncoming fist": ["Bro Fist / Brofist", "Fist Bump", "Punch"], "open mailbox with raised flag": ["Open Mailbox"], "ballot box with ballot": ["Vote Box", "Voting"], "left-facing fist": ["Left Fist Bump"], "pencil": ["\u270f Lead Pencil"], "black nib": ["\u2712 Pen Nib", "\u2712 Fountain Pen"], "paintbrush": ["Brush"], "right-facing fist": ["Right Fist Bump"], "memo": ["Memorandum", "Note", "Pencil And Paper"], "briefcase": ["Suitcase"], "person juggling": ["Juggler"], "clapping hands": ["Applause", "Clap", "Clapping", "Golf Clap", "Round Of Applause"], "calendar": ["July 17", "World Emoji Day"], "grinning face": ["Happy Face", "Smiley Face"], "file folder": ["Folder", "Manilla Folder"], "person getting haircut": ["Cutting Hair", "Hairdresser"], "tear-off calendar": ["Day Calendar", "Desk Calendar"], "open file folder": ["Open Folder"], "disappointed face": [":(", "Sad", "Sadface"], "persevering face": ["Helpless Face", "Scrunched Eyes"], "downcast face with sweat": ["Hard Work", "Sad Sweat Face"], "tired face": ["Exhausted", "Fed Up"], "face with steam from nose": ["Airing of Grievances", "Frustrated", "Mad Face", "Steaming"], "smiling face with horns": ["Devil", "Devil Horns", "Happy Devil", "Purple Devil", "Red Devil"], "face with symbols on mouth": ["Cursing", "Cussing", "Grawlix", "Swearing"], "old woman": ["Elderly Woman", "Grandma", "Nanna", "Old Lady"], "angry face with horns": ["Devil", "Devil Horns", "Purple Devil", "Purple Goblin", "Sad Devil"], "weary face": ["Distraught Face", "Wailing"], "old man": ["Elderly Man", "Grandpa", "Old Man"], "skull": ["Death", "Grey Skull", "Skeleton"], "angry face": ["Angry", "Grumpy Face"], "ghost": ["Disappear", "Ghoul", "Halloween"], "ogre": ["Mask Face", "Oni", "Red Monster"], "goblin": ["Long Nose Face", "Red Mask", "Tengu"], "grinning cat": ["Happy Cat", "Smiling Cat"], "grinning cat with smiling eyes": ["Grinning Cat", "Happy Cat"], "robot": ["Robot"], "smiling cat with heart-eyes": ["Heart Eyes Cat", "Loving Cat"], "cat with wry smile": ["Smirking Cat"], "pile of poo": ["Dog Dirt", "Smiling Poop"], "kissing cat": ["Kissing Cat"], "alien": ["Alien", "ET"], "love letter": ["Heart Envelope", "Love Note"], "person frowning": ["Sad Person", "Woman Frowning"], "heart with arrow": ["Cupid Arrow", "Lovestruck"], "alien monster": ["Space Invader", "Video Game Monster"], "cat with tears of joy": ["Happy Tears Cat", "Laughing Cat"], "person pouting": ["Blank Look", "Fed Up"], "heart with ribbon": ["Chocolate Box", "Gift Box", "Gift Heart"], "sparkling heart": ["Sparkle Heart", "Sparkly Heart", "Stars Heart"], "hear-no-evil monkey": ["Kikazaru", "Monkey Covering Ears"], "growing heart": ["Multiple Heart", "Triple Heart"], "revolving hearts": ["Two Hearts"], "heart exclamation": ["Heart Above Dot"], "two hearts": ["Small Hearts", "Two Pink Hearts"], "mending heart": ["Bandaged Heart", "Healing Heart", "Unbroken Heart"], "red heart": ["Heart", "Love Heart", "Red Heart"], "beating heart": ["Heart Alarm", "Heartbeat", "Wifi Heart"], "weary cat": ["Scared Cat", "Screaming Cat"], "green heart": ["NCT Heart"], "crying cat": ["Crying Cat", "Sad Cat"], "see-no-evil monkey": ["Mizaru", "Monkey Covering Eyes"], "pouting cat": ["Grumpy Cat"], "purple heart": ["BTS Emoji"], "speak-no-evil monkey": ["Iwazaru", "Monkey Covering Mouth", "No Speaking"], "black heart": ["Dark Heart"], "yellow heart": ["#1 BF Snapchat", "Gold Heart"], "hundred points": ["100", "Keep It 100", "Perfect Score"], "blue heart": ["Brand Heart", "Neutral Heart"], "broken heart": ["Breaking Heart", "Brokenhearted", "Heart Broken"], "dizzy": ["Circle And Star", "Dizzy"], "anger symbol": ["Anger Sign", "Vein Pop"], "collision": ["Bang", "Explode", "Impact", "Red Spark"], "speech balloon": ["Chat Bubble", "Speech Bubble"], "dashing away": ["Fast", "Steam", "Vaping", "Wind"], "eye in speech bubble": ["I Am A Witness"], "thought balloon": ["Thinking Bubble", "Thought Bubble"], "kiss mark": ["Kissing Lips"], "waving hand": ["Goodbye", "Hand Wave", "Hello", "Waving"], "right anger bubble": ["Zig Zag Bubble"], "sweat droplets": ["Plewds", "Splashing Water", "Water Drops"], "hand with fingers splayed": ["Five Hand", "Splayed Hand"], "person tipping hand": ["Bellhop", "Concierge", "Hair Flick", "Sassy Girl"], "raised hand": ["High Five", "Stop"], "person raising hand": ["Answering Question", "Hand Up"], "raised back of hand": ["Backhand"], "vulcan salute": ["Spock", "Star Trek", "Vulcan Salute"], "person bowing": ["Bowing Man", "Cute Boy", "Dogeza", "Massage"], "llama": ["Alpaca"], "rhinoceros": ["Rhino"], "hippopotamus": ["Hippo"], "mouse face": ["Mouse"], "mouse": ["Dormouse", "Mice", "Rodent"], "rabbit face": ["Easter Bunny"], "chipmunk": ["Squirrel"], "rat": ["Rodent"], "rabbit": ["Bunny", "Bunny Rabbit"], "bear": ["Teddy Bear"], "koala": ["Koala Bear"], "person with skullcap": ["Asian Man"], "kangaroo": ["Roo"], "person wearing turban": ["Arab", "Muslim", "Sikh", "Turban"], "bat": ["Batman"], "chicken": ["Hen"], "rooster": ["Cock", "Cockerel"], "baby chick": ["Yellow Bird"], "hamster": ["Hamster"], "eagle": ["Bald Eagle"], "person in tuxedo": ["Groom", "Man In Suit"], "paw prints": ["Cat Paw Prints", "Dog Paw Prints", "Kitten Paw Prints", "Puppy Paw Prints"], "turkey": ["Thanksgiving Turkey", "Wild Turkey"], "front-facing baby chick": ["Baby Chick"], "hatching chick": ["Baby Chicken", "Chick Hatching"], "crocodile": ["Alligator", "Croc"], "turtle": ["Tortoise"], "snake": ["Serpent"], "frog": ["Frog", "Toad"], "dragon face": ["Dragon Head"], "lizard": ["Gecko"], "tropical fish": ["Fish", "Yellow-Blue Fish"], "sauropod": ["Brachiosaurus", "Brontosaurus", "Dinosaur"], "fish": ["Freshwater Fish"], "spouting whale": ["Cute Whale"], "snail": ["Garden Snail", "Slug"], "blowfish": ["Fugu", "Pufferfish"], "ant": ["Bug", "Insect"], "honeybee": ["Bee", "Bumblebee"], "bug": ["Caterpillar", "Insect"], "shark": ["Great White Shark"], "lady beetle": ["Ladybird", "Ladybug", "Lady Bug"], "pregnant woman": ["Pregnancy", "Pregnant Lady"], "spiral shell": ["Beach", "Seashell", "Shell"], "cricket": ["Grasshopper"], "spider web": ["Cobweb", "Web"], "microbe": ["Cell", "Coronavirus", "COVID-19", "Germ", "Microorganism", "Virus"], "rose": ["Red Flower", "Red Rose"], "breast-feeding": ["Breastfeeding"], "bouquet": ["Bouquet Of Flowers"], "cherry blossom": ["Pink Flower", "Sakura"], "white flower": ["Cherry Blossom", "Paper Doily", "Well Done Stamp"], "wilted flower": ["Dead Flower", "Drooping Flower"], "blossom": ["Blossoming Flower", "Daisy", "Yellow Flower"], "potted plant": ["Houseplant"], "sunflower": ["Yellow Flower"], "deciduous tree": ["Rounded Tree"], "seedling": ["Spring", "Sprout", "Sprouting"], "sheaf of rice": ["Crop", "Farming", "Wheat"], "herb": ["Crop", "Plant"], "shamrock": ["Clover", "Trefoil"], "evergreen tree": ["Fir Tree", "Pine Tree", "Tree"], "fallen leaf": ["Autumn Leaves", "Brown Leaves", "Fall Leaves"], "cactus": ["Desert"], "baby angel": ["Angel", "Cherub", "Cupid", "Putto"], "mushroom": ["Shroom", "Toadstool"], "maple leaf": ["Canada", "Canadian", "Maple"], "palm tree": ["Coconut Tree"], "tangerine": ["Mandarin", "Orange"], "four leaf clover": ["Clover", "Ireland", "Lucky"], "banana": ["Plantain"], "leaf fluttering in wind": ["Green Leaves", "Spring"], "red apple": ["Red Delicious Apple"], "grapes": ["Grape"], "cherries": ["Cherry", "Wild Cherry"], "lemon": ["Lemonade"], "coconut": ["Cocoanut"], "green apple": ["Golden Delicious Apple", "Granny Smith Apple"], "peach": ["Bottom", "Butt"], "eggplant": ["Eggplant", "Phallic", "Purple Vegetable"], "potato": ["Baked Potato", "Idaho Potato"], "ear of corn": ["Corn", "Corn On The Cob", "Maize"], "melon": ["Cantaloupe", "Honeydew", "Muskmelon"], "kiwi fruit": ["Chinese Gooseberry", "Kiwi"], "hot pepper": ["Chili Pepper", "Spicy"], "cucumber": ["Gherkin", "Pickle"], "child": ["Gender Inclusive Child", "Gender Neutral Child"], "leafy green": ["Bok Choy", "Chinese Cabbage", "Cos Lettuce", "Romaine Lettuce"], "peanuts": ["Nuts"], "thermometer": ["Hot Weather", "Temperature"], "om": ["Aumkara", "Omkara", "Pranava"], "sun": ["Sun", "Sunshine"], "full moon face": ["Moonface", "Smiley Moon", "Smiling Moon"], "woman running": ["Boy Runner"], "sun with face": ["Smiley Sun", "Smiling Sun", "Sunface"], "ringed planet": ["Saturn"], "wheel of dharma": ["Helm"], "woman dancing": ["Red Dress Woman", "Salsa Dancer"], "peace symbol": ["Peace Sign"], "latin cross": ["Christian Cross"], "red square": ["Red Card"], "shooting star": ["Meteoroid", "When You Wish Upon A Star"], "star": ["Gold Star"], "yellow square": ["Yellow Card"], "menorah": ["Candelabrum", "Candles", "Chanukiah", "Menorah"], "glowing star": ["Shining Star"], "milky way": ["Galaxy", "Night Sky", "Space", "Stars", "Universe"], "cloud": ["Cloudy", "Overcast"], "man dancing": ["Disco Dancer", "Male Dancer"], "person": ["Gender Inclusive Adult", "Gender Neutral Adult", "Person"], "person in suit levitating": ["Hovering Man", "Rude Boy", "Walt Jabsco"], "man astronaut": ["Man Cosmonaut"], "rainbow": ["Gay Pride", "Primary Rainbow"], "wind face": ["Blowing Wind", "Mother Nature"], "cyclone": ["Hurricane", "Spiral", "Swirl", "Tornado"], "closed umbrella": ["Collapsed Umbrella", "Pink Umbrella"], "repeat button": ["Loop Symbol", "Retweet"], "diamond with a dot": ["Cuteness", "Diamond Flower", "Kawaii"], "woman astronaut": ["Woman Cosmonaut"], "play button": ["Play Button", "Right Triangle"], "umbrella": ["Umbrella"], "high voltage": ["Lightning Bolt", "Thunderbolt"], "repeat single button": ["Circle Arrows With Number 1", "Loop Once Symbol"], "next track button": ["Next Track"], "man": ["Male", "Moustache Man"], "people with bunny ears": ["Ballet", "Dancing Girls", "Let's Party", "Showgirls"], "snowman": ["Snowing Snowman"], "snowflake": ["Snow", "Snowing"], "umbrella with rain drops": ["Raining", "Rainy"], "snowman without snow": ["Frosty The Snowman", "Olaf", "Snowman"], "chequered flag": ["Checkered Flag", "Grid Girl", "Racing Flag"], "person in steamy room": ["Sauna"], "play or pause button": ["Play-Pause"], "fast-forward button": ["Fast Forward"], "fast reverse button": ["Rewind"], "reverse button": ["Left Triangle"], "triangular flag": ["Flag on Pole", "Red Flag"], "crossed flags": ["Two Flags"], "rainbow flag": ["Pride Flag"], "water wave": ["Beach", "Ocean Wave", "Sea", "Waves"], "last track button": ["Previous Track"], "pirate flag": ["Jolly Roger"], "fire": ["Flame", "Hot", "Lit", "Snapstreak"], "droplet": ["Water", "Water Drop"], "jack-o-lantern": ["Gourd", "Halloween", "Pumpkin"], "fireworks": ["Explosion"], "upwards button": ["Up Triangle"], "transgender flag": ["Blue, Pink, and White Flag", "Trans Flag"], "sparkler": ["Senko Hanabi"], "downwards button": ["Down Triangle"], "stop button": ["Stop"], "sparkles": ["Glitter", "Shiny"], "cinema": ["Cinema Screen", "Movies"], "pause button": ["Pause"], "party popper": ["Celebration", "Party Hat"], "dim button": ["Decrease Brightness"], "tanabata tree": ["Tanabata", "Wish Tree"], "firecracker": ["Dynamite"], "record button": ["Record"], "antenna bars": ["Reception Bars", "Signal Strength", "Stairs"], "vibration mode": ["Phone Heart", "Silent Mode"], "balloon": ["Party", "Red Balloon"], "carp streamer": ["Fish Flag", "Koinobori", "Wind Socks"], "bright button": ["Increase Brightness"], "wind chime": ["Furin", "Jellyfish", "Wind Bell"], "confetti ball": ["Confetti"], "police officer": ["Cop", "Police", "Policeman", "Policewoman"], "moon viewing ceremony": ["Grass, Dumplings and Moon", "Harvest Moon", "Mid-Autumn Festival", "Tsukimi"], "pine decoration": ["Bamboo", "Kadomatsu", "New Year Decoration"], "ribbon": ["Bow", "Pink Bow"], "person climbing": ["Climber", "Rock Climbing"], "male sign": ["Man Symbol", "Mars Symbol"], "transgender symbol": ["Transgender Sign"], "female sign": ["Venus Symbol", "Woman Symbol"], "wrapped gift": ["Birthday Present", "Christmas Present", "Gift", "Gift Box"], "red envelope": ["Ang Pao", "H\u00f3ngb\u0101o", "Lai See", "Red Packet"], "ticket": ["Ticket Stub", "World Tour TIcket"], "military medal": ["Medal", "Medallion", "Military Decoration"], "man climbing": ["Male Rock Climber", "Man Climber"], "trophy": ["Championship Trophy", "Winners Trophy"], "woman climbing": ["Female Rock Climber", "Woman Climber"], "2nd place medal": ["Silver Medal"], "infinity": ["Infinity"], "3rd place medal": ["Bronze Medal"], "soccer ball": ["Football", "Soccer"], "baseball": ["Softball"], "wavy dash": ["\u3030 Wave"], "basketball": ["Basketball", "Orange Ball"], "heavy dollar sign": ["Dollar", "Dollar Sign"], "horse racing": ["Horse Race", "Jockey"], "medical symbol": ["Aesculapius", "Asklepios", "Rod of Asclepius"], "recycling symbol": ["Recycle Logo"], "1st place medal": ["Gold Medal"], "detective": ["Detective", "Private Eye", "Sleuth", "Spy"], "rugby football": ["Football", "League", "Rugby", "Union"], "fleur-de-lis": ["New Orleans Saints", "Scouts"], "trident emblem": ["Pitchfork", "Trident"], "name badge": ["Fire Tag", "Name Tag", "Tofu On Fire"], "bowling": ["Bowling Ball", "Pins", "Skittles", "Ten Pin Bowling"], "cricket game": ["Cricket"], "hollow red circle": ["Circle", "Correct", "Red Circle"], "american football": ["Football", "Gridiron", "Superbowl"], "snowboarder": ["Snowboard", "Snowboarding"], "field hockey": ["Field Hockey", "Hockey"], "check box with check": ["Checkbox", "Check Mark In Box"], "tennis": ["Tennis", "Tennis Ball", "Tennis Racket", "Tennis Racquet"], "woman": ["Female", "Lady", "Yellow Woman"], "cross mark": ["Cross", "X"], "cross mark button": ["Cross", "X"], "check mark button": ["Green Check Mark", "Green Tick"], "ice hockey": ["Ice Hockey"], "check mark": ["Check", "Tick"], "ping pong": ["Ping Pong", "Table Tennis"], "martial arts uniform": ["Judo"], "curly loop": ["Curling Loop", "Loop"], "double curly loop": ["Double Curling Loop", "Voicemail"], "part alternation mark": ["M", "McDonald\u2019s"], "person golfing": ["Golf", "Golf Club"], "fishing pole": ["Fishing", "Fishing Rod"], "ice skate": ["Ice Skating"], "flag in hole": ["Golf", "Golf Flag"], "guard": ["British Guardsman", "Foot Guard"], "running shirt": ["Running Shirt", "Singlet"], "eight-pointed star": ["Orange Star"], "skis": ["Skiing", "Skis"], "trade mark": ["\u2122 TM", "\u2122 Trademark"], "yo-yo": ["Yoyo"], "crystal ball": ["Clairvoyant", "Fortune Teller", "Psychic", "Purple Crystal"], "pool 8 ball": ["8 Ball", "Cue Ball", "Magic 8 Ball", "Pool", "Snooker"], "video game": ["Gamepad", "Playstation", "Wii U", "Xbox"], "person surfing": ["Surf", "Surfing"], "game die": ["Dice"], "slot machine": ["Casino", "Fruit Machine", "Gambling", "Poker Machine"], "speaking head": ["Mansplaining", "Shout", "Shouting"], "busts in silhouette": ["Shadows", "Silhouettes", "Users"], "teddy bear": ["Toy"], "bust in silhouette": ["Shadow", "Silhouette", "User"], "input numbers": ["1234", "Numbers", "Numeric Input"], "input symbols": ["Symbols", "Symbol Input"], "input latin uppercase": ["ABCD", "Uppercase"], "monkey face": ["Monkey Head"], "nesting dolls": ["Matryoshka", "Russian Dolls"], "input latin lowercase": ["ABCD", "Lowercase"], "monkey": ["Cheeky Monkey"], "spade suit": ["Spades"], "heart suit": ["Card With Heart", "Hearts"], "mirror ball": ["Disco Ball"], "input latin letters": ["ABC", "Alphabet", "Letters"], "footprints": ["Feet", "Footsteps"], "dog face": ["Dog", "Puppy"], "club suit": ["Clubs"], "dog": ["Doggo"], "diamond suit": ["Diamonds"], "guide dog": ["Seeing Eye Dog"], "information": ["Info", "Lowercase I", "Tourist Information"], "joker": ["Joker", "Joker Card"], "poodle": ["Miniature Poodle", "Standard Poodle", "Toy Poodle"], "flower playing cards": ["Deck Of Cards", "Hanafuda", "Hwatu", "Playing Cards"], "construction worker": ["Builder", "Face With Hat", "Hard-Hat", "Safety Helmet"], "mahjong red dragon": ["Mahjong", "Mahjong Tile", "\u4e2d"], "performing arts": ["Drama Masks", "Greek Theatre Masks", "Theatre Logo", "Tragedy and Comedy Masks"], "fox": ["Fox"], "framed picture": ["Painting", "Picture Frame"], "artist palette": ["Art", "Painting"], "cat face": [":3", "Kitten", "Kitty"], "cat": ["Domestic Cat", "Feline", "Housecat"], "person rowing boat": ["Boat With Paddles", "Rowing"], "glasses": ["Glasses"], "tiger face": ["Cute Tiger"], "tiger": ["Bengal Tiger"], "leopard": ["African Leopard", "Jaguar"], "horse face": ["Horse Head"], "necktie": ["Business Shirt", "Shirt And Tie", "Tie"], "horse": ["Galloping Horse", "Racehorse"], "unicorn": ["Unicorn"], "jeans": ["Denim", "Pants", "Trousers"], "t-shirt": ["Polo Shirt", "Tee Shirt"], "deer": ["Buck", "Reindeer", "Stag"], "cow face": ["Cow", "Happy Cow"], "bison": ["buffalo"], "ox": ["Bull", "Bullock", "Oxen", "Steer"], "water buffalo": ["Buffalo", "Domestic Water Buffalo"], "person swimming": ["Swimming"], "pig face": ["Pig Head"], "dress": ["Gown", "Skirt"], "pig": ["Hog", "Sow"], "kimono": ["Dressing Gown", "Japanese Dress"], "sari": ["Saree", "Shari"], "cow": ["Dairy Cow"], "bikini": ["Bathers", "Swimsuit"], "pig nose": ["Pig Snout"], "ram": ["Sheep"], "ewe": ["Ewe", "Lamb"], "princess": ["Blonde Girl", "Girl With Crown", "Girl With Tiara"], "camel": ["Arabian Camel", "Dromedary Camel", "One-Bump Camel"], "purse": ["Wallet"], "two-hump camel": ["Asian Camel", "Bactrian Camel", "Two-Bump Camel"], "older person": ["Gender Neutral Older Adult"], "handbag": ["Women\u2019s Bag"], "boar": ["Warthog", "Wild Boar", "Wild Pig"], "men holding hands": ["Gay Couple"], "kiss": ["Couple Kissing", "Gender Neutral Couple Kissing"], "couple with heart": ["Couple In Love", "Gender Neutral Couple", "Loving Couple"]} \ No newline at end of file diff --git a/emoji/emoji-test.txt b/emoji/emoji-test.txt deleted file mode 100644 index 87d093d6..00000000 --- a/emoji/emoji-test.txt +++ /dev/null @@ -1,5024 +0,0 @@ -# emoji-test.txt -# Date: 2022-08-12, 20:24:39 GMT -# © 2022 Unicode®, Inc. -# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see https://www.unicode.org/terms_of_use.html -# -# Emoji Keyboard/Display Test Data for UTS #51 -# Version: 15.0 -# -# For documentation and usage, see https://www.unicode.org/reports/tr51 -# -# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. -# Format: code points; status # emoji name -# Code points — list of one or more hex code points, separated by spaces -# Status -# component — an Emoji_Component, -# excluding Regional_Indicators, ASCII, and non-Emoji. -# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51), -# excluding Emoji_Component -# minimally-qualified — a minimally-qualified emoji (see ED-18a in UTS #51) -# unqualified — a unqualified emoji (See ED-19 in UTS #51) -# Notes: -# • This includes the emoji components that need emoji presentation (skin tone and hair) -# when isolated, but omits the components that need not have an emoji -# presentation when isolated. -# • The RGI set is covered by the listed fully-qualified emoji. -# • The listed minimally-qualified and unqualified cover all cases where an -# element of the RGI set is missing one or more emoji presentation selectors. -# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. -# • The groups and subgroups are illustrative. See the Emoji Order chart for more information. - - -# group: Smileys & Emotion - -# subgroup: face-smiling -1F600 ; fully-qualified # 😀 E1.0 grinning face -1F603 ; fully-qualified # 😃 E0.6 grinning face with big eyes -1F604 ; fully-qualified # 😄 E0.6 grinning face with smiling eyes -1F601 ; fully-qualified # 😁 E0.6 beaming face with smiling eyes -1F606 ; fully-qualified # 😆 E0.6 grinning squinting face -1F605 ; fully-qualified # 😅 E0.6 grinning face with sweat -1F923 ; fully-qualified # 🤣 E3.0 rolling on the floor laughing -1F602 ; fully-qualified # 😂 E0.6 face with tears of joy -1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face -1F643 ; fully-qualified # 🙃 E1.0 upside-down face -1FAE0 ; fully-qualified # 🫠 E14.0 melting face -1F609 ; fully-qualified # 😉 E0.6 winking face -1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes -1F607 ; fully-qualified # 😇 E1.0 smiling face with halo - -# subgroup: face-affection -1F970 ; fully-qualified # 🥰 E11.0 smiling face with hearts -1F60D ; fully-qualified # 😍 E0.6 smiling face with heart-eyes -1F929 ; fully-qualified # 🤩 E5.0 star-struck -1F618 ; fully-qualified # 😘 E0.6 face blowing a kiss -1F617 ; fully-qualified # 😗 E1.0 kissing face -263A FE0F ; fully-qualified # ☺️ E0.6 smiling face -263A ; unqualified # ☺ E0.6 smiling face -1F61A ; fully-qualified # 😚 E0.6 kissing face with closed eyes -1F619 ; fully-qualified # 😙 E1.0 kissing face with smiling eyes -1F972 ; fully-qualified # 🥲 E13.0 smiling face with tear - -# subgroup: face-tongue -1F60B ; fully-qualified # 😋 E0.6 face savoring food -1F61B ; fully-qualified # 😛 E1.0 face with tongue -1F61C ; fully-qualified # 😜 E0.6 winking face with tongue -1F92A ; fully-qualified # 🤪 E5.0 zany face -1F61D ; fully-qualified # 😝 E0.6 squinting face with tongue -1F911 ; fully-qualified # 🤑 E1.0 money-mouth face - -# subgroup: face-hand -1F917 ; fully-qualified # 🤗 E1.0 smiling face with open hands -1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth -1FAE2 ; fully-qualified # 🫢 E14.0 face with open eyes and hand over mouth -1FAE3 ; fully-qualified # 🫣 E14.0 face with peeking eye -1F92B ; fully-qualified # 🤫 E5.0 shushing face -1F914 ; fully-qualified # 🤔 E1.0 thinking face -1FAE1 ; fully-qualified # 🫡 E14.0 saluting face - -# subgroup: face-neutral-skeptical -1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face -1F928 ; fully-qualified # 🤨 E5.0 face with raised eyebrow -1F610 ; fully-qualified # 😐 E0.7 neutral face -1F611 ; fully-qualified # 😑 E1.0 expressionless face -1F636 ; fully-qualified # 😶 E1.0 face without mouth -1FAE5 ; fully-qualified # 🫥 E14.0 dotted line face -1F636 200D 1F32B FE0F ; fully-qualified # 😶‍🌫️ E13.1 face in clouds -1F636 200D 1F32B ; minimally-qualified # 😶‍🌫 E13.1 face in clouds -1F60F ; fully-qualified # 😏 E0.6 smirking face -1F612 ; fully-qualified # 😒 E0.6 unamused face -1F644 ; fully-qualified # 🙄 E1.0 face with rolling eyes -1F62C ; fully-qualified # 😬 E1.0 grimacing face -1F62E 200D 1F4A8 ; fully-qualified # 😮‍💨 E13.1 face exhaling -1F925 ; fully-qualified # 🤥 E3.0 lying face -1FAE8 ; fully-qualified # 🫨 E15.0 shaking face - -# subgroup: face-sleepy -1F60C ; fully-qualified # 😌 E0.6 relieved face -1F614 ; fully-qualified # 😔 E0.6 pensive face -1F62A ; fully-qualified # 😪 E0.6 sleepy face -1F924 ; fully-qualified # 🤤 E3.0 drooling face -1F634 ; fully-qualified # 😴 E1.0 sleeping face - -# subgroup: face-unwell -1F637 ; fully-qualified # 😷 E0.6 face with medical mask -1F912 ; fully-qualified # 🤒 E1.0 face with thermometer -1F915 ; fully-qualified # 🤕 E1.0 face with head-bandage -1F922 ; fully-qualified # 🤢 E3.0 nauseated face -1F92E ; fully-qualified # 🤮 E5.0 face vomiting -1F927 ; fully-qualified # 🤧 E3.0 sneezing face -1F975 ; fully-qualified # 🥵 E11.0 hot face -1F976 ; fully-qualified # 🥶 E11.0 cold face -1F974 ; fully-qualified # 🥴 E11.0 woozy face -1F635 ; fully-qualified # 😵 E0.6 face with crossed-out eyes -1F635 200D 1F4AB ; fully-qualified # 😵‍💫 E13.1 face with spiral eyes -1F92F ; fully-qualified # 🤯 E5.0 exploding head - -# subgroup: face-hat -1F920 ; fully-qualified # 🤠 E3.0 cowboy hat face -1F973 ; fully-qualified # 🥳 E11.0 partying face -1F978 ; fully-qualified # 🥸 E13.0 disguised face - -# subgroup: face-glasses -1F60E ; fully-qualified # 😎 E1.0 smiling face with sunglasses -1F913 ; fully-qualified # 🤓 E1.0 nerd face -1F9D0 ; fully-qualified # 🧐 E5.0 face with monocle - -# subgroup: face-concerned -1F615 ; fully-qualified # 😕 E1.0 confused face -1FAE4 ; fully-qualified # 🫤 E14.0 face with diagonal mouth -1F61F ; fully-qualified # 😟 E1.0 worried face -1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face -2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face -2639 ; unqualified # ☹ E0.7 frowning face -1F62E ; fully-qualified # 😮 E1.0 face with open mouth -1F62F ; fully-qualified # 😯 E1.0 hushed face -1F632 ; fully-qualified # 😲 E0.6 astonished face -1F633 ; fully-qualified # 😳 E0.6 flushed face -1F97A ; fully-qualified # 🥺 E11.0 pleading face -1F979 ; fully-qualified # 🥹 E14.0 face holding back tears -1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth -1F627 ; fully-qualified # 😧 E1.0 anguished face -1F628 ; fully-qualified # 😨 E0.6 fearful face -1F630 ; fully-qualified # 😰 E0.6 anxious face with sweat -1F625 ; fully-qualified # 😥 E0.6 sad but relieved face -1F622 ; fully-qualified # 😢 E0.6 crying face -1F62D ; fully-qualified # 😭 E0.6 loudly crying face -1F631 ; fully-qualified # 😱 E0.6 face screaming in fear -1F616 ; fully-qualified # 😖 E0.6 confounded face -1F623 ; fully-qualified # 😣 E0.6 persevering face -1F61E ; fully-qualified # 😞 E0.6 disappointed face -1F613 ; fully-qualified # 😓 E0.6 downcast face with sweat -1F629 ; fully-qualified # 😩 E0.6 weary face -1F62B ; fully-qualified # 😫 E0.6 tired face -1F971 ; fully-qualified # 🥱 E12.0 yawning face - -# subgroup: face-negative -1F624 ; fully-qualified # 😤 E0.6 face with steam from nose -1F621 ; fully-qualified # 😡 E0.6 enraged face -1F620 ; fully-qualified # 😠 E0.6 angry face -1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth -1F608 ; fully-qualified # 😈 E1.0 smiling face with horns -1F47F ; fully-qualified # 👿 E0.6 angry face with horns -1F480 ; fully-qualified # 💀 E0.6 skull -2620 FE0F ; fully-qualified # ☠️ E1.0 skull and crossbones -2620 ; unqualified # ☠ E1.0 skull and crossbones - -# subgroup: face-costume -1F4A9 ; fully-qualified # 💩 E0.6 pile of poo -1F921 ; fully-qualified # 🤡 E3.0 clown face -1F479 ; fully-qualified # 👹 E0.6 ogre -1F47A ; fully-qualified # 👺 E0.6 goblin -1F47B ; fully-qualified # 👻 E0.6 ghost -1F47D ; fully-qualified # 👽 E0.6 alien -1F47E ; fully-qualified # 👾 E0.6 alien monster -1F916 ; fully-qualified # 🤖 E1.0 robot - -# subgroup: cat-face -1F63A ; fully-qualified # 😺 E0.6 grinning cat -1F638 ; fully-qualified # 😸 E0.6 grinning cat with smiling eyes -1F639 ; fully-qualified # 😹 E0.6 cat with tears of joy -1F63B ; fully-qualified # 😻 E0.6 smiling cat with heart-eyes -1F63C ; fully-qualified # 😼 E0.6 cat with wry smile -1F63D ; fully-qualified # 😽 E0.6 kissing cat -1F640 ; fully-qualified # 🙀 E0.6 weary cat -1F63F ; fully-qualified # 😿 E0.6 crying cat -1F63E ; fully-qualified # 😾 E0.6 pouting cat - -# subgroup: monkey-face -1F648 ; fully-qualified # 🙈 E0.6 see-no-evil monkey -1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey -1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey - -# subgroup: heart -1F48C ; fully-qualified # 💌 E0.6 love letter -1F498 ; fully-qualified # 💘 E0.6 heart with arrow -1F49D ; fully-qualified # 💝 E0.6 heart with ribbon -1F496 ; fully-qualified # 💖 E0.6 sparkling heart -1F497 ; fully-qualified # 💗 E0.6 growing heart -1F493 ; fully-qualified # 💓 E0.6 beating heart -1F49E ; fully-qualified # 💞 E0.6 revolving hearts -1F495 ; fully-qualified # 💕 E0.6 two hearts -1F49F ; fully-qualified # 💟 E0.6 heart decoration -2763 FE0F ; fully-qualified # ❣️ E1.0 heart exclamation -2763 ; unqualified # ❣ E1.0 heart exclamation -1F494 ; fully-qualified # 💔 E0.6 broken heart -2764 FE0F 200D 1F525 ; fully-qualified # ❤️‍🔥 E13.1 heart on fire -2764 200D 1F525 ; unqualified # ❤‍🔥 E13.1 heart on fire -2764 FE0F 200D 1FA79 ; fully-qualified # ❤️‍🩹 E13.1 mending heart -2764 200D 1FA79 ; unqualified # ❤‍🩹 E13.1 mending heart -2764 FE0F ; fully-qualified # ❤️ E0.6 red heart -2764 ; unqualified # ❤ E0.6 red heart -1FA77 ; fully-qualified # 🩷 E15.0 pink heart -1F9E1 ; fully-qualified # 🧡 E5.0 orange heart -1F49B ; fully-qualified # 💛 E0.6 yellow heart -1F49A ; fully-qualified # 💚 E0.6 green heart -1F499 ; fully-qualified # 💙 E0.6 blue heart -1FA75 ; fully-qualified # 🩵 E15.0 light blue heart -1F49C ; fully-qualified # 💜 E0.6 purple heart -1F90E ; fully-qualified # 🤎 E12.0 brown heart -1F5A4 ; fully-qualified # 🖤 E3.0 black heart -1FA76 ; fully-qualified # 🩶 E15.0 grey heart -1F90D ; fully-qualified # 🤍 E12.0 white heart - -# subgroup: emotion -1F48B ; fully-qualified # 💋 E0.6 kiss mark -1F4AF ; fully-qualified # 💯 E0.6 hundred points -1F4A2 ; fully-qualified # 💢 E0.6 anger symbol -1F4A5 ; fully-qualified # 💥 E0.6 collision -1F4AB ; fully-qualified # 💫 E0.6 dizzy -1F4A6 ; fully-qualified # 💦 E0.6 sweat droplets -1F4A8 ; fully-qualified # 💨 E0.6 dashing away -1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole -1F573 ; unqualified # 🕳 E0.7 hole -1F4AC ; fully-qualified # 💬 E0.6 speech balloon -1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ E2.0 eye in speech bubble -1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ E2.0 eye in speech bubble -1F441 FE0F 200D 1F5E8 ; minimally-qualified # 👁️‍🗨 E2.0 eye in speech bubble -1F441 200D 1F5E8 ; unqualified # 👁‍🗨 E2.0 eye in speech bubble -1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble -1F5E8 ; unqualified # 🗨 E2.0 left speech bubble -1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble -1F5EF ; unqualified # 🗯 E0.7 right anger bubble -1F4AD ; fully-qualified # 💭 E1.0 thought balloon -1F4A4 ; fully-qualified # 💤 E0.6 ZZZ - -# Smileys & Emotion subtotal: 180 -# Smileys & Emotion subtotal: 180 w/o modifiers - -# group: People & Body - -# subgroup: hand-fingers-open -1F44B ; fully-qualified # 👋 E0.6 waving hand -1F44B 1F3FB ; fully-qualified # 👋🏻 E1.0 waving hand: light skin tone -1F44B 1F3FC ; fully-qualified # 👋🏼 E1.0 waving hand: medium-light skin tone -1F44B 1F3FD ; fully-qualified # 👋🏽 E1.0 waving hand: medium skin tone -1F44B 1F3FE ; fully-qualified # 👋🏾 E1.0 waving hand: medium-dark skin tone -1F44B 1F3FF ; fully-qualified # 👋🏿 E1.0 waving hand: dark skin tone -1F91A ; fully-qualified # 🤚 E3.0 raised back of hand -1F91A 1F3FB ; fully-qualified # 🤚🏻 E3.0 raised back of hand: light skin tone -1F91A 1F3FC ; fully-qualified # 🤚🏼 E3.0 raised back of hand: medium-light skin tone -1F91A 1F3FD ; fully-qualified # 🤚🏽 E3.0 raised back of hand: medium skin tone -1F91A 1F3FE ; fully-qualified # 🤚🏾 E3.0 raised back of hand: medium-dark skin tone -1F91A 1F3FF ; fully-qualified # 🤚🏿 E3.0 raised back of hand: dark skin tone -1F590 FE0F ; fully-qualified # 🖐️ E0.7 hand with fingers splayed -1F590 ; unqualified # 🖐 E0.7 hand with fingers splayed -1F590 1F3FB ; fully-qualified # 🖐🏻 E1.0 hand with fingers splayed: light skin tone -1F590 1F3FC ; fully-qualified # 🖐🏼 E1.0 hand with fingers splayed: medium-light skin tone -1F590 1F3FD ; fully-qualified # 🖐🏽 E1.0 hand with fingers splayed: medium skin tone -1F590 1F3FE ; fully-qualified # 🖐🏾 E1.0 hand with fingers splayed: medium-dark skin tone -1F590 1F3FF ; fully-qualified # 🖐🏿 E1.0 hand with fingers splayed: dark skin tone -270B ; fully-qualified # ✋ E0.6 raised hand -270B 1F3FB ; fully-qualified # ✋🏻 E1.0 raised hand: light skin tone -270B 1F3FC ; fully-qualified # ✋🏼 E1.0 raised hand: medium-light skin tone -270B 1F3FD ; fully-qualified # ✋🏽 E1.0 raised hand: medium skin tone -270B 1F3FE ; fully-qualified # ✋🏾 E1.0 raised hand: medium-dark skin tone -270B 1F3FF ; fully-qualified # ✋🏿 E1.0 raised hand: dark skin tone -1F596 ; fully-qualified # 🖖 E1.0 vulcan salute -1F596 1F3FB ; fully-qualified # 🖖🏻 E1.0 vulcan salute: light skin tone -1F596 1F3FC ; fully-qualified # 🖖🏼 E1.0 vulcan salute: medium-light skin tone -1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone -1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone -1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone -1FAF1 ; fully-qualified # 🫱 E14.0 rightwards hand -1FAF1 1F3FB ; fully-qualified # 🫱🏻 E14.0 rightwards hand: light skin tone -1FAF1 1F3FC ; fully-qualified # 🫱🏼 E14.0 rightwards hand: medium-light skin tone -1FAF1 1F3FD ; fully-qualified # 🫱🏽 E14.0 rightwards hand: medium skin tone -1FAF1 1F3FE ; fully-qualified # 🫱🏾 E14.0 rightwards hand: medium-dark skin tone -1FAF1 1F3FF ; fully-qualified # 🫱🏿 E14.0 rightwards hand: dark skin tone -1FAF2 ; fully-qualified # 🫲 E14.0 leftwards hand -1FAF2 1F3FB ; fully-qualified # 🫲🏻 E14.0 leftwards hand: light skin tone -1FAF2 1F3FC ; fully-qualified # 🫲🏼 E14.0 leftwards hand: medium-light skin tone -1FAF2 1F3FD ; fully-qualified # 🫲🏽 E14.0 leftwards hand: medium skin tone -1FAF2 1F3FE ; fully-qualified # 🫲🏾 E14.0 leftwards hand: medium-dark skin tone -1FAF2 1F3FF ; fully-qualified # 🫲🏿 E14.0 leftwards hand: dark skin tone -1FAF3 ; fully-qualified # 🫳 E14.0 palm down hand -1FAF3 1F3FB ; fully-qualified # 🫳🏻 E14.0 palm down hand: light skin tone -1FAF3 1F3FC ; fully-qualified # 🫳🏼 E14.0 palm down hand: medium-light skin tone -1FAF3 1F3FD ; fully-qualified # 🫳🏽 E14.0 palm down hand: medium skin tone -1FAF3 1F3FE ; fully-qualified # 🫳🏾 E14.0 palm down hand: medium-dark skin tone -1FAF3 1F3FF ; fully-qualified # 🫳🏿 E14.0 palm down hand: dark skin tone -1FAF4 ; fully-qualified # 🫴 E14.0 palm up hand -1FAF4 1F3FB ; fully-qualified # 🫴🏻 E14.0 palm up hand: light skin tone -1FAF4 1F3FC ; fully-qualified # 🫴🏼 E14.0 palm up hand: medium-light skin tone -1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone -1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone -1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone -1FAF7 ; fully-qualified # 🫷 E15.0 leftwards pushing hand -1FAF7 1F3FB ; fully-qualified # 🫷🏻 E15.0 leftwards pushing hand: light skin tone -1FAF7 1F3FC ; fully-qualified # 🫷🏼 E15.0 leftwards pushing hand: medium-light skin tone -1FAF7 1F3FD ; fully-qualified # 🫷🏽 E15.0 leftwards pushing hand: medium skin tone -1FAF7 1F3FE ; fully-qualified # 🫷🏾 E15.0 leftwards pushing hand: medium-dark skin tone -1FAF7 1F3FF ; fully-qualified # 🫷🏿 E15.0 leftwards pushing hand: dark skin tone -1FAF8 ; fully-qualified # 🫸 E15.0 rightwards pushing hand -1FAF8 1F3FB ; fully-qualified # 🫸🏻 E15.0 rightwards pushing hand: light skin tone -1FAF8 1F3FC ; fully-qualified # 🫸🏼 E15.0 rightwards pushing hand: medium-light skin tone -1FAF8 1F3FD ; fully-qualified # 🫸🏽 E15.0 rightwards pushing hand: medium skin tone -1FAF8 1F3FE ; fully-qualified # 🫸🏾 E15.0 rightwards pushing hand: medium-dark skin tone -1FAF8 1F3FF ; fully-qualified # 🫸🏿 E15.0 rightwards pushing hand: dark skin tone - -# subgroup: hand-fingers-partial -1F44C ; fully-qualified # 👌 E0.6 OK hand -1F44C 1F3FB ; fully-qualified # 👌🏻 E1.0 OK hand: light skin tone -1F44C 1F3FC ; fully-qualified # 👌🏼 E1.0 OK hand: medium-light skin tone -1F44C 1F3FD ; fully-qualified # 👌🏽 E1.0 OK hand: medium skin tone -1F44C 1F3FE ; fully-qualified # 👌🏾 E1.0 OK hand: medium-dark skin tone -1F44C 1F3FF ; fully-qualified # 👌🏿 E1.0 OK hand: dark skin tone -1F90C ; fully-qualified # 🤌 E13.0 pinched fingers -1F90C 1F3FB ; fully-qualified # 🤌🏻 E13.0 pinched fingers: light skin tone -1F90C 1F3FC ; fully-qualified # 🤌🏼 E13.0 pinched fingers: medium-light skin tone -1F90C 1F3FD ; fully-qualified # 🤌🏽 E13.0 pinched fingers: medium skin tone -1F90C 1F3FE ; fully-qualified # 🤌🏾 E13.0 pinched fingers: medium-dark skin tone -1F90C 1F3FF ; fully-qualified # 🤌🏿 E13.0 pinched fingers: dark skin tone -1F90F ; fully-qualified # 🤏 E12.0 pinching hand -1F90F 1F3FB ; fully-qualified # 🤏🏻 E12.0 pinching hand: light skin tone -1F90F 1F3FC ; fully-qualified # 🤏🏼 E12.0 pinching hand: medium-light skin tone -1F90F 1F3FD ; fully-qualified # 🤏🏽 E12.0 pinching hand: medium skin tone -1F90F 1F3FE ; fully-qualified # 🤏🏾 E12.0 pinching hand: medium-dark skin tone -1F90F 1F3FF ; fully-qualified # 🤏🏿 E12.0 pinching hand: dark skin tone -270C FE0F ; fully-qualified # ✌️ E0.6 victory hand -270C ; unqualified # ✌ E0.6 victory hand -270C 1F3FB ; fully-qualified # ✌🏻 E1.0 victory hand: light skin tone -270C 1F3FC ; fully-qualified # ✌🏼 E1.0 victory hand: medium-light skin tone -270C 1F3FD ; fully-qualified # ✌🏽 E1.0 victory hand: medium skin tone -270C 1F3FE ; fully-qualified # ✌🏾 E1.0 victory hand: medium-dark skin tone -270C 1F3FF ; fully-qualified # ✌🏿 E1.0 victory hand: dark skin tone -1F91E ; fully-qualified # 🤞 E3.0 crossed fingers -1F91E 1F3FB ; fully-qualified # 🤞🏻 E3.0 crossed fingers: light skin tone -1F91E 1F3FC ; fully-qualified # 🤞🏼 E3.0 crossed fingers: medium-light skin tone -1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone -1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone -1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone -1FAF0 ; fully-qualified # 🫰 E14.0 hand with index finger and thumb crossed -1FAF0 1F3FB ; fully-qualified # 🫰🏻 E14.0 hand with index finger and thumb crossed: light skin tone -1FAF0 1F3FC ; fully-qualified # 🫰🏼 E14.0 hand with index finger and thumb crossed: medium-light skin tone -1FAF0 1F3FD ; fully-qualified # 🫰🏽 E14.0 hand with index finger and thumb crossed: medium skin tone -1FAF0 1F3FE ; fully-qualified # 🫰🏾 E14.0 hand with index finger and thumb crossed: medium-dark skin tone -1FAF0 1F3FF ; fully-qualified # 🫰🏿 E14.0 hand with index finger and thumb crossed: dark skin tone -1F91F ; fully-qualified # 🤟 E5.0 love-you gesture -1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone -1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone -1F91F 1F3FD ; fully-qualified # 🤟🏽 E5.0 love-you gesture: medium skin tone -1F91F 1F3FE ; fully-qualified # 🤟🏾 E5.0 love-you gesture: medium-dark skin tone -1F91F 1F3FF ; fully-qualified # 🤟🏿 E5.0 love-you gesture: dark skin tone -1F918 ; fully-qualified # 🤘 E1.0 sign of the horns -1F918 1F3FB ; fully-qualified # 🤘🏻 E1.0 sign of the horns: light skin tone -1F918 1F3FC ; fully-qualified # 🤘🏼 E1.0 sign of the horns: medium-light skin tone -1F918 1F3FD ; fully-qualified # 🤘🏽 E1.0 sign of the horns: medium skin tone -1F918 1F3FE ; fully-qualified # 🤘🏾 E1.0 sign of the horns: medium-dark skin tone -1F918 1F3FF ; fully-qualified # 🤘🏿 E1.0 sign of the horns: dark skin tone -1F919 ; fully-qualified # 🤙 E3.0 call me hand -1F919 1F3FB ; fully-qualified # 🤙🏻 E3.0 call me hand: light skin tone -1F919 1F3FC ; fully-qualified # 🤙🏼 E3.0 call me hand: medium-light skin tone -1F919 1F3FD ; fully-qualified # 🤙🏽 E3.0 call me hand: medium skin tone -1F919 1F3FE ; fully-qualified # 🤙🏾 E3.0 call me hand: medium-dark skin tone -1F919 1F3FF ; fully-qualified # 🤙🏿 E3.0 call me hand: dark skin tone - -# subgroup: hand-single-finger -1F448 ; fully-qualified # 👈 E0.6 backhand index pointing left -1F448 1F3FB ; fully-qualified # 👈🏻 E1.0 backhand index pointing left: light skin tone -1F448 1F3FC ; fully-qualified # 👈🏼 E1.0 backhand index pointing left: medium-light skin tone -1F448 1F3FD ; fully-qualified # 👈🏽 E1.0 backhand index pointing left: medium skin tone -1F448 1F3FE ; fully-qualified # 👈🏾 E1.0 backhand index pointing left: medium-dark skin tone -1F448 1F3FF ; fully-qualified # 👈🏿 E1.0 backhand index pointing left: dark skin tone -1F449 ; fully-qualified # 👉 E0.6 backhand index pointing right -1F449 1F3FB ; fully-qualified # 👉🏻 E1.0 backhand index pointing right: light skin tone -1F449 1F3FC ; fully-qualified # 👉🏼 E1.0 backhand index pointing right: medium-light skin tone -1F449 1F3FD ; fully-qualified # 👉🏽 E1.0 backhand index pointing right: medium skin tone -1F449 1F3FE ; fully-qualified # 👉🏾 E1.0 backhand index pointing right: medium-dark skin tone -1F449 1F3FF ; fully-qualified # 👉🏿 E1.0 backhand index pointing right: dark skin tone -1F446 ; fully-qualified # 👆 E0.6 backhand index pointing up -1F446 1F3FB ; fully-qualified # 👆🏻 E1.0 backhand index pointing up: light skin tone -1F446 1F3FC ; fully-qualified # 👆🏼 E1.0 backhand index pointing up: medium-light skin tone -1F446 1F3FD ; fully-qualified # 👆🏽 E1.0 backhand index pointing up: medium skin tone -1F446 1F3FE ; fully-qualified # 👆🏾 E1.0 backhand index pointing up: medium-dark skin tone -1F446 1F3FF ; fully-qualified # 👆🏿 E1.0 backhand index pointing up: dark skin tone -1F595 ; fully-qualified # 🖕 E1.0 middle finger -1F595 1F3FB ; fully-qualified # 🖕🏻 E1.0 middle finger: light skin tone -1F595 1F3FC ; fully-qualified # 🖕🏼 E1.0 middle finger: medium-light skin tone -1F595 1F3FD ; fully-qualified # 🖕🏽 E1.0 middle finger: medium skin tone -1F595 1F3FE ; fully-qualified # 🖕🏾 E1.0 middle finger: medium-dark skin tone -1F595 1F3FF ; fully-qualified # 🖕🏿 E1.0 middle finger: dark skin tone -1F447 ; fully-qualified # 👇 E0.6 backhand index pointing down -1F447 1F3FB ; fully-qualified # 👇🏻 E1.0 backhand index pointing down: light skin tone -1F447 1F3FC ; fully-qualified # 👇🏼 E1.0 backhand index pointing down: medium-light skin tone -1F447 1F3FD ; fully-qualified # 👇🏽 E1.0 backhand index pointing down: medium skin tone -1F447 1F3FE ; fully-qualified # 👇🏾 E1.0 backhand index pointing down: medium-dark skin tone -1F447 1F3FF ; fully-qualified # 👇🏿 E1.0 backhand index pointing down: dark skin tone -261D FE0F ; fully-qualified # ☝️ E0.6 index pointing up -261D ; unqualified # ☝ E0.6 index pointing up -261D 1F3FB ; fully-qualified # ☝🏻 E1.0 index pointing up: light skin tone -261D 1F3FC ; fully-qualified # ☝🏼 E1.0 index pointing up: medium-light skin tone -261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone -261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone -261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone -1FAF5 ; fully-qualified # 🫵 E14.0 index pointing at the viewer -1FAF5 1F3FB ; fully-qualified # 🫵🏻 E14.0 index pointing at the viewer: light skin tone -1FAF5 1F3FC ; fully-qualified # 🫵🏼 E14.0 index pointing at the viewer: medium-light skin tone -1FAF5 1F3FD ; fully-qualified # 🫵🏽 E14.0 index pointing at the viewer: medium skin tone -1FAF5 1F3FE ; fully-qualified # 🫵🏾 E14.0 index pointing at the viewer: medium-dark skin tone -1FAF5 1F3FF ; fully-qualified # 🫵🏿 E14.0 index pointing at the viewer: dark skin tone - -# subgroup: hand-fingers-closed -1F44D ; fully-qualified # 👍 E0.6 thumbs up -1F44D 1F3FB ; fully-qualified # 👍🏻 E1.0 thumbs up: light skin tone -1F44D 1F3FC ; fully-qualified # 👍🏼 E1.0 thumbs up: medium-light skin tone -1F44D 1F3FD ; fully-qualified # 👍🏽 E1.0 thumbs up: medium skin tone -1F44D 1F3FE ; fully-qualified # 👍🏾 E1.0 thumbs up: medium-dark skin tone -1F44D 1F3FF ; fully-qualified # 👍🏿 E1.0 thumbs up: dark skin tone -1F44E ; fully-qualified # 👎 E0.6 thumbs down -1F44E 1F3FB ; fully-qualified # 👎🏻 E1.0 thumbs down: light skin tone -1F44E 1F3FC ; fully-qualified # 👎🏼 E1.0 thumbs down: medium-light skin tone -1F44E 1F3FD ; fully-qualified # 👎🏽 E1.0 thumbs down: medium skin tone -1F44E 1F3FE ; fully-qualified # 👎🏾 E1.0 thumbs down: medium-dark skin tone -1F44E 1F3FF ; fully-qualified # 👎🏿 E1.0 thumbs down: dark skin tone -270A ; fully-qualified # ✊ E0.6 raised fist -270A 1F3FB ; fully-qualified # ✊🏻 E1.0 raised fist: light skin tone -270A 1F3FC ; fully-qualified # ✊🏼 E1.0 raised fist: medium-light skin tone -270A 1F3FD ; fully-qualified # ✊🏽 E1.0 raised fist: medium skin tone -270A 1F3FE ; fully-qualified # ✊🏾 E1.0 raised fist: medium-dark skin tone -270A 1F3FF ; fully-qualified # ✊🏿 E1.0 raised fist: dark skin tone -1F44A ; fully-qualified # 👊 E0.6 oncoming fist -1F44A 1F3FB ; fully-qualified # 👊🏻 E1.0 oncoming fist: light skin tone -1F44A 1F3FC ; fully-qualified # 👊🏼 E1.0 oncoming fist: medium-light skin tone -1F44A 1F3FD ; fully-qualified # 👊🏽 E1.0 oncoming fist: medium skin tone -1F44A 1F3FE ; fully-qualified # 👊🏾 E1.0 oncoming fist: medium-dark skin tone -1F44A 1F3FF ; fully-qualified # 👊🏿 E1.0 oncoming fist: dark skin tone -1F91B ; fully-qualified # 🤛 E3.0 left-facing fist -1F91B 1F3FB ; fully-qualified # 🤛🏻 E3.0 left-facing fist: light skin tone -1F91B 1F3FC ; fully-qualified # 🤛🏼 E3.0 left-facing fist: medium-light skin tone -1F91B 1F3FD ; fully-qualified # 🤛🏽 E3.0 left-facing fist: medium skin tone -1F91B 1F3FE ; fully-qualified # 🤛🏾 E3.0 left-facing fist: medium-dark skin tone -1F91B 1F3FF ; fully-qualified # 🤛🏿 E3.0 left-facing fist: dark skin tone -1F91C ; fully-qualified # 🤜 E3.0 right-facing fist -1F91C 1F3FB ; fully-qualified # 🤜🏻 E3.0 right-facing fist: light skin tone -1F91C 1F3FC ; fully-qualified # 🤜🏼 E3.0 right-facing fist: medium-light skin tone -1F91C 1F3FD ; fully-qualified # 🤜🏽 E3.0 right-facing fist: medium skin tone -1F91C 1F3FE ; fully-qualified # 🤜🏾 E3.0 right-facing fist: medium-dark skin tone -1F91C 1F3FF ; fully-qualified # 🤜🏿 E3.0 right-facing fist: dark skin tone - -# subgroup: hands -1F44F ; fully-qualified # 👏 E0.6 clapping hands -1F44F 1F3FB ; fully-qualified # 👏🏻 E1.0 clapping hands: light skin tone -1F44F 1F3FC ; fully-qualified # 👏🏼 E1.0 clapping hands: medium-light skin tone -1F44F 1F3FD ; fully-qualified # 👏🏽 E1.0 clapping hands: medium skin tone -1F44F 1F3FE ; fully-qualified # 👏🏾 E1.0 clapping hands: medium-dark skin tone -1F44F 1F3FF ; fully-qualified # 👏🏿 E1.0 clapping hands: dark skin tone -1F64C ; fully-qualified # 🙌 E0.6 raising hands -1F64C 1F3FB ; fully-qualified # 🙌🏻 E1.0 raising hands: light skin tone -1F64C 1F3FC ; fully-qualified # 🙌🏼 E1.0 raising hands: medium-light skin tone -1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone -1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone -1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone -1FAF6 ; fully-qualified # 🫶 E14.0 heart hands -1FAF6 1F3FB ; fully-qualified # 🫶🏻 E14.0 heart hands: light skin tone -1FAF6 1F3FC ; fully-qualified # 🫶🏼 E14.0 heart hands: medium-light skin tone -1FAF6 1F3FD ; fully-qualified # 🫶🏽 E14.0 heart hands: medium skin tone -1FAF6 1F3FE ; fully-qualified # 🫶🏾 E14.0 heart hands: medium-dark skin tone -1FAF6 1F3FF ; fully-qualified # 🫶🏿 E14.0 heart hands: dark skin tone -1F450 ; fully-qualified # 👐 E0.6 open hands -1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone -1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone -1F450 1F3FD ; fully-qualified # 👐🏽 E1.0 open hands: medium skin tone -1F450 1F3FE ; fully-qualified # 👐🏾 E1.0 open hands: medium-dark skin tone -1F450 1F3FF ; fully-qualified # 👐🏿 E1.0 open hands: dark skin tone -1F932 ; fully-qualified # 🤲 E5.0 palms up together -1F932 1F3FB ; fully-qualified # 🤲🏻 E5.0 palms up together: light skin tone -1F932 1F3FC ; fully-qualified # 🤲🏼 E5.0 palms up together: medium-light skin tone -1F932 1F3FD ; fully-qualified # 🤲🏽 E5.0 palms up together: medium skin tone -1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone -1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone -1F91D ; fully-qualified # 🤝 E3.0 handshake -1F91D 1F3FB ; fully-qualified # 🤝🏻 E14.0 handshake: light skin tone -1F91D 1F3FC ; fully-qualified # 🤝🏼 E14.0 handshake: medium-light skin tone -1F91D 1F3FD ; fully-qualified # 🤝🏽 E14.0 handshake: medium skin tone -1F91D 1F3FE ; fully-qualified # 🤝🏾 E14.0 handshake: medium-dark skin tone -1F91D 1F3FF ; fully-qualified # 🤝🏿 E14.0 handshake: dark skin tone -1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻‍🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone -1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻‍🫲🏽 E14.0 handshake: light skin tone, medium skin tone -1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻‍🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone -1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏻‍🫲🏿 E14.0 handshake: light skin tone, dark skin tone -1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏼‍🫲🏻 E14.0 handshake: medium-light skin tone, light skin tone -1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏼‍🫲🏽 E14.0 handshake: medium-light skin tone, medium skin tone -1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏼‍🫲🏾 E14.0 handshake: medium-light skin tone, medium-dark skin tone -1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏼‍🫲🏿 E14.0 handshake: medium-light skin tone, dark skin tone -1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏽‍🫲🏻 E14.0 handshake: medium skin tone, light skin tone -1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏽‍🫲🏼 E14.0 handshake: medium skin tone, medium-light skin tone -1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏽‍🫲🏾 E14.0 handshake: medium skin tone, medium-dark skin tone -1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏽‍🫲🏿 E14.0 handshake: medium skin tone, dark skin tone -1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏾‍🫲🏻 E14.0 handshake: medium-dark skin tone, light skin tone -1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏾‍🫲🏼 E14.0 handshake: medium-dark skin tone, medium-light skin tone -1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏾‍🫲🏽 E14.0 handshake: medium-dark skin tone, medium skin tone -1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏾‍🫲🏿 E14.0 handshake: medium-dark skin tone, dark skin tone -1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏿‍🫲🏻 E14.0 handshake: dark skin tone, light skin tone -1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏿‍🫲🏼 E14.0 handshake: dark skin tone, medium-light skin tone -1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏿‍🫲🏽 E14.0 handshake: dark skin tone, medium skin tone -1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏿‍🫲🏾 E14.0 handshake: dark skin tone, medium-dark skin tone -1F64F ; fully-qualified # 🙏 E0.6 folded hands -1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone -1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone -1F64F 1F3FD ; fully-qualified # 🙏🏽 E1.0 folded hands: medium skin tone -1F64F 1F3FE ; fully-qualified # 🙏🏾 E1.0 folded hands: medium-dark skin tone -1F64F 1F3FF ; fully-qualified # 🙏🏿 E1.0 folded hands: dark skin tone - -# subgroup: hand-prop -270D FE0F ; fully-qualified # ✍️ E0.7 writing hand -270D ; unqualified # ✍ E0.7 writing hand -270D 1F3FB ; fully-qualified # ✍🏻 E1.0 writing hand: light skin tone -270D 1F3FC ; fully-qualified # ✍🏼 E1.0 writing hand: medium-light skin tone -270D 1F3FD ; fully-qualified # ✍🏽 E1.0 writing hand: medium skin tone -270D 1F3FE ; fully-qualified # ✍🏾 E1.0 writing hand: medium-dark skin tone -270D 1F3FF ; fully-qualified # ✍🏿 E1.0 writing hand: dark skin tone -1F485 ; fully-qualified # 💅 E0.6 nail polish -1F485 1F3FB ; fully-qualified # 💅🏻 E1.0 nail polish: light skin tone -1F485 1F3FC ; fully-qualified # 💅🏼 E1.0 nail polish: medium-light skin tone -1F485 1F3FD ; fully-qualified # 💅🏽 E1.0 nail polish: medium skin tone -1F485 1F3FE ; fully-qualified # 💅🏾 E1.0 nail polish: medium-dark skin tone -1F485 1F3FF ; fully-qualified # 💅🏿 E1.0 nail polish: dark skin tone -1F933 ; fully-qualified # 🤳 E3.0 selfie -1F933 1F3FB ; fully-qualified # 🤳🏻 E3.0 selfie: light skin tone -1F933 1F3FC ; fully-qualified # 🤳🏼 E3.0 selfie: medium-light skin tone -1F933 1F3FD ; fully-qualified # 🤳🏽 E3.0 selfie: medium skin tone -1F933 1F3FE ; fully-qualified # 🤳🏾 E3.0 selfie: medium-dark skin tone -1F933 1F3FF ; fully-qualified # 🤳🏿 E3.0 selfie: dark skin tone - -# subgroup: body-parts -1F4AA ; fully-qualified # 💪 E0.6 flexed biceps -1F4AA 1F3FB ; fully-qualified # 💪🏻 E1.0 flexed biceps: light skin tone -1F4AA 1F3FC ; fully-qualified # 💪🏼 E1.0 flexed biceps: medium-light skin tone -1F4AA 1F3FD ; fully-qualified # 💪🏽 E1.0 flexed biceps: medium skin tone -1F4AA 1F3FE ; fully-qualified # 💪🏾 E1.0 flexed biceps: medium-dark skin tone -1F4AA 1F3FF ; fully-qualified # 💪🏿 E1.0 flexed biceps: dark skin tone -1F9BE ; fully-qualified # 🦾 E12.0 mechanical arm -1F9BF ; fully-qualified # 🦿 E12.0 mechanical leg -1F9B5 ; fully-qualified # 🦵 E11.0 leg -1F9B5 1F3FB ; fully-qualified # 🦵🏻 E11.0 leg: light skin tone -1F9B5 1F3FC ; fully-qualified # 🦵🏼 E11.0 leg: medium-light skin tone -1F9B5 1F3FD ; fully-qualified # 🦵🏽 E11.0 leg: medium skin tone -1F9B5 1F3FE ; fully-qualified # 🦵🏾 E11.0 leg: medium-dark skin tone -1F9B5 1F3FF ; fully-qualified # 🦵🏿 E11.0 leg: dark skin tone -1F9B6 ; fully-qualified # 🦶 E11.0 foot -1F9B6 1F3FB ; fully-qualified # 🦶🏻 E11.0 foot: light skin tone -1F9B6 1F3FC ; fully-qualified # 🦶🏼 E11.0 foot: medium-light skin tone -1F9B6 1F3FD ; fully-qualified # 🦶🏽 E11.0 foot: medium skin tone -1F9B6 1F3FE ; fully-qualified # 🦶🏾 E11.0 foot: medium-dark skin tone -1F9B6 1F3FF ; fully-qualified # 🦶🏿 E11.0 foot: dark skin tone -1F442 ; fully-qualified # 👂 E0.6 ear -1F442 1F3FB ; fully-qualified # 👂🏻 E1.0 ear: light skin tone -1F442 1F3FC ; fully-qualified # 👂🏼 E1.0 ear: medium-light skin tone -1F442 1F3FD ; fully-qualified # 👂🏽 E1.0 ear: medium skin tone -1F442 1F3FE ; fully-qualified # 👂🏾 E1.0 ear: medium-dark skin tone -1F442 1F3FF ; fully-qualified # 👂🏿 E1.0 ear: dark skin tone -1F9BB ; fully-qualified # 🦻 E12.0 ear with hearing aid -1F9BB 1F3FB ; fully-qualified # 🦻🏻 E12.0 ear with hearing aid: light skin tone -1F9BB 1F3FC ; fully-qualified # 🦻🏼 E12.0 ear with hearing aid: medium-light skin tone -1F9BB 1F3FD ; fully-qualified # 🦻🏽 E12.0 ear with hearing aid: medium skin tone -1F9BB 1F3FE ; fully-qualified # 🦻🏾 E12.0 ear with hearing aid: medium-dark skin tone -1F9BB 1F3FF ; fully-qualified # 🦻🏿 E12.0 ear with hearing aid: dark skin tone -1F443 ; fully-qualified # 👃 E0.6 nose -1F443 1F3FB ; fully-qualified # 👃🏻 E1.0 nose: light skin tone -1F443 1F3FC ; fully-qualified # 👃🏼 E1.0 nose: medium-light skin tone -1F443 1F3FD ; fully-qualified # 👃🏽 E1.0 nose: medium skin tone -1F443 1F3FE ; fully-qualified # 👃🏾 E1.0 nose: medium-dark skin tone -1F443 1F3FF ; fully-qualified # 👃🏿 E1.0 nose: dark skin tone -1F9E0 ; fully-qualified # 🧠 E5.0 brain -1FAC0 ; fully-qualified # 🫀 E13.0 anatomical heart -1FAC1 ; fully-qualified # 🫁 E13.0 lungs -1F9B7 ; fully-qualified # 🦷 E11.0 tooth -1F9B4 ; fully-qualified # 🦴 E11.0 bone -1F440 ; fully-qualified # 👀 E0.6 eyes -1F441 FE0F ; fully-qualified # 👁️ E0.7 eye -1F441 ; unqualified # 👁 E0.7 eye -1F445 ; fully-qualified # 👅 E0.6 tongue -1F444 ; fully-qualified # 👄 E0.6 mouth -1FAE6 ; fully-qualified # 🫦 E14.0 biting lip - -# subgroup: person -1F476 ; fully-qualified # 👶 E0.6 baby -1F476 1F3FB ; fully-qualified # 👶🏻 E1.0 baby: light skin tone -1F476 1F3FC ; fully-qualified # 👶🏼 E1.0 baby: medium-light skin tone -1F476 1F3FD ; fully-qualified # 👶🏽 E1.0 baby: medium skin tone -1F476 1F3FE ; fully-qualified # 👶🏾 E1.0 baby: medium-dark skin tone -1F476 1F3FF ; fully-qualified # 👶🏿 E1.0 baby: dark skin tone -1F9D2 ; fully-qualified # 🧒 E5.0 child -1F9D2 1F3FB ; fully-qualified # 🧒🏻 E5.0 child: light skin tone -1F9D2 1F3FC ; fully-qualified # 🧒🏼 E5.0 child: medium-light skin tone -1F9D2 1F3FD ; fully-qualified # 🧒🏽 E5.0 child: medium skin tone -1F9D2 1F3FE ; fully-qualified # 🧒🏾 E5.0 child: medium-dark skin tone -1F9D2 1F3FF ; fully-qualified # 🧒🏿 E5.0 child: dark skin tone -1F466 ; fully-qualified # 👦 E0.6 boy -1F466 1F3FB ; fully-qualified # 👦🏻 E1.0 boy: light skin tone -1F466 1F3FC ; fully-qualified # 👦🏼 E1.0 boy: medium-light skin tone -1F466 1F3FD ; fully-qualified # 👦🏽 E1.0 boy: medium skin tone -1F466 1F3FE ; fully-qualified # 👦🏾 E1.0 boy: medium-dark skin tone -1F466 1F3FF ; fully-qualified # 👦🏿 E1.0 boy: dark skin tone -1F467 ; fully-qualified # 👧 E0.6 girl -1F467 1F3FB ; fully-qualified # 👧🏻 E1.0 girl: light skin tone -1F467 1F3FC ; fully-qualified # 👧🏼 E1.0 girl: medium-light skin tone -1F467 1F3FD ; fully-qualified # 👧🏽 E1.0 girl: medium skin tone -1F467 1F3FE ; fully-qualified # 👧🏾 E1.0 girl: medium-dark skin tone -1F467 1F3FF ; fully-qualified # 👧🏿 E1.0 girl: dark skin tone -1F9D1 ; fully-qualified # 🧑 E5.0 person -1F9D1 1F3FB ; fully-qualified # 🧑🏻 E5.0 person: light skin tone -1F9D1 1F3FC ; fully-qualified # 🧑🏼 E5.0 person: medium-light skin tone -1F9D1 1F3FD ; fully-qualified # 🧑🏽 E5.0 person: medium skin tone -1F9D1 1F3FE ; fully-qualified # 🧑🏾 E5.0 person: medium-dark skin tone -1F9D1 1F3FF ; fully-qualified # 🧑🏿 E5.0 person: dark skin tone -1F471 ; fully-qualified # 👱 E0.6 person: blond hair -1F471 1F3FB ; fully-qualified # 👱🏻 E1.0 person: light skin tone, blond hair -1F471 1F3FC ; fully-qualified # 👱🏼 E1.0 person: medium-light skin tone, blond hair -1F471 1F3FD ; fully-qualified # 👱🏽 E1.0 person: medium skin tone, blond hair -1F471 1F3FE ; fully-qualified # 👱🏾 E1.0 person: medium-dark skin tone, blond hair -1F471 1F3FF ; fully-qualified # 👱🏿 E1.0 person: dark skin tone, blond hair -1F468 ; fully-qualified # 👨 E0.6 man -1F468 1F3FB ; fully-qualified # 👨🏻 E1.0 man: light skin tone -1F468 1F3FC ; fully-qualified # 👨🏼 E1.0 man: medium-light skin tone -1F468 1F3FD ; fully-qualified # 👨🏽 E1.0 man: medium skin tone -1F468 1F3FE ; fully-qualified # 👨🏾 E1.0 man: medium-dark skin tone -1F468 1F3FF ; fully-qualified # 👨🏿 E1.0 man: dark skin tone -1F9D4 ; fully-qualified # 🧔 E5.0 person: beard -1F9D4 1F3FB ; fully-qualified # 🧔🏻 E5.0 person: light skin tone, beard -1F9D4 1F3FC ; fully-qualified # 🧔🏼 E5.0 person: medium-light skin tone, beard -1F9D4 1F3FD ; fully-qualified # 🧔🏽 E5.0 person: medium skin tone, beard -1F9D4 1F3FE ; fully-qualified # 🧔🏾 E5.0 person: medium-dark skin tone, beard -1F9D4 1F3FF ; fully-qualified # 🧔🏿 E5.0 person: dark skin tone, beard -1F9D4 200D 2642 FE0F ; fully-qualified # 🧔‍♂️ E13.1 man: beard -1F9D4 200D 2642 ; minimally-qualified # 🧔‍♂ E13.1 man: beard -1F9D4 1F3FB 200D 2642 FE0F ; fully-qualified # 🧔🏻‍♂️ E13.1 man: light skin tone, beard -1F9D4 1F3FB 200D 2642 ; minimally-qualified # 🧔🏻‍♂ E13.1 man: light skin tone, beard -1F9D4 1F3FC 200D 2642 FE0F ; fully-qualified # 🧔🏼‍♂️ E13.1 man: medium-light skin tone, beard -1F9D4 1F3FC 200D 2642 ; minimally-qualified # 🧔🏼‍♂ E13.1 man: medium-light skin tone, beard -1F9D4 1F3FD 200D 2642 FE0F ; fully-qualified # 🧔🏽‍♂️ E13.1 man: medium skin tone, beard -1F9D4 1F3FD 200D 2642 ; minimally-qualified # 🧔🏽‍♂ E13.1 man: medium skin tone, beard -1F9D4 1F3FE 200D 2642 FE0F ; fully-qualified # 🧔🏾‍♂️ E13.1 man: medium-dark skin tone, beard -1F9D4 1F3FE 200D 2642 ; minimally-qualified # 🧔🏾‍♂ E13.1 man: medium-dark skin tone, beard -1F9D4 1F3FF 200D 2642 FE0F ; fully-qualified # 🧔🏿‍♂️ E13.1 man: dark skin tone, beard -1F9D4 1F3FF 200D 2642 ; minimally-qualified # 🧔🏿‍♂ E13.1 man: dark skin tone, beard -1F9D4 200D 2640 FE0F ; fully-qualified # 🧔‍♀️ E13.1 woman: beard -1F9D4 200D 2640 ; minimally-qualified # 🧔‍♀ E13.1 woman: beard -1F9D4 1F3FB 200D 2640 FE0F ; fully-qualified # 🧔🏻‍♀️ E13.1 woman: light skin tone, beard -1F9D4 1F3FB 200D 2640 ; minimally-qualified # 🧔🏻‍♀ E13.1 woman: light skin tone, beard -1F9D4 1F3FC 200D 2640 FE0F ; fully-qualified # 🧔🏼‍♀️ E13.1 woman: medium-light skin tone, beard -1F9D4 1F3FC 200D 2640 ; minimally-qualified # 🧔🏼‍♀ E13.1 woman: medium-light skin tone, beard -1F9D4 1F3FD 200D 2640 FE0F ; fully-qualified # 🧔🏽‍♀️ E13.1 woman: medium skin tone, beard -1F9D4 1F3FD 200D 2640 ; minimally-qualified # 🧔🏽‍♀ E13.1 woman: medium skin tone, beard -1F9D4 1F3FE 200D 2640 FE0F ; fully-qualified # 🧔🏾‍♀️ E13.1 woman: medium-dark skin tone, beard -1F9D4 1F3FE 200D 2640 ; minimally-qualified # 🧔🏾‍♀ E13.1 woman: medium-dark skin tone, beard -1F9D4 1F3FF 200D 2640 FE0F ; fully-qualified # 🧔🏿‍♀️ E13.1 woman: dark skin tone, beard -1F9D4 1F3FF 200D 2640 ; minimally-qualified # 🧔🏿‍♀ E13.1 woman: dark skin tone, beard -1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 E11.0 man: red hair -1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 E11.0 man: light skin tone, red hair -1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 E11.0 man: medium-light skin tone, red hair -1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 E11.0 man: medium skin tone, red hair -1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 E11.0 man: medium-dark skin tone, red hair -1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 E11.0 man: dark skin tone, red hair -1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 E11.0 man: curly hair -1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 E11.0 man: light skin tone, curly hair -1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 E11.0 man: medium-light skin tone, curly hair -1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 E11.0 man: medium skin tone, curly hair -1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 E11.0 man: medium-dark skin tone, curly hair -1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 E11.0 man: dark skin tone, curly hair -1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 E11.0 man: white hair -1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 E11.0 man: light skin tone, white hair -1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 E11.0 man: medium-light skin tone, white hair -1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 E11.0 man: medium skin tone, white hair -1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 E11.0 man: medium-dark skin tone, white hair -1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 E11.0 man: dark skin tone, white hair -1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 E11.0 man: bald -1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 E11.0 man: light skin tone, bald -1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 E11.0 man: medium-light skin tone, bald -1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 E11.0 man: medium skin tone, bald -1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 E11.0 man: medium-dark skin tone, bald -1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 E11.0 man: dark skin tone, bald -1F469 ; fully-qualified # 👩 E0.6 woman -1F469 1F3FB ; fully-qualified # 👩🏻 E1.0 woman: light skin tone -1F469 1F3FC ; fully-qualified # 👩🏼 E1.0 woman: medium-light skin tone -1F469 1F3FD ; fully-qualified # 👩🏽 E1.0 woman: medium skin tone -1F469 1F3FE ; fully-qualified # 👩🏾 E1.0 woman: medium-dark skin tone -1F469 1F3FF ; fully-qualified # 👩🏿 E1.0 woman: dark skin tone -1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 E11.0 woman: red hair -1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 E11.0 woman: light skin tone, red hair -1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 E11.0 woman: medium-light skin tone, red hair -1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 E11.0 woman: medium skin tone, red hair -1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 E11.0 woman: medium-dark skin tone, red hair -1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 E11.0 woman: dark skin tone, red hair -1F9D1 200D 1F9B0 ; fully-qualified # 🧑‍🦰 E12.1 person: red hair -1F9D1 1F3FB 200D 1F9B0 ; fully-qualified # 🧑🏻‍🦰 E12.1 person: light skin tone, red hair -1F9D1 1F3FC 200D 1F9B0 ; fully-qualified # 🧑🏼‍🦰 E12.1 person: medium-light skin tone, red hair -1F9D1 1F3FD 200D 1F9B0 ; fully-qualified # 🧑🏽‍🦰 E12.1 person: medium skin tone, red hair -1F9D1 1F3FE 200D 1F9B0 ; fully-qualified # 🧑🏾‍🦰 E12.1 person: medium-dark skin tone, red hair -1F9D1 1F3FF 200D 1F9B0 ; fully-qualified # 🧑🏿‍🦰 E12.1 person: dark skin tone, red hair -1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 E11.0 woman: curly hair -1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 E11.0 woman: light skin tone, curly hair -1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 E11.0 woman: medium-light skin tone, curly hair -1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 E11.0 woman: medium skin tone, curly hair -1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 E11.0 woman: medium-dark skin tone, curly hair -1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 E11.0 woman: dark skin tone, curly hair -1F9D1 200D 1F9B1 ; fully-qualified # 🧑‍🦱 E12.1 person: curly hair -1F9D1 1F3FB 200D 1F9B1 ; fully-qualified # 🧑🏻‍🦱 E12.1 person: light skin tone, curly hair -1F9D1 1F3FC 200D 1F9B1 ; fully-qualified # 🧑🏼‍🦱 E12.1 person: medium-light skin tone, curly hair -1F9D1 1F3FD 200D 1F9B1 ; fully-qualified # 🧑🏽‍🦱 E12.1 person: medium skin tone, curly hair -1F9D1 1F3FE 200D 1F9B1 ; fully-qualified # 🧑🏾‍🦱 E12.1 person: medium-dark skin tone, curly hair -1F9D1 1F3FF 200D 1F9B1 ; fully-qualified # 🧑🏿‍🦱 E12.1 person: dark skin tone, curly hair -1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 E11.0 woman: white hair -1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 E11.0 woman: light skin tone, white hair -1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 E11.0 woman: medium-light skin tone, white hair -1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 E11.0 woman: medium skin tone, white hair -1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 E11.0 woman: medium-dark skin tone, white hair -1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 E11.0 woman: dark skin tone, white hair -1F9D1 200D 1F9B3 ; fully-qualified # 🧑‍🦳 E12.1 person: white hair -1F9D1 1F3FB 200D 1F9B3 ; fully-qualified # 🧑🏻‍🦳 E12.1 person: light skin tone, white hair -1F9D1 1F3FC 200D 1F9B3 ; fully-qualified # 🧑🏼‍🦳 E12.1 person: medium-light skin tone, white hair -1F9D1 1F3FD 200D 1F9B3 ; fully-qualified # 🧑🏽‍🦳 E12.1 person: medium skin tone, white hair -1F9D1 1F3FE 200D 1F9B3 ; fully-qualified # 🧑🏾‍🦳 E12.1 person: medium-dark skin tone, white hair -1F9D1 1F3FF 200D 1F9B3 ; fully-qualified # 🧑🏿‍🦳 E12.1 person: dark skin tone, white hair -1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 E11.0 woman: bald -1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 E11.0 woman: light skin tone, bald -1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 E11.0 woman: medium-light skin tone, bald -1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 E11.0 woman: medium skin tone, bald -1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 E11.0 woman: medium-dark skin tone, bald -1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 E11.0 woman: dark skin tone, bald -1F9D1 200D 1F9B2 ; fully-qualified # 🧑‍🦲 E12.1 person: bald -1F9D1 1F3FB 200D 1F9B2 ; fully-qualified # 🧑🏻‍🦲 E12.1 person: light skin tone, bald -1F9D1 1F3FC 200D 1F9B2 ; fully-qualified # 🧑🏼‍🦲 E12.1 person: medium-light skin tone, bald -1F9D1 1F3FD 200D 1F9B2 ; fully-qualified # 🧑🏽‍🦲 E12.1 person: medium skin tone, bald -1F9D1 1F3FE 200D 1F9B2 ; fully-qualified # 🧑🏾‍🦲 E12.1 person: medium-dark skin tone, bald -1F9D1 1F3FF 200D 1F9B2 ; fully-qualified # 🧑🏿‍🦲 E12.1 person: dark skin tone, bald -1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ E4.0 woman: blond hair -1F471 200D 2640 ; minimally-qualified # 👱‍♀ E4.0 woman: blond hair -1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ E4.0 woman: light skin tone, blond hair -1F471 1F3FB 200D 2640 ; minimally-qualified # 👱🏻‍♀ E4.0 woman: light skin tone, blond hair -1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ E4.0 woman: medium-light skin tone, blond hair -1F471 1F3FC 200D 2640 ; minimally-qualified # 👱🏼‍♀ E4.0 woman: medium-light skin tone, blond hair -1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ E4.0 woman: medium skin tone, blond hair -1F471 1F3FD 200D 2640 ; minimally-qualified # 👱🏽‍♀ E4.0 woman: medium skin tone, blond hair -1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ E4.0 woman: medium-dark skin tone, blond hair -1F471 1F3FE 200D 2640 ; minimally-qualified # 👱🏾‍♀ E4.0 woman: medium-dark skin tone, blond hair -1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ E4.0 woman: dark skin tone, blond hair -1F471 1F3FF 200D 2640 ; minimally-qualified # 👱🏿‍♀ E4.0 woman: dark skin tone, blond hair -1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ E4.0 man: blond hair -1F471 200D 2642 ; minimally-qualified # 👱‍♂ E4.0 man: blond hair -1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ E4.0 man: light skin tone, blond hair -1F471 1F3FB 200D 2642 ; minimally-qualified # 👱🏻‍♂ E4.0 man: light skin tone, blond hair -1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ E4.0 man: medium-light skin tone, blond hair -1F471 1F3FC 200D 2642 ; minimally-qualified # 👱🏼‍♂ E4.0 man: medium-light skin tone, blond hair -1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ E4.0 man: medium skin tone, blond hair -1F471 1F3FD 200D 2642 ; minimally-qualified # 👱🏽‍♂ E4.0 man: medium skin tone, blond hair -1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ E4.0 man: medium-dark skin tone, blond hair -1F471 1F3FE 200D 2642 ; minimally-qualified # 👱🏾‍♂ E4.0 man: medium-dark skin tone, blond hair -1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ E4.0 man: dark skin tone, blond hair -1F471 1F3FF 200D 2642 ; minimally-qualified # 👱🏿‍♂ E4.0 man: dark skin tone, blond hair -1F9D3 ; fully-qualified # 🧓 E5.0 older person -1F9D3 1F3FB ; fully-qualified # 🧓🏻 E5.0 older person: light skin tone -1F9D3 1F3FC ; fully-qualified # 🧓🏼 E5.0 older person: medium-light skin tone -1F9D3 1F3FD ; fully-qualified # 🧓🏽 E5.0 older person: medium skin tone -1F9D3 1F3FE ; fully-qualified # 🧓🏾 E5.0 older person: medium-dark skin tone -1F9D3 1F3FF ; fully-qualified # 🧓🏿 E5.0 older person: dark skin tone -1F474 ; fully-qualified # 👴 E0.6 old man -1F474 1F3FB ; fully-qualified # 👴🏻 E1.0 old man: light skin tone -1F474 1F3FC ; fully-qualified # 👴🏼 E1.0 old man: medium-light skin tone -1F474 1F3FD ; fully-qualified # 👴🏽 E1.0 old man: medium skin tone -1F474 1F3FE ; fully-qualified # 👴🏾 E1.0 old man: medium-dark skin tone -1F474 1F3FF ; fully-qualified # 👴🏿 E1.0 old man: dark skin tone -1F475 ; fully-qualified # 👵 E0.6 old woman -1F475 1F3FB ; fully-qualified # 👵🏻 E1.0 old woman: light skin tone -1F475 1F3FC ; fully-qualified # 👵🏼 E1.0 old woman: medium-light skin tone -1F475 1F3FD ; fully-qualified # 👵🏽 E1.0 old woman: medium skin tone -1F475 1F3FE ; fully-qualified # 👵🏾 E1.0 old woman: medium-dark skin tone -1F475 1F3FF ; fully-qualified # 👵🏿 E1.0 old woman: dark skin tone - -# subgroup: person-gesture -1F64D ; fully-qualified # 🙍 E0.6 person frowning -1F64D 1F3FB ; fully-qualified # 🙍🏻 E1.0 person frowning: light skin tone -1F64D 1F3FC ; fully-qualified # 🙍🏼 E1.0 person frowning: medium-light skin tone -1F64D 1F3FD ; fully-qualified # 🙍🏽 E1.0 person frowning: medium skin tone -1F64D 1F3FE ; fully-qualified # 🙍🏾 E1.0 person frowning: medium-dark skin tone -1F64D 1F3FF ; fully-qualified # 🙍🏿 E1.0 person frowning: dark skin tone -1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ E4.0 man frowning -1F64D 200D 2642 ; minimally-qualified # 🙍‍♂ E4.0 man frowning -1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ E4.0 man frowning: light skin tone -1F64D 1F3FB 200D 2642 ; minimally-qualified # 🙍🏻‍♂ E4.0 man frowning: light skin tone -1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ E4.0 man frowning: medium-light skin tone -1F64D 1F3FC 200D 2642 ; minimally-qualified # 🙍🏼‍♂ E4.0 man frowning: medium-light skin tone -1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ E4.0 man frowning: medium skin tone -1F64D 1F3FD 200D 2642 ; minimally-qualified # 🙍🏽‍♂ E4.0 man frowning: medium skin tone -1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ E4.0 man frowning: medium-dark skin tone -1F64D 1F3FE 200D 2642 ; minimally-qualified # 🙍🏾‍♂ E4.0 man frowning: medium-dark skin tone -1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ E4.0 man frowning: dark skin tone -1F64D 1F3FF 200D 2642 ; minimally-qualified # 🙍🏿‍♂ E4.0 man frowning: dark skin tone -1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ E4.0 woman frowning -1F64D 200D 2640 ; minimally-qualified # 🙍‍♀ E4.0 woman frowning -1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ E4.0 woman frowning: light skin tone -1F64D 1F3FB 200D 2640 ; minimally-qualified # 🙍🏻‍♀ E4.0 woman frowning: light skin tone -1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ E4.0 woman frowning: medium-light skin tone -1F64D 1F3FC 200D 2640 ; minimally-qualified # 🙍🏼‍♀ E4.0 woman frowning: medium-light skin tone -1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ E4.0 woman frowning: medium skin tone -1F64D 1F3FD 200D 2640 ; minimally-qualified # 🙍🏽‍♀ E4.0 woman frowning: medium skin tone -1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ E4.0 woman frowning: medium-dark skin tone -1F64D 1F3FE 200D 2640 ; minimally-qualified # 🙍🏾‍♀ E4.0 woman frowning: medium-dark skin tone -1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ E4.0 woman frowning: dark skin tone -1F64D 1F3FF 200D 2640 ; minimally-qualified # 🙍🏿‍♀ E4.0 woman frowning: dark skin tone -1F64E ; fully-qualified # 🙎 E0.6 person pouting -1F64E 1F3FB ; fully-qualified # 🙎🏻 E1.0 person pouting: light skin tone -1F64E 1F3FC ; fully-qualified # 🙎🏼 E1.0 person pouting: medium-light skin tone -1F64E 1F3FD ; fully-qualified # 🙎🏽 E1.0 person pouting: medium skin tone -1F64E 1F3FE ; fully-qualified # 🙎🏾 E1.0 person pouting: medium-dark skin tone -1F64E 1F3FF ; fully-qualified # 🙎🏿 E1.0 person pouting: dark skin tone -1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ E4.0 man pouting -1F64E 200D 2642 ; minimally-qualified # 🙎‍♂ E4.0 man pouting -1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ E4.0 man pouting: light skin tone -1F64E 1F3FB 200D 2642 ; minimally-qualified # 🙎🏻‍♂ E4.0 man pouting: light skin tone -1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ E4.0 man pouting: medium-light skin tone -1F64E 1F3FC 200D 2642 ; minimally-qualified # 🙎🏼‍♂ E4.0 man pouting: medium-light skin tone -1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ E4.0 man pouting: medium skin tone -1F64E 1F3FD 200D 2642 ; minimally-qualified # 🙎🏽‍♂ E4.0 man pouting: medium skin tone -1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ E4.0 man pouting: medium-dark skin tone -1F64E 1F3FE 200D 2642 ; minimally-qualified # 🙎🏾‍♂ E4.0 man pouting: medium-dark skin tone -1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ E4.0 man pouting: dark skin tone -1F64E 1F3FF 200D 2642 ; minimally-qualified # 🙎🏿‍♂ E4.0 man pouting: dark skin tone -1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ E4.0 woman pouting -1F64E 200D 2640 ; minimally-qualified # 🙎‍♀ E4.0 woman pouting -1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ E4.0 woman pouting: light skin tone -1F64E 1F3FB 200D 2640 ; minimally-qualified # 🙎🏻‍♀ E4.0 woman pouting: light skin tone -1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ E4.0 woman pouting: medium-light skin tone -1F64E 1F3FC 200D 2640 ; minimally-qualified # 🙎🏼‍♀ E4.0 woman pouting: medium-light skin tone -1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ E4.0 woman pouting: medium skin tone -1F64E 1F3FD 200D 2640 ; minimally-qualified # 🙎🏽‍♀ E4.0 woman pouting: medium skin tone -1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ E4.0 woman pouting: medium-dark skin tone -1F64E 1F3FE 200D 2640 ; minimally-qualified # 🙎🏾‍♀ E4.0 woman pouting: medium-dark skin tone -1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ E4.0 woman pouting: dark skin tone -1F64E 1F3FF 200D 2640 ; minimally-qualified # 🙎🏿‍♀ E4.0 woman pouting: dark skin tone -1F645 ; fully-qualified # 🙅 E0.6 person gesturing NO -1F645 1F3FB ; fully-qualified # 🙅🏻 E1.0 person gesturing NO: light skin tone -1F645 1F3FC ; fully-qualified # 🙅🏼 E1.0 person gesturing NO: medium-light skin tone -1F645 1F3FD ; fully-qualified # 🙅🏽 E1.0 person gesturing NO: medium skin tone -1F645 1F3FE ; fully-qualified # 🙅🏾 E1.0 person gesturing NO: medium-dark skin tone -1F645 1F3FF ; fully-qualified # 🙅🏿 E1.0 person gesturing NO: dark skin tone -1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ E4.0 man gesturing NO -1F645 200D 2642 ; minimally-qualified # 🙅‍♂ E4.0 man gesturing NO -1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ E4.0 man gesturing NO: light skin tone -1F645 1F3FB 200D 2642 ; minimally-qualified # 🙅🏻‍♂ E4.0 man gesturing NO: light skin tone -1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ E4.0 man gesturing NO: medium-light skin tone -1F645 1F3FC 200D 2642 ; minimally-qualified # 🙅🏼‍♂ E4.0 man gesturing NO: medium-light skin tone -1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ E4.0 man gesturing NO: medium skin tone -1F645 1F3FD 200D 2642 ; minimally-qualified # 🙅🏽‍♂ E4.0 man gesturing NO: medium skin tone -1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ E4.0 man gesturing NO: medium-dark skin tone -1F645 1F3FE 200D 2642 ; minimally-qualified # 🙅🏾‍♂ E4.0 man gesturing NO: medium-dark skin tone -1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ E4.0 man gesturing NO: dark skin tone -1F645 1F3FF 200D 2642 ; minimally-qualified # 🙅🏿‍♂ E4.0 man gesturing NO: dark skin tone -1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ E4.0 woman gesturing NO -1F645 200D 2640 ; minimally-qualified # 🙅‍♀ E4.0 woman gesturing NO -1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ E4.0 woman gesturing NO: light skin tone -1F645 1F3FB 200D 2640 ; minimally-qualified # 🙅🏻‍♀ E4.0 woman gesturing NO: light skin tone -1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ E4.0 woman gesturing NO: medium-light skin tone -1F645 1F3FC 200D 2640 ; minimally-qualified # 🙅🏼‍♀ E4.0 woman gesturing NO: medium-light skin tone -1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ E4.0 woman gesturing NO: medium skin tone -1F645 1F3FD 200D 2640 ; minimally-qualified # 🙅🏽‍♀ E4.0 woman gesturing NO: medium skin tone -1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ E4.0 woman gesturing NO: medium-dark skin tone -1F645 1F3FE 200D 2640 ; minimally-qualified # 🙅🏾‍♀ E4.0 woman gesturing NO: medium-dark skin tone -1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ E4.0 woman gesturing NO: dark skin tone -1F645 1F3FF 200D 2640 ; minimally-qualified # 🙅🏿‍♀ E4.0 woman gesturing NO: dark skin tone -1F646 ; fully-qualified # 🙆 E0.6 person gesturing OK -1F646 1F3FB ; fully-qualified # 🙆🏻 E1.0 person gesturing OK: light skin tone -1F646 1F3FC ; fully-qualified # 🙆🏼 E1.0 person gesturing OK: medium-light skin tone -1F646 1F3FD ; fully-qualified # 🙆🏽 E1.0 person gesturing OK: medium skin tone -1F646 1F3FE ; fully-qualified # 🙆🏾 E1.0 person gesturing OK: medium-dark skin tone -1F646 1F3FF ; fully-qualified # 🙆🏿 E1.0 person gesturing OK: dark skin tone -1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ E4.0 man gesturing OK -1F646 200D 2642 ; minimally-qualified # 🙆‍♂ E4.0 man gesturing OK -1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ E4.0 man gesturing OK: light skin tone -1F646 1F3FB 200D 2642 ; minimally-qualified # 🙆🏻‍♂ E4.0 man gesturing OK: light skin tone -1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ E4.0 man gesturing OK: medium-light skin tone -1F646 1F3FC 200D 2642 ; minimally-qualified # 🙆🏼‍♂ E4.0 man gesturing OK: medium-light skin tone -1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ E4.0 man gesturing OK: medium skin tone -1F646 1F3FD 200D 2642 ; minimally-qualified # 🙆🏽‍♂ E4.0 man gesturing OK: medium skin tone -1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ E4.0 man gesturing OK: medium-dark skin tone -1F646 1F3FE 200D 2642 ; minimally-qualified # 🙆🏾‍♂ E4.0 man gesturing OK: medium-dark skin tone -1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ E4.0 man gesturing OK: dark skin tone -1F646 1F3FF 200D 2642 ; minimally-qualified # 🙆🏿‍♂ E4.0 man gesturing OK: dark skin tone -1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ E4.0 woman gesturing OK -1F646 200D 2640 ; minimally-qualified # 🙆‍♀ E4.0 woman gesturing OK -1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ E4.0 woman gesturing OK: light skin tone -1F646 1F3FB 200D 2640 ; minimally-qualified # 🙆🏻‍♀ E4.0 woman gesturing OK: light skin tone -1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ E4.0 woman gesturing OK: medium-light skin tone -1F646 1F3FC 200D 2640 ; minimally-qualified # 🙆🏼‍♀ E4.0 woman gesturing OK: medium-light skin tone -1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ E4.0 woman gesturing OK: medium skin tone -1F646 1F3FD 200D 2640 ; minimally-qualified # 🙆🏽‍♀ E4.0 woman gesturing OK: medium skin tone -1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ E4.0 woman gesturing OK: medium-dark skin tone -1F646 1F3FE 200D 2640 ; minimally-qualified # 🙆🏾‍♀ E4.0 woman gesturing OK: medium-dark skin tone -1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ E4.0 woman gesturing OK: dark skin tone -1F646 1F3FF 200D 2640 ; minimally-qualified # 🙆🏿‍♀ E4.0 woman gesturing OK: dark skin tone -1F481 ; fully-qualified # 💁 E0.6 person tipping hand -1F481 1F3FB ; fully-qualified # 💁🏻 E1.0 person tipping hand: light skin tone -1F481 1F3FC ; fully-qualified # 💁🏼 E1.0 person tipping hand: medium-light skin tone -1F481 1F3FD ; fully-qualified # 💁🏽 E1.0 person tipping hand: medium skin tone -1F481 1F3FE ; fully-qualified # 💁🏾 E1.0 person tipping hand: medium-dark skin tone -1F481 1F3FF ; fully-qualified # 💁🏿 E1.0 person tipping hand: dark skin tone -1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ E4.0 man tipping hand -1F481 200D 2642 ; minimally-qualified # 💁‍♂ E4.0 man tipping hand -1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ E4.0 man tipping hand: light skin tone -1F481 1F3FB 200D 2642 ; minimally-qualified # 💁🏻‍♂ E4.0 man tipping hand: light skin tone -1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ E4.0 man tipping hand: medium-light skin tone -1F481 1F3FC 200D 2642 ; minimally-qualified # 💁🏼‍♂ E4.0 man tipping hand: medium-light skin tone -1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ E4.0 man tipping hand: medium skin tone -1F481 1F3FD 200D 2642 ; minimally-qualified # 💁🏽‍♂ E4.0 man tipping hand: medium skin tone -1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ E4.0 man tipping hand: medium-dark skin tone -1F481 1F3FE 200D 2642 ; minimally-qualified # 💁🏾‍♂ E4.0 man tipping hand: medium-dark skin tone -1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ E4.0 man tipping hand: dark skin tone -1F481 1F3FF 200D 2642 ; minimally-qualified # 💁🏿‍♂ E4.0 man tipping hand: dark skin tone -1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ E4.0 woman tipping hand -1F481 200D 2640 ; minimally-qualified # 💁‍♀ E4.0 woman tipping hand -1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ E4.0 woman tipping hand: light skin tone -1F481 1F3FB 200D 2640 ; minimally-qualified # 💁🏻‍♀ E4.0 woman tipping hand: light skin tone -1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ E4.0 woman tipping hand: medium-light skin tone -1F481 1F3FC 200D 2640 ; minimally-qualified # 💁🏼‍♀ E4.0 woman tipping hand: medium-light skin tone -1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ E4.0 woman tipping hand: medium skin tone -1F481 1F3FD 200D 2640 ; minimally-qualified # 💁🏽‍♀ E4.0 woman tipping hand: medium skin tone -1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ E4.0 woman tipping hand: medium-dark skin tone -1F481 1F3FE 200D 2640 ; minimally-qualified # 💁🏾‍♀ E4.0 woman tipping hand: medium-dark skin tone -1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ E4.0 woman tipping hand: dark skin tone -1F481 1F3FF 200D 2640 ; minimally-qualified # 💁🏿‍♀ E4.0 woman tipping hand: dark skin tone -1F64B ; fully-qualified # 🙋 E0.6 person raising hand -1F64B 1F3FB ; fully-qualified # 🙋🏻 E1.0 person raising hand: light skin tone -1F64B 1F3FC ; fully-qualified # 🙋🏼 E1.0 person raising hand: medium-light skin tone -1F64B 1F3FD ; fully-qualified # 🙋🏽 E1.0 person raising hand: medium skin tone -1F64B 1F3FE ; fully-qualified # 🙋🏾 E1.0 person raising hand: medium-dark skin tone -1F64B 1F3FF ; fully-qualified # 🙋🏿 E1.0 person raising hand: dark skin tone -1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ E4.0 man raising hand -1F64B 200D 2642 ; minimally-qualified # 🙋‍♂ E4.0 man raising hand -1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ E4.0 man raising hand: light skin tone -1F64B 1F3FB 200D 2642 ; minimally-qualified # 🙋🏻‍♂ E4.0 man raising hand: light skin tone -1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ E4.0 man raising hand: medium-light skin tone -1F64B 1F3FC 200D 2642 ; minimally-qualified # 🙋🏼‍♂ E4.0 man raising hand: medium-light skin tone -1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ E4.0 man raising hand: medium skin tone -1F64B 1F3FD 200D 2642 ; minimally-qualified # 🙋🏽‍♂ E4.0 man raising hand: medium skin tone -1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ E4.0 man raising hand: medium-dark skin tone -1F64B 1F3FE 200D 2642 ; minimally-qualified # 🙋🏾‍♂ E4.0 man raising hand: medium-dark skin tone -1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ E4.0 man raising hand: dark skin tone -1F64B 1F3FF 200D 2642 ; minimally-qualified # 🙋🏿‍♂ E4.0 man raising hand: dark skin tone -1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ E4.0 woman raising hand -1F64B 200D 2640 ; minimally-qualified # 🙋‍♀ E4.0 woman raising hand -1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ E4.0 woman raising hand: light skin tone -1F64B 1F3FB 200D 2640 ; minimally-qualified # 🙋🏻‍♀ E4.0 woman raising hand: light skin tone -1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ E4.0 woman raising hand: medium-light skin tone -1F64B 1F3FC 200D 2640 ; minimally-qualified # 🙋🏼‍♀ E4.0 woman raising hand: medium-light skin tone -1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ E4.0 woman raising hand: medium skin tone -1F64B 1F3FD 200D 2640 ; minimally-qualified # 🙋🏽‍♀ E4.0 woman raising hand: medium skin tone -1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ E4.0 woman raising hand: medium-dark skin tone -1F64B 1F3FE 200D 2640 ; minimally-qualified # 🙋🏾‍♀ E4.0 woman raising hand: medium-dark skin tone -1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ E4.0 woman raising hand: dark skin tone -1F64B 1F3FF 200D 2640 ; minimally-qualified # 🙋🏿‍♀ E4.0 woman raising hand: dark skin tone -1F9CF ; fully-qualified # 🧏 E12.0 deaf person -1F9CF 1F3FB ; fully-qualified # 🧏🏻 E12.0 deaf person: light skin tone -1F9CF 1F3FC ; fully-qualified # 🧏🏼 E12.0 deaf person: medium-light skin tone -1F9CF 1F3FD ; fully-qualified # 🧏🏽 E12.0 deaf person: medium skin tone -1F9CF 1F3FE ; fully-qualified # 🧏🏾 E12.0 deaf person: medium-dark skin tone -1F9CF 1F3FF ; fully-qualified # 🧏🏿 E12.0 deaf person: dark skin tone -1F9CF 200D 2642 FE0F ; fully-qualified # 🧏‍♂️ E12.0 deaf man -1F9CF 200D 2642 ; minimally-qualified # 🧏‍♂ E12.0 deaf man -1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻‍♂️ E12.0 deaf man: light skin tone -1F9CF 1F3FB 200D 2642 ; minimally-qualified # 🧏🏻‍♂ E12.0 deaf man: light skin tone -1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼‍♂️ E12.0 deaf man: medium-light skin tone -1F9CF 1F3FC 200D 2642 ; minimally-qualified # 🧏🏼‍♂ E12.0 deaf man: medium-light skin tone -1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽‍♂️ E12.0 deaf man: medium skin tone -1F9CF 1F3FD 200D 2642 ; minimally-qualified # 🧏🏽‍♂ E12.0 deaf man: medium skin tone -1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾‍♂️ E12.0 deaf man: medium-dark skin tone -1F9CF 1F3FE 200D 2642 ; minimally-qualified # 🧏🏾‍♂ E12.0 deaf man: medium-dark skin tone -1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿‍♂️ E12.0 deaf man: dark skin tone -1F9CF 1F3FF 200D 2642 ; minimally-qualified # 🧏🏿‍♂ E12.0 deaf man: dark skin tone -1F9CF 200D 2640 FE0F ; fully-qualified # 🧏‍♀️ E12.0 deaf woman -1F9CF 200D 2640 ; minimally-qualified # 🧏‍♀ E12.0 deaf woman -1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻‍♀️ E12.0 deaf woman: light skin tone -1F9CF 1F3FB 200D 2640 ; minimally-qualified # 🧏🏻‍♀ E12.0 deaf woman: light skin tone -1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼‍♀️ E12.0 deaf woman: medium-light skin tone -1F9CF 1F3FC 200D 2640 ; minimally-qualified # 🧏🏼‍♀ E12.0 deaf woman: medium-light skin tone -1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽‍♀️ E12.0 deaf woman: medium skin tone -1F9CF 1F3FD 200D 2640 ; minimally-qualified # 🧏🏽‍♀ E12.0 deaf woman: medium skin tone -1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾‍♀️ E12.0 deaf woman: medium-dark skin tone -1F9CF 1F3FE 200D 2640 ; minimally-qualified # 🧏🏾‍♀ E12.0 deaf woman: medium-dark skin tone -1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿‍♀️ E12.0 deaf woman: dark skin tone -1F9CF 1F3FF 200D 2640 ; minimally-qualified # 🧏🏿‍♀ E12.0 deaf woman: dark skin tone -1F647 ; fully-qualified # 🙇 E0.6 person bowing -1F647 1F3FB ; fully-qualified # 🙇🏻 E1.0 person bowing: light skin tone -1F647 1F3FC ; fully-qualified # 🙇🏼 E1.0 person bowing: medium-light skin tone -1F647 1F3FD ; fully-qualified # 🙇🏽 E1.0 person bowing: medium skin tone -1F647 1F3FE ; fully-qualified # 🙇🏾 E1.0 person bowing: medium-dark skin tone -1F647 1F3FF ; fully-qualified # 🙇🏿 E1.0 person bowing: dark skin tone -1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ E4.0 man bowing -1F647 200D 2642 ; minimally-qualified # 🙇‍♂ E4.0 man bowing -1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ E4.0 man bowing: light skin tone -1F647 1F3FB 200D 2642 ; minimally-qualified # 🙇🏻‍♂ E4.0 man bowing: light skin tone -1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ E4.0 man bowing: medium-light skin tone -1F647 1F3FC 200D 2642 ; minimally-qualified # 🙇🏼‍♂ E4.0 man bowing: medium-light skin tone -1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ E4.0 man bowing: medium skin tone -1F647 1F3FD 200D 2642 ; minimally-qualified # 🙇🏽‍♂ E4.0 man bowing: medium skin tone -1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ E4.0 man bowing: medium-dark skin tone -1F647 1F3FE 200D 2642 ; minimally-qualified # 🙇🏾‍♂ E4.0 man bowing: medium-dark skin tone -1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ E4.0 man bowing: dark skin tone -1F647 1F3FF 200D 2642 ; minimally-qualified # 🙇🏿‍♂ E4.0 man bowing: dark skin tone -1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ E4.0 woman bowing -1F647 200D 2640 ; minimally-qualified # 🙇‍♀ E4.0 woman bowing -1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ E4.0 woman bowing: light skin tone -1F647 1F3FB 200D 2640 ; minimally-qualified # 🙇🏻‍♀ E4.0 woman bowing: light skin tone -1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ E4.0 woman bowing: medium-light skin tone -1F647 1F3FC 200D 2640 ; minimally-qualified # 🙇🏼‍♀ E4.0 woman bowing: medium-light skin tone -1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ E4.0 woman bowing: medium skin tone -1F647 1F3FD 200D 2640 ; minimally-qualified # 🙇🏽‍♀ E4.0 woman bowing: medium skin tone -1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ E4.0 woman bowing: medium-dark skin tone -1F647 1F3FE 200D 2640 ; minimally-qualified # 🙇🏾‍♀ E4.0 woman bowing: medium-dark skin tone -1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ E4.0 woman bowing: dark skin tone -1F647 1F3FF 200D 2640 ; minimally-qualified # 🙇🏿‍♀ E4.0 woman bowing: dark skin tone -1F926 ; fully-qualified # 🤦 E3.0 person facepalming -1F926 1F3FB ; fully-qualified # 🤦🏻 E3.0 person facepalming: light skin tone -1F926 1F3FC ; fully-qualified # 🤦🏼 E3.0 person facepalming: medium-light skin tone -1F926 1F3FD ; fully-qualified # 🤦🏽 E3.0 person facepalming: medium skin tone -1F926 1F3FE ; fully-qualified # 🤦🏾 E3.0 person facepalming: medium-dark skin tone -1F926 1F3FF ; fully-qualified # 🤦🏿 E3.0 person facepalming: dark skin tone -1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ E4.0 man facepalming -1F926 200D 2642 ; minimally-qualified # 🤦‍♂ E4.0 man facepalming -1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ E4.0 man facepalming: light skin tone -1F926 1F3FB 200D 2642 ; minimally-qualified # 🤦🏻‍♂ E4.0 man facepalming: light skin tone -1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ E4.0 man facepalming: medium-light skin tone -1F926 1F3FC 200D 2642 ; minimally-qualified # 🤦🏼‍♂ E4.0 man facepalming: medium-light skin tone -1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ E4.0 man facepalming: medium skin tone -1F926 1F3FD 200D 2642 ; minimally-qualified # 🤦🏽‍♂ E4.0 man facepalming: medium skin tone -1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ E4.0 man facepalming: medium-dark skin tone -1F926 1F3FE 200D 2642 ; minimally-qualified # 🤦🏾‍♂ E4.0 man facepalming: medium-dark skin tone -1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ E4.0 man facepalming: dark skin tone -1F926 1F3FF 200D 2642 ; minimally-qualified # 🤦🏿‍♂ E4.0 man facepalming: dark skin tone -1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ E4.0 woman facepalming -1F926 200D 2640 ; minimally-qualified # 🤦‍♀ E4.0 woman facepalming -1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ E4.0 woman facepalming: light skin tone -1F926 1F3FB 200D 2640 ; minimally-qualified # 🤦🏻‍♀ E4.0 woman facepalming: light skin tone -1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ E4.0 woman facepalming: medium-light skin tone -1F926 1F3FC 200D 2640 ; minimally-qualified # 🤦🏼‍♀ E4.0 woman facepalming: medium-light skin tone -1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ E4.0 woman facepalming: medium skin tone -1F926 1F3FD 200D 2640 ; minimally-qualified # 🤦🏽‍♀ E4.0 woman facepalming: medium skin tone -1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ E4.0 woman facepalming: medium-dark skin tone -1F926 1F3FE 200D 2640 ; minimally-qualified # 🤦🏾‍♀ E4.0 woman facepalming: medium-dark skin tone -1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ E4.0 woman facepalming: dark skin tone -1F926 1F3FF 200D 2640 ; minimally-qualified # 🤦🏿‍♀ E4.0 woman facepalming: dark skin tone -1F937 ; fully-qualified # 🤷 E3.0 person shrugging -1F937 1F3FB ; fully-qualified # 🤷🏻 E3.0 person shrugging: light skin tone -1F937 1F3FC ; fully-qualified # 🤷🏼 E3.0 person shrugging: medium-light skin tone -1F937 1F3FD ; fully-qualified # 🤷🏽 E3.0 person shrugging: medium skin tone -1F937 1F3FE ; fully-qualified # 🤷🏾 E3.0 person shrugging: medium-dark skin tone -1F937 1F3FF ; fully-qualified # 🤷🏿 E3.0 person shrugging: dark skin tone -1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ E4.0 man shrugging -1F937 200D 2642 ; minimally-qualified # 🤷‍♂ E4.0 man shrugging -1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ E4.0 man shrugging: light skin tone -1F937 1F3FB 200D 2642 ; minimally-qualified # 🤷🏻‍♂ E4.0 man shrugging: light skin tone -1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ E4.0 man shrugging: medium-light skin tone -1F937 1F3FC 200D 2642 ; minimally-qualified # 🤷🏼‍♂ E4.0 man shrugging: medium-light skin tone -1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ E4.0 man shrugging: medium skin tone -1F937 1F3FD 200D 2642 ; minimally-qualified # 🤷🏽‍♂ E4.0 man shrugging: medium skin tone -1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ E4.0 man shrugging: medium-dark skin tone -1F937 1F3FE 200D 2642 ; minimally-qualified # 🤷🏾‍♂ E4.0 man shrugging: medium-dark skin tone -1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ E4.0 man shrugging: dark skin tone -1F937 1F3FF 200D 2642 ; minimally-qualified # 🤷🏿‍♂ E4.0 man shrugging: dark skin tone -1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ E4.0 woman shrugging -1F937 200D 2640 ; minimally-qualified # 🤷‍♀ E4.0 woman shrugging -1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ E4.0 woman shrugging: light skin tone -1F937 1F3FB 200D 2640 ; minimally-qualified # 🤷🏻‍♀ E4.0 woman shrugging: light skin tone -1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ E4.0 woman shrugging: medium-light skin tone -1F937 1F3FC 200D 2640 ; minimally-qualified # 🤷🏼‍♀ E4.0 woman shrugging: medium-light skin tone -1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ E4.0 woman shrugging: medium skin tone -1F937 1F3FD 200D 2640 ; minimally-qualified # 🤷🏽‍♀ E4.0 woman shrugging: medium skin tone -1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ E4.0 woman shrugging: medium-dark skin tone -1F937 1F3FE 200D 2640 ; minimally-qualified # 🤷🏾‍♀ E4.0 woman shrugging: medium-dark skin tone -1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ E4.0 woman shrugging: dark skin tone -1F937 1F3FF 200D 2640 ; minimally-qualified # 🤷🏿‍♀ E4.0 woman shrugging: dark skin tone - -# subgroup: person-role -1F9D1 200D 2695 FE0F ; fully-qualified # 🧑‍⚕️ E12.1 health worker -1F9D1 200D 2695 ; minimally-qualified # 🧑‍⚕ E12.1 health worker -1F9D1 1F3FB 200D 2695 FE0F ; fully-qualified # 🧑🏻‍⚕️ E12.1 health worker: light skin tone -1F9D1 1F3FB 200D 2695 ; minimally-qualified # 🧑🏻‍⚕ E12.1 health worker: light skin tone -1F9D1 1F3FC 200D 2695 FE0F ; fully-qualified # 🧑🏼‍⚕️ E12.1 health worker: medium-light skin tone -1F9D1 1F3FC 200D 2695 ; minimally-qualified # 🧑🏼‍⚕ E12.1 health worker: medium-light skin tone -1F9D1 1F3FD 200D 2695 FE0F ; fully-qualified # 🧑🏽‍⚕️ E12.1 health worker: medium skin tone -1F9D1 1F3FD 200D 2695 ; minimally-qualified # 🧑🏽‍⚕ E12.1 health worker: medium skin tone -1F9D1 1F3FE 200D 2695 FE0F ; fully-qualified # 🧑🏾‍⚕️ E12.1 health worker: medium-dark skin tone -1F9D1 1F3FE 200D 2695 ; minimally-qualified # 🧑🏾‍⚕ E12.1 health worker: medium-dark skin tone -1F9D1 1F3FF 200D 2695 FE0F ; fully-qualified # 🧑🏿‍⚕️ E12.1 health worker: dark skin tone -1F9D1 1F3FF 200D 2695 ; minimally-qualified # 🧑🏿‍⚕ E12.1 health worker: dark skin tone -1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ E4.0 man health worker -1F468 200D 2695 ; minimally-qualified # 👨‍⚕ E4.0 man health worker -1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ E4.0 man health worker: light skin tone -1F468 1F3FB 200D 2695 ; minimally-qualified # 👨🏻‍⚕ E4.0 man health worker: light skin tone -1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ E4.0 man health worker: medium-light skin tone -1F468 1F3FC 200D 2695 ; minimally-qualified # 👨🏼‍⚕ E4.0 man health worker: medium-light skin tone -1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ E4.0 man health worker: medium skin tone -1F468 1F3FD 200D 2695 ; minimally-qualified # 👨🏽‍⚕ E4.0 man health worker: medium skin tone -1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ E4.0 man health worker: medium-dark skin tone -1F468 1F3FE 200D 2695 ; minimally-qualified # 👨🏾‍⚕ E4.0 man health worker: medium-dark skin tone -1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ E4.0 man health worker: dark skin tone -1F468 1F3FF 200D 2695 ; minimally-qualified # 👨🏿‍⚕ E4.0 man health worker: dark skin tone -1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ E4.0 woman health worker -1F469 200D 2695 ; minimally-qualified # 👩‍⚕ E4.0 woman health worker -1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ E4.0 woman health worker: light skin tone -1F469 1F3FB 200D 2695 ; minimally-qualified # 👩🏻‍⚕ E4.0 woman health worker: light skin tone -1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ E4.0 woman health worker: medium-light skin tone -1F469 1F3FC 200D 2695 ; minimally-qualified # 👩🏼‍⚕ E4.0 woman health worker: medium-light skin tone -1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ E4.0 woman health worker: medium skin tone -1F469 1F3FD 200D 2695 ; minimally-qualified # 👩🏽‍⚕ E4.0 woman health worker: medium skin tone -1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ E4.0 woman health worker: medium-dark skin tone -1F469 1F3FE 200D 2695 ; minimally-qualified # 👩🏾‍⚕ E4.0 woman health worker: medium-dark skin tone -1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ E4.0 woman health worker: dark skin tone -1F469 1F3FF 200D 2695 ; minimally-qualified # 👩🏿‍⚕ E4.0 woman health worker: dark skin tone -1F9D1 200D 1F393 ; fully-qualified # 🧑‍🎓 E12.1 student -1F9D1 1F3FB 200D 1F393 ; fully-qualified # 🧑🏻‍🎓 E12.1 student: light skin tone -1F9D1 1F3FC 200D 1F393 ; fully-qualified # 🧑🏼‍🎓 E12.1 student: medium-light skin tone -1F9D1 1F3FD 200D 1F393 ; fully-qualified # 🧑🏽‍🎓 E12.1 student: medium skin tone -1F9D1 1F3FE 200D 1F393 ; fully-qualified # 🧑🏾‍🎓 E12.1 student: medium-dark skin tone -1F9D1 1F3FF 200D 1F393 ; fully-qualified # 🧑🏿‍🎓 E12.1 student: dark skin tone -1F468 200D 1F393 ; fully-qualified # 👨‍🎓 E4.0 man student -1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 E4.0 man student: light skin tone -1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 E4.0 man student: medium-light skin tone -1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 E4.0 man student: medium skin tone -1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 E4.0 man student: medium-dark skin tone -1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 E4.0 man student: dark skin tone -1F469 200D 1F393 ; fully-qualified # 👩‍🎓 E4.0 woman student -1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 E4.0 woman student: light skin tone -1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 E4.0 woman student: medium-light skin tone -1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 E4.0 woman student: medium skin tone -1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 E4.0 woman student: medium-dark skin tone -1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 E4.0 woman student: dark skin tone -1F9D1 200D 1F3EB ; fully-qualified # 🧑‍🏫 E12.1 teacher -1F9D1 1F3FB 200D 1F3EB ; fully-qualified # 🧑🏻‍🏫 E12.1 teacher: light skin tone -1F9D1 1F3FC 200D 1F3EB ; fully-qualified # 🧑🏼‍🏫 E12.1 teacher: medium-light skin tone -1F9D1 1F3FD 200D 1F3EB ; fully-qualified # 🧑🏽‍🏫 E12.1 teacher: medium skin tone -1F9D1 1F3FE 200D 1F3EB ; fully-qualified # 🧑🏾‍🏫 E12.1 teacher: medium-dark skin tone -1F9D1 1F3FF 200D 1F3EB ; fully-qualified # 🧑🏿‍🏫 E12.1 teacher: dark skin tone -1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 E4.0 man teacher -1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 E4.0 man teacher: light skin tone -1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 E4.0 man teacher: medium-light skin tone -1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 E4.0 man teacher: medium skin tone -1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 E4.0 man teacher: medium-dark skin tone -1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 E4.0 man teacher: dark skin tone -1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 E4.0 woman teacher -1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 E4.0 woman teacher: light skin tone -1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 E4.0 woman teacher: medium-light skin tone -1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 E4.0 woman teacher: medium skin tone -1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 E4.0 woman teacher: medium-dark skin tone -1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 E4.0 woman teacher: dark skin tone -1F9D1 200D 2696 FE0F ; fully-qualified # 🧑‍⚖️ E12.1 judge -1F9D1 200D 2696 ; minimally-qualified # 🧑‍⚖ E12.1 judge -1F9D1 1F3FB 200D 2696 FE0F ; fully-qualified # 🧑🏻‍⚖️ E12.1 judge: light skin tone -1F9D1 1F3FB 200D 2696 ; minimally-qualified # 🧑🏻‍⚖ E12.1 judge: light skin tone -1F9D1 1F3FC 200D 2696 FE0F ; fully-qualified # 🧑🏼‍⚖️ E12.1 judge: medium-light skin tone -1F9D1 1F3FC 200D 2696 ; minimally-qualified # 🧑🏼‍⚖ E12.1 judge: medium-light skin tone -1F9D1 1F3FD 200D 2696 FE0F ; fully-qualified # 🧑🏽‍⚖️ E12.1 judge: medium skin tone -1F9D1 1F3FD 200D 2696 ; minimally-qualified # 🧑🏽‍⚖ E12.1 judge: medium skin tone -1F9D1 1F3FE 200D 2696 FE0F ; fully-qualified # 🧑🏾‍⚖️ E12.1 judge: medium-dark skin tone -1F9D1 1F3FE 200D 2696 ; minimally-qualified # 🧑🏾‍⚖ E12.1 judge: medium-dark skin tone -1F9D1 1F3FF 200D 2696 FE0F ; fully-qualified # 🧑🏿‍⚖️ E12.1 judge: dark skin tone -1F9D1 1F3FF 200D 2696 ; minimally-qualified # 🧑🏿‍⚖ E12.1 judge: dark skin tone -1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ E4.0 man judge -1F468 200D 2696 ; minimally-qualified # 👨‍⚖ E4.0 man judge -1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ E4.0 man judge: light skin tone -1F468 1F3FB 200D 2696 ; minimally-qualified # 👨🏻‍⚖ E4.0 man judge: light skin tone -1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ E4.0 man judge: medium-light skin tone -1F468 1F3FC 200D 2696 ; minimally-qualified # 👨🏼‍⚖ E4.0 man judge: medium-light skin tone -1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ E4.0 man judge: medium skin tone -1F468 1F3FD 200D 2696 ; minimally-qualified # 👨🏽‍⚖ E4.0 man judge: medium skin tone -1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ E4.0 man judge: medium-dark skin tone -1F468 1F3FE 200D 2696 ; minimally-qualified # 👨🏾‍⚖ E4.0 man judge: medium-dark skin tone -1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ E4.0 man judge: dark skin tone -1F468 1F3FF 200D 2696 ; minimally-qualified # 👨🏿‍⚖ E4.0 man judge: dark skin tone -1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ E4.0 woman judge -1F469 200D 2696 ; minimally-qualified # 👩‍⚖ E4.0 woman judge -1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ E4.0 woman judge: light skin tone -1F469 1F3FB 200D 2696 ; minimally-qualified # 👩🏻‍⚖ E4.0 woman judge: light skin tone -1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ E4.0 woman judge: medium-light skin tone -1F469 1F3FC 200D 2696 ; minimally-qualified # 👩🏼‍⚖ E4.0 woman judge: medium-light skin tone -1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ E4.0 woman judge: medium skin tone -1F469 1F3FD 200D 2696 ; minimally-qualified # 👩🏽‍⚖ E4.0 woman judge: medium skin tone -1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ E4.0 woman judge: medium-dark skin tone -1F469 1F3FE 200D 2696 ; minimally-qualified # 👩🏾‍⚖ E4.0 woman judge: medium-dark skin tone -1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ E4.0 woman judge: dark skin tone -1F469 1F3FF 200D 2696 ; minimally-qualified # 👩🏿‍⚖ E4.0 woman judge: dark skin tone -1F9D1 200D 1F33E ; fully-qualified # 🧑‍🌾 E12.1 farmer -1F9D1 1F3FB 200D 1F33E ; fully-qualified # 🧑🏻‍🌾 E12.1 farmer: light skin tone -1F9D1 1F3FC 200D 1F33E ; fully-qualified # 🧑🏼‍🌾 E12.1 farmer: medium-light skin tone -1F9D1 1F3FD 200D 1F33E ; fully-qualified # 🧑🏽‍🌾 E12.1 farmer: medium skin tone -1F9D1 1F3FE 200D 1F33E ; fully-qualified # 🧑🏾‍🌾 E12.1 farmer: medium-dark skin tone -1F9D1 1F3FF 200D 1F33E ; fully-qualified # 🧑🏿‍🌾 E12.1 farmer: dark skin tone -1F468 200D 1F33E ; fully-qualified # 👨‍🌾 E4.0 man farmer -1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 E4.0 man farmer: light skin tone -1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 E4.0 man farmer: medium-light skin tone -1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 E4.0 man farmer: medium skin tone -1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 E4.0 man farmer: medium-dark skin tone -1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 E4.0 man farmer: dark skin tone -1F469 200D 1F33E ; fully-qualified # 👩‍🌾 E4.0 woman farmer -1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 E4.0 woman farmer: light skin tone -1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 E4.0 woman farmer: medium-light skin tone -1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 E4.0 woman farmer: medium skin tone -1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 E4.0 woman farmer: medium-dark skin tone -1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 E4.0 woman farmer: dark skin tone -1F9D1 200D 1F373 ; fully-qualified # 🧑‍🍳 E12.1 cook -1F9D1 1F3FB 200D 1F373 ; fully-qualified # 🧑🏻‍🍳 E12.1 cook: light skin tone -1F9D1 1F3FC 200D 1F373 ; fully-qualified # 🧑🏼‍🍳 E12.1 cook: medium-light skin tone -1F9D1 1F3FD 200D 1F373 ; fully-qualified # 🧑🏽‍🍳 E12.1 cook: medium skin tone -1F9D1 1F3FE 200D 1F373 ; fully-qualified # 🧑🏾‍🍳 E12.1 cook: medium-dark skin tone -1F9D1 1F3FF 200D 1F373 ; fully-qualified # 🧑🏿‍🍳 E12.1 cook: dark skin tone -1F468 200D 1F373 ; fully-qualified # 👨‍🍳 E4.0 man cook -1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 E4.0 man cook: light skin tone -1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 E4.0 man cook: medium-light skin tone -1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 E4.0 man cook: medium skin tone -1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 E4.0 man cook: medium-dark skin tone -1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 E4.0 man cook: dark skin tone -1F469 200D 1F373 ; fully-qualified # 👩‍🍳 E4.0 woman cook -1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 E4.0 woman cook: light skin tone -1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 E4.0 woman cook: medium-light skin tone -1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 E4.0 woman cook: medium skin tone -1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 E4.0 woman cook: medium-dark skin tone -1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 E4.0 woman cook: dark skin tone -1F9D1 200D 1F527 ; fully-qualified # 🧑‍🔧 E12.1 mechanic -1F9D1 1F3FB 200D 1F527 ; fully-qualified # 🧑🏻‍🔧 E12.1 mechanic: light skin tone -1F9D1 1F3FC 200D 1F527 ; fully-qualified # 🧑🏼‍🔧 E12.1 mechanic: medium-light skin tone -1F9D1 1F3FD 200D 1F527 ; fully-qualified # 🧑🏽‍🔧 E12.1 mechanic: medium skin tone -1F9D1 1F3FE 200D 1F527 ; fully-qualified # 🧑🏾‍🔧 E12.1 mechanic: medium-dark skin tone -1F9D1 1F3FF 200D 1F527 ; fully-qualified # 🧑🏿‍🔧 E12.1 mechanic: dark skin tone -1F468 200D 1F527 ; fully-qualified # 👨‍🔧 E4.0 man mechanic -1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 E4.0 man mechanic: light skin tone -1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 E4.0 man mechanic: medium-light skin tone -1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 E4.0 man mechanic: medium skin tone -1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 E4.0 man mechanic: medium-dark skin tone -1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 E4.0 man mechanic: dark skin tone -1F469 200D 1F527 ; fully-qualified # 👩‍🔧 E4.0 woman mechanic -1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 E4.0 woman mechanic: light skin tone -1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 E4.0 woman mechanic: medium-light skin tone -1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 E4.0 woman mechanic: medium skin tone -1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 E4.0 woman mechanic: medium-dark skin tone -1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 E4.0 woman mechanic: dark skin tone -1F9D1 200D 1F3ED ; fully-qualified # 🧑‍🏭 E12.1 factory worker -1F9D1 1F3FB 200D 1F3ED ; fully-qualified # 🧑🏻‍🏭 E12.1 factory worker: light skin tone -1F9D1 1F3FC 200D 1F3ED ; fully-qualified # 🧑🏼‍🏭 E12.1 factory worker: medium-light skin tone -1F9D1 1F3FD 200D 1F3ED ; fully-qualified # 🧑🏽‍🏭 E12.1 factory worker: medium skin tone -1F9D1 1F3FE 200D 1F3ED ; fully-qualified # 🧑🏾‍🏭 E12.1 factory worker: medium-dark skin tone -1F9D1 1F3FF 200D 1F3ED ; fully-qualified # 🧑🏿‍🏭 E12.1 factory worker: dark skin tone -1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 E4.0 man factory worker -1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 E4.0 man factory worker: light skin tone -1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 E4.0 man factory worker: medium-light skin tone -1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 E4.0 man factory worker: medium skin tone -1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 E4.0 man factory worker: medium-dark skin tone -1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 E4.0 man factory worker: dark skin tone -1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 E4.0 woman factory worker -1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 E4.0 woman factory worker: light skin tone -1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 E4.0 woman factory worker: medium-light skin tone -1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 E4.0 woman factory worker: medium skin tone -1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 E4.0 woman factory worker: medium-dark skin tone -1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 E4.0 woman factory worker: dark skin tone -1F9D1 200D 1F4BC ; fully-qualified # 🧑‍💼 E12.1 office worker -1F9D1 1F3FB 200D 1F4BC ; fully-qualified # 🧑🏻‍💼 E12.1 office worker: light skin tone -1F9D1 1F3FC 200D 1F4BC ; fully-qualified # 🧑🏼‍💼 E12.1 office worker: medium-light skin tone -1F9D1 1F3FD 200D 1F4BC ; fully-qualified # 🧑🏽‍💼 E12.1 office worker: medium skin tone -1F9D1 1F3FE 200D 1F4BC ; fully-qualified # 🧑🏾‍💼 E12.1 office worker: medium-dark skin tone -1F9D1 1F3FF 200D 1F4BC ; fully-qualified # 🧑🏿‍💼 E12.1 office worker: dark skin tone -1F468 200D 1F4BC ; fully-qualified # 👨‍💼 E4.0 man office worker -1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 E4.0 man office worker: light skin tone -1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 E4.0 man office worker: medium-light skin tone -1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 E4.0 man office worker: medium skin tone -1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 E4.0 man office worker: medium-dark skin tone -1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 E4.0 man office worker: dark skin tone -1F469 200D 1F4BC ; fully-qualified # 👩‍💼 E4.0 woman office worker -1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 E4.0 woman office worker: light skin tone -1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 E4.0 woman office worker: medium-light skin tone -1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 E4.0 woman office worker: medium skin tone -1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 E4.0 woman office worker: medium-dark skin tone -1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 E4.0 woman office worker: dark skin tone -1F9D1 200D 1F52C ; fully-qualified # 🧑‍🔬 E12.1 scientist -1F9D1 1F3FB 200D 1F52C ; fully-qualified # 🧑🏻‍🔬 E12.1 scientist: light skin tone -1F9D1 1F3FC 200D 1F52C ; fully-qualified # 🧑🏼‍🔬 E12.1 scientist: medium-light skin tone -1F9D1 1F3FD 200D 1F52C ; fully-qualified # 🧑🏽‍🔬 E12.1 scientist: medium skin tone -1F9D1 1F3FE 200D 1F52C ; fully-qualified # 🧑🏾‍🔬 E12.1 scientist: medium-dark skin tone -1F9D1 1F3FF 200D 1F52C ; fully-qualified # 🧑🏿‍🔬 E12.1 scientist: dark skin tone -1F468 200D 1F52C ; fully-qualified # 👨‍🔬 E4.0 man scientist -1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 E4.0 man scientist: light skin tone -1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 E4.0 man scientist: medium-light skin tone -1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 E4.0 man scientist: medium skin tone -1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 E4.0 man scientist: medium-dark skin tone -1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 E4.0 man scientist: dark skin tone -1F469 200D 1F52C ; fully-qualified # 👩‍🔬 E4.0 woman scientist -1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 E4.0 woman scientist: light skin tone -1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 E4.0 woman scientist: medium-light skin tone -1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 E4.0 woman scientist: medium skin tone -1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 E4.0 woman scientist: medium-dark skin tone -1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 E4.0 woman scientist: dark skin tone -1F9D1 200D 1F4BB ; fully-qualified # 🧑‍💻 E12.1 technologist -1F9D1 1F3FB 200D 1F4BB ; fully-qualified # 🧑🏻‍💻 E12.1 technologist: light skin tone -1F9D1 1F3FC 200D 1F4BB ; fully-qualified # 🧑🏼‍💻 E12.1 technologist: medium-light skin tone -1F9D1 1F3FD 200D 1F4BB ; fully-qualified # 🧑🏽‍💻 E12.1 technologist: medium skin tone -1F9D1 1F3FE 200D 1F4BB ; fully-qualified # 🧑🏾‍💻 E12.1 technologist: medium-dark skin tone -1F9D1 1F3FF 200D 1F4BB ; fully-qualified # 🧑🏿‍💻 E12.1 technologist: dark skin tone -1F468 200D 1F4BB ; fully-qualified # 👨‍💻 E4.0 man technologist -1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 E4.0 man technologist: light skin tone -1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 E4.0 man technologist: medium-light skin tone -1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 E4.0 man technologist: medium skin tone -1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 E4.0 man technologist: medium-dark skin tone -1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 E4.0 man technologist: dark skin tone -1F469 200D 1F4BB ; fully-qualified # 👩‍💻 E4.0 woman technologist -1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 E4.0 woman technologist: light skin tone -1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 E4.0 woman technologist: medium-light skin tone -1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 E4.0 woman technologist: medium skin tone -1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 E4.0 woman technologist: medium-dark skin tone -1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 E4.0 woman technologist: dark skin tone -1F9D1 200D 1F3A4 ; fully-qualified # 🧑‍🎤 E12.1 singer -1F9D1 1F3FB 200D 1F3A4 ; fully-qualified # 🧑🏻‍🎤 E12.1 singer: light skin tone -1F9D1 1F3FC 200D 1F3A4 ; fully-qualified # 🧑🏼‍🎤 E12.1 singer: medium-light skin tone -1F9D1 1F3FD 200D 1F3A4 ; fully-qualified # 🧑🏽‍🎤 E12.1 singer: medium skin tone -1F9D1 1F3FE 200D 1F3A4 ; fully-qualified # 🧑🏾‍🎤 E12.1 singer: medium-dark skin tone -1F9D1 1F3FF 200D 1F3A4 ; fully-qualified # 🧑🏿‍🎤 E12.1 singer: dark skin tone -1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 E4.0 man singer -1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 E4.0 man singer: light skin tone -1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 E4.0 man singer: medium-light skin tone -1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 E4.0 man singer: medium skin tone -1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 E4.0 man singer: medium-dark skin tone -1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 E4.0 man singer: dark skin tone -1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 E4.0 woman singer -1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 E4.0 woman singer: light skin tone -1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 E4.0 woman singer: medium-light skin tone -1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 E4.0 woman singer: medium skin tone -1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 E4.0 woman singer: medium-dark skin tone -1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 E4.0 woman singer: dark skin tone -1F9D1 200D 1F3A8 ; fully-qualified # 🧑‍🎨 E12.1 artist -1F9D1 1F3FB 200D 1F3A8 ; fully-qualified # 🧑🏻‍🎨 E12.1 artist: light skin tone -1F9D1 1F3FC 200D 1F3A8 ; fully-qualified # 🧑🏼‍🎨 E12.1 artist: medium-light skin tone -1F9D1 1F3FD 200D 1F3A8 ; fully-qualified # 🧑🏽‍🎨 E12.1 artist: medium skin tone -1F9D1 1F3FE 200D 1F3A8 ; fully-qualified # 🧑🏾‍🎨 E12.1 artist: medium-dark skin tone -1F9D1 1F3FF 200D 1F3A8 ; fully-qualified # 🧑🏿‍🎨 E12.1 artist: dark skin tone -1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 E4.0 man artist -1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 E4.0 man artist: light skin tone -1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 E4.0 man artist: medium-light skin tone -1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 E4.0 man artist: medium skin tone -1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 E4.0 man artist: medium-dark skin tone -1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 E4.0 man artist: dark skin tone -1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 E4.0 woman artist -1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 E4.0 woman artist: light skin tone -1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 E4.0 woman artist: medium-light skin tone -1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 E4.0 woman artist: medium skin tone -1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 E4.0 woman artist: medium-dark skin tone -1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 E4.0 woman artist: dark skin tone -1F9D1 200D 2708 FE0F ; fully-qualified # 🧑‍✈️ E12.1 pilot -1F9D1 200D 2708 ; minimally-qualified # 🧑‍✈ E12.1 pilot -1F9D1 1F3FB 200D 2708 FE0F ; fully-qualified # 🧑🏻‍✈️ E12.1 pilot: light skin tone -1F9D1 1F3FB 200D 2708 ; minimally-qualified # 🧑🏻‍✈ E12.1 pilot: light skin tone -1F9D1 1F3FC 200D 2708 FE0F ; fully-qualified # 🧑🏼‍✈️ E12.1 pilot: medium-light skin tone -1F9D1 1F3FC 200D 2708 ; minimally-qualified # 🧑🏼‍✈ E12.1 pilot: medium-light skin tone -1F9D1 1F3FD 200D 2708 FE0F ; fully-qualified # 🧑🏽‍✈️ E12.1 pilot: medium skin tone -1F9D1 1F3FD 200D 2708 ; minimally-qualified # 🧑🏽‍✈ E12.1 pilot: medium skin tone -1F9D1 1F3FE 200D 2708 FE0F ; fully-qualified # 🧑🏾‍✈️ E12.1 pilot: medium-dark skin tone -1F9D1 1F3FE 200D 2708 ; minimally-qualified # 🧑🏾‍✈ E12.1 pilot: medium-dark skin tone -1F9D1 1F3FF 200D 2708 FE0F ; fully-qualified # 🧑🏿‍✈️ E12.1 pilot: dark skin tone -1F9D1 1F3FF 200D 2708 ; minimally-qualified # 🧑🏿‍✈ E12.1 pilot: dark skin tone -1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ E4.0 man pilot -1F468 200D 2708 ; minimally-qualified # 👨‍✈ E4.0 man pilot -1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ E4.0 man pilot: light skin tone -1F468 1F3FB 200D 2708 ; minimally-qualified # 👨🏻‍✈ E4.0 man pilot: light skin tone -1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ E4.0 man pilot: medium-light skin tone -1F468 1F3FC 200D 2708 ; minimally-qualified # 👨🏼‍✈ E4.0 man pilot: medium-light skin tone -1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ E4.0 man pilot: medium skin tone -1F468 1F3FD 200D 2708 ; minimally-qualified # 👨🏽‍✈ E4.0 man pilot: medium skin tone -1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ E4.0 man pilot: medium-dark skin tone -1F468 1F3FE 200D 2708 ; minimally-qualified # 👨🏾‍✈ E4.0 man pilot: medium-dark skin tone -1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ E4.0 man pilot: dark skin tone -1F468 1F3FF 200D 2708 ; minimally-qualified # 👨🏿‍✈ E4.0 man pilot: dark skin tone -1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ E4.0 woman pilot -1F469 200D 2708 ; minimally-qualified # 👩‍✈ E4.0 woman pilot -1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ E4.0 woman pilot: light skin tone -1F469 1F3FB 200D 2708 ; minimally-qualified # 👩🏻‍✈ E4.0 woman pilot: light skin tone -1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ E4.0 woman pilot: medium-light skin tone -1F469 1F3FC 200D 2708 ; minimally-qualified # 👩🏼‍✈ E4.0 woman pilot: medium-light skin tone -1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ E4.0 woman pilot: medium skin tone -1F469 1F3FD 200D 2708 ; minimally-qualified # 👩🏽‍✈ E4.0 woman pilot: medium skin tone -1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ E4.0 woman pilot: medium-dark skin tone -1F469 1F3FE 200D 2708 ; minimally-qualified # 👩🏾‍✈ E4.0 woman pilot: medium-dark skin tone -1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ E4.0 woman pilot: dark skin tone -1F469 1F3FF 200D 2708 ; minimally-qualified # 👩🏿‍✈ E4.0 woman pilot: dark skin tone -1F9D1 200D 1F680 ; fully-qualified # 🧑‍🚀 E12.1 astronaut -1F9D1 1F3FB 200D 1F680 ; fully-qualified # 🧑🏻‍🚀 E12.1 astronaut: light skin tone -1F9D1 1F3FC 200D 1F680 ; fully-qualified # 🧑🏼‍🚀 E12.1 astronaut: medium-light skin tone -1F9D1 1F3FD 200D 1F680 ; fully-qualified # 🧑🏽‍🚀 E12.1 astronaut: medium skin tone -1F9D1 1F3FE 200D 1F680 ; fully-qualified # 🧑🏾‍🚀 E12.1 astronaut: medium-dark skin tone -1F9D1 1F3FF 200D 1F680 ; fully-qualified # 🧑🏿‍🚀 E12.1 astronaut: dark skin tone -1F468 200D 1F680 ; fully-qualified # 👨‍🚀 E4.0 man astronaut -1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 E4.0 man astronaut: light skin tone -1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 E4.0 man astronaut: medium-light skin tone -1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 E4.0 man astronaut: medium skin tone -1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 E4.0 man astronaut: medium-dark skin tone -1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 E4.0 man astronaut: dark skin tone -1F469 200D 1F680 ; fully-qualified # 👩‍🚀 E4.0 woman astronaut -1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 E4.0 woman astronaut: light skin tone -1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 E4.0 woman astronaut: medium-light skin tone -1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 E4.0 woman astronaut: medium skin tone -1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 E4.0 woman astronaut: medium-dark skin tone -1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 E4.0 woman astronaut: dark skin tone -1F9D1 200D 1F692 ; fully-qualified # 🧑‍🚒 E12.1 firefighter -1F9D1 1F3FB 200D 1F692 ; fully-qualified # 🧑🏻‍🚒 E12.1 firefighter: light skin tone -1F9D1 1F3FC 200D 1F692 ; fully-qualified # 🧑🏼‍🚒 E12.1 firefighter: medium-light skin tone -1F9D1 1F3FD 200D 1F692 ; fully-qualified # 🧑🏽‍🚒 E12.1 firefighter: medium skin tone -1F9D1 1F3FE 200D 1F692 ; fully-qualified # 🧑🏾‍🚒 E12.1 firefighter: medium-dark skin tone -1F9D1 1F3FF 200D 1F692 ; fully-qualified # 🧑🏿‍🚒 E12.1 firefighter: dark skin tone -1F468 200D 1F692 ; fully-qualified # 👨‍🚒 E4.0 man firefighter -1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 E4.0 man firefighter: light skin tone -1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 E4.0 man firefighter: medium-light skin tone -1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 E4.0 man firefighter: medium skin tone -1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 E4.0 man firefighter: medium-dark skin tone -1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 E4.0 man firefighter: dark skin tone -1F469 200D 1F692 ; fully-qualified # 👩‍🚒 E4.0 woman firefighter -1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 E4.0 woman firefighter: light skin tone -1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 E4.0 woman firefighter: medium-light skin tone -1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 E4.0 woman firefighter: medium skin tone -1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 E4.0 woman firefighter: medium-dark skin tone -1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 E4.0 woman firefighter: dark skin tone -1F46E ; fully-qualified # 👮 E0.6 police officer -1F46E 1F3FB ; fully-qualified # 👮🏻 E1.0 police officer: light skin tone -1F46E 1F3FC ; fully-qualified # 👮🏼 E1.0 police officer: medium-light skin tone -1F46E 1F3FD ; fully-qualified # 👮🏽 E1.0 police officer: medium skin tone -1F46E 1F3FE ; fully-qualified # 👮🏾 E1.0 police officer: medium-dark skin tone -1F46E 1F3FF ; fully-qualified # 👮🏿 E1.0 police officer: dark skin tone -1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ E4.0 man police officer -1F46E 200D 2642 ; minimally-qualified # 👮‍♂ E4.0 man police officer -1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ E4.0 man police officer: light skin tone -1F46E 1F3FB 200D 2642 ; minimally-qualified # 👮🏻‍♂ E4.0 man police officer: light skin tone -1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ E4.0 man police officer: medium-light skin tone -1F46E 1F3FC 200D 2642 ; minimally-qualified # 👮🏼‍♂ E4.0 man police officer: medium-light skin tone -1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ E4.0 man police officer: medium skin tone -1F46E 1F3FD 200D 2642 ; minimally-qualified # 👮🏽‍♂ E4.0 man police officer: medium skin tone -1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ E4.0 man police officer: medium-dark skin tone -1F46E 1F3FE 200D 2642 ; minimally-qualified # 👮🏾‍♂ E4.0 man police officer: medium-dark skin tone -1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ E4.0 man police officer: dark skin tone -1F46E 1F3FF 200D 2642 ; minimally-qualified # 👮🏿‍♂ E4.0 man police officer: dark skin tone -1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ E4.0 woman police officer -1F46E 200D 2640 ; minimally-qualified # 👮‍♀ E4.0 woman police officer -1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ E4.0 woman police officer: light skin tone -1F46E 1F3FB 200D 2640 ; minimally-qualified # 👮🏻‍♀ E4.0 woman police officer: light skin tone -1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ E4.0 woman police officer: medium-light skin tone -1F46E 1F3FC 200D 2640 ; minimally-qualified # 👮🏼‍♀ E4.0 woman police officer: medium-light skin tone -1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ E4.0 woman police officer: medium skin tone -1F46E 1F3FD 200D 2640 ; minimally-qualified # 👮🏽‍♀ E4.0 woman police officer: medium skin tone -1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ E4.0 woman police officer: medium-dark skin tone -1F46E 1F3FE 200D 2640 ; minimally-qualified # 👮🏾‍♀ E4.0 woman police officer: medium-dark skin tone -1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ E4.0 woman police officer: dark skin tone -1F46E 1F3FF 200D 2640 ; minimally-qualified # 👮🏿‍♀ E4.0 woman police officer: dark skin tone -1F575 FE0F ; fully-qualified # 🕵️ E0.7 detective -1F575 ; unqualified # 🕵 E0.7 detective -1F575 1F3FB ; fully-qualified # 🕵🏻 E2.0 detective: light skin tone -1F575 1F3FC ; fully-qualified # 🕵🏼 E2.0 detective: medium-light skin tone -1F575 1F3FD ; fully-qualified # 🕵🏽 E2.0 detective: medium skin tone -1F575 1F3FE ; fully-qualified # 🕵🏾 E2.0 detective: medium-dark skin tone -1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone -1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ E4.0 man detective -1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ E4.0 man detective -1F575 FE0F 200D 2642 ; minimally-qualified # 🕵️‍♂ E4.0 man detective -1F575 200D 2642 ; unqualified # 🕵‍♂ E4.0 man detective -1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ E4.0 man detective: light skin tone -1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ E4.0 man detective: light skin tone -1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ E4.0 man detective: medium-light skin tone -1F575 1F3FC 200D 2642 ; minimally-qualified # 🕵🏼‍♂ E4.0 man detective: medium-light skin tone -1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ E4.0 man detective: medium skin tone -1F575 1F3FD 200D 2642 ; minimally-qualified # 🕵🏽‍♂ E4.0 man detective: medium skin tone -1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ E4.0 man detective: medium-dark skin tone -1F575 1F3FE 200D 2642 ; minimally-qualified # 🕵🏾‍♂ E4.0 man detective: medium-dark skin tone -1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ E4.0 man detective: dark skin tone -1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone -1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ E4.0 woman detective -1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ E4.0 woman detective -1F575 FE0F 200D 2640 ; minimally-qualified # 🕵️‍♀ E4.0 woman detective -1F575 200D 2640 ; unqualified # 🕵‍♀ E4.0 woman detective -1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: light skin tone -1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ E4.0 woman detective: light skin tone -1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ E4.0 woman detective: medium-light skin tone -1F575 1F3FC 200D 2640 ; minimally-qualified # 🕵🏼‍♀ E4.0 woman detective: medium-light skin tone -1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ E4.0 woman detective: medium skin tone -1F575 1F3FD 200D 2640 ; minimally-qualified # 🕵🏽‍♀ E4.0 woman detective: medium skin tone -1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ E4.0 woman detective: medium-dark skin tone -1F575 1F3FE 200D 2640 ; minimally-qualified # 🕵🏾‍♀ E4.0 woman detective: medium-dark skin tone -1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ E4.0 woman detective: dark skin tone -1F575 1F3FF 200D 2640 ; minimally-qualified # 🕵🏿‍♀ E4.0 woman detective: dark skin tone -1F482 ; fully-qualified # 💂 E0.6 guard -1F482 1F3FB ; fully-qualified # 💂🏻 E1.0 guard: light skin tone -1F482 1F3FC ; fully-qualified # 💂🏼 E1.0 guard: medium-light skin tone -1F482 1F3FD ; fully-qualified # 💂🏽 E1.0 guard: medium skin tone -1F482 1F3FE ; fully-qualified # 💂🏾 E1.0 guard: medium-dark skin tone -1F482 1F3FF ; fully-qualified # 💂🏿 E1.0 guard: dark skin tone -1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ E4.0 man guard -1F482 200D 2642 ; minimally-qualified # 💂‍♂ E4.0 man guard -1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ E4.0 man guard: light skin tone -1F482 1F3FB 200D 2642 ; minimally-qualified # 💂🏻‍♂ E4.0 man guard: light skin tone -1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ E4.0 man guard: medium-light skin tone -1F482 1F3FC 200D 2642 ; minimally-qualified # 💂🏼‍♂ E4.0 man guard: medium-light skin tone -1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ E4.0 man guard: medium skin tone -1F482 1F3FD 200D 2642 ; minimally-qualified # 💂🏽‍♂ E4.0 man guard: medium skin tone -1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ E4.0 man guard: medium-dark skin tone -1F482 1F3FE 200D 2642 ; minimally-qualified # 💂🏾‍♂ E4.0 man guard: medium-dark skin tone -1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ E4.0 man guard: dark skin tone -1F482 1F3FF 200D 2642 ; minimally-qualified # 💂🏿‍♂ E4.0 man guard: dark skin tone -1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ E4.0 woman guard -1F482 200D 2640 ; minimally-qualified # 💂‍♀ E4.0 woman guard -1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ E4.0 woman guard: light skin tone -1F482 1F3FB 200D 2640 ; minimally-qualified # 💂🏻‍♀ E4.0 woman guard: light skin tone -1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ E4.0 woman guard: medium-light skin tone -1F482 1F3FC 200D 2640 ; minimally-qualified # 💂🏼‍♀ E4.0 woman guard: medium-light skin tone -1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ E4.0 woman guard: medium skin tone -1F482 1F3FD 200D 2640 ; minimally-qualified # 💂🏽‍♀ E4.0 woman guard: medium skin tone -1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ E4.0 woman guard: medium-dark skin tone -1F482 1F3FE 200D 2640 ; minimally-qualified # 💂🏾‍♀ E4.0 woman guard: medium-dark skin tone -1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ E4.0 woman guard: dark skin tone -1F482 1F3FF 200D 2640 ; minimally-qualified # 💂🏿‍♀ E4.0 woman guard: dark skin tone -1F977 ; fully-qualified # 🥷 E13.0 ninja -1F977 1F3FB ; fully-qualified # 🥷🏻 E13.0 ninja: light skin tone -1F977 1F3FC ; fully-qualified # 🥷🏼 E13.0 ninja: medium-light skin tone -1F977 1F3FD ; fully-qualified # 🥷🏽 E13.0 ninja: medium skin tone -1F977 1F3FE ; fully-qualified # 🥷🏾 E13.0 ninja: medium-dark skin tone -1F977 1F3FF ; fully-qualified # 🥷🏿 E13.0 ninja: dark skin tone -1F477 ; fully-qualified # 👷 E0.6 construction worker -1F477 1F3FB ; fully-qualified # 👷🏻 E1.0 construction worker: light skin tone -1F477 1F3FC ; fully-qualified # 👷🏼 E1.0 construction worker: medium-light skin tone -1F477 1F3FD ; fully-qualified # 👷🏽 E1.0 construction worker: medium skin tone -1F477 1F3FE ; fully-qualified # 👷🏾 E1.0 construction worker: medium-dark skin tone -1F477 1F3FF ; fully-qualified # 👷🏿 E1.0 construction worker: dark skin tone -1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ E4.0 man construction worker -1F477 200D 2642 ; minimally-qualified # 👷‍♂ E4.0 man construction worker -1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ E4.0 man construction worker: light skin tone -1F477 1F3FB 200D 2642 ; minimally-qualified # 👷🏻‍♂ E4.0 man construction worker: light skin tone -1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ E4.0 man construction worker: medium-light skin tone -1F477 1F3FC 200D 2642 ; minimally-qualified # 👷🏼‍♂ E4.0 man construction worker: medium-light skin tone -1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ E4.0 man construction worker: medium skin tone -1F477 1F3FD 200D 2642 ; minimally-qualified # 👷🏽‍♂ E4.0 man construction worker: medium skin tone -1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ E4.0 man construction worker: medium-dark skin tone -1F477 1F3FE 200D 2642 ; minimally-qualified # 👷🏾‍♂ E4.0 man construction worker: medium-dark skin tone -1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ E4.0 man construction worker: dark skin tone -1F477 1F3FF 200D 2642 ; minimally-qualified # 👷🏿‍♂ E4.0 man construction worker: dark skin tone -1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ E4.0 woman construction worker -1F477 200D 2640 ; minimally-qualified # 👷‍♀ E4.0 woman construction worker -1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ E4.0 woman construction worker: light skin tone -1F477 1F3FB 200D 2640 ; minimally-qualified # 👷🏻‍♀ E4.0 woman construction worker: light skin tone -1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ E4.0 woman construction worker: medium-light skin tone -1F477 1F3FC 200D 2640 ; minimally-qualified # 👷🏼‍♀ E4.0 woman construction worker: medium-light skin tone -1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ E4.0 woman construction worker: medium skin tone -1F477 1F3FD 200D 2640 ; minimally-qualified # 👷🏽‍♀ E4.0 woman construction worker: medium skin tone -1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ E4.0 woman construction worker: medium-dark skin tone -1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone -1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ E4.0 woman construction worker: dark skin tone -1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ E4.0 woman construction worker: dark skin tone -1FAC5 ; fully-qualified # 🫅 E14.0 person with crown -1FAC5 1F3FB ; fully-qualified # 🫅🏻 E14.0 person with crown: light skin tone -1FAC5 1F3FC ; fully-qualified # 🫅🏼 E14.0 person with crown: medium-light skin tone -1FAC5 1F3FD ; fully-qualified # 🫅🏽 E14.0 person with crown: medium skin tone -1FAC5 1F3FE ; fully-qualified # 🫅🏾 E14.0 person with crown: medium-dark skin tone -1FAC5 1F3FF ; fully-qualified # 🫅🏿 E14.0 person with crown: dark skin tone -1F934 ; fully-qualified # 🤴 E3.0 prince -1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone -1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone -1F934 1F3FD ; fully-qualified # 🤴🏽 E3.0 prince: medium skin tone -1F934 1F3FE ; fully-qualified # 🤴🏾 E3.0 prince: medium-dark skin tone -1F934 1F3FF ; fully-qualified # 🤴🏿 E3.0 prince: dark skin tone -1F478 ; fully-qualified # 👸 E0.6 princess -1F478 1F3FB ; fully-qualified # 👸🏻 E1.0 princess: light skin tone -1F478 1F3FC ; fully-qualified # 👸🏼 E1.0 princess: medium-light skin tone -1F478 1F3FD ; fully-qualified # 👸🏽 E1.0 princess: medium skin tone -1F478 1F3FE ; fully-qualified # 👸🏾 E1.0 princess: medium-dark skin tone -1F478 1F3FF ; fully-qualified # 👸🏿 E1.0 princess: dark skin tone -1F473 ; fully-qualified # 👳 E0.6 person wearing turban -1F473 1F3FB ; fully-qualified # 👳🏻 E1.0 person wearing turban: light skin tone -1F473 1F3FC ; fully-qualified # 👳🏼 E1.0 person wearing turban: medium-light skin tone -1F473 1F3FD ; fully-qualified # 👳🏽 E1.0 person wearing turban: medium skin tone -1F473 1F3FE ; fully-qualified # 👳🏾 E1.0 person wearing turban: medium-dark skin tone -1F473 1F3FF ; fully-qualified # 👳🏿 E1.0 person wearing turban: dark skin tone -1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ E4.0 man wearing turban -1F473 200D 2642 ; minimally-qualified # 👳‍♂ E4.0 man wearing turban -1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ E4.0 man wearing turban: light skin tone -1F473 1F3FB 200D 2642 ; minimally-qualified # 👳🏻‍♂ E4.0 man wearing turban: light skin tone -1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ E4.0 man wearing turban: medium-light skin tone -1F473 1F3FC 200D 2642 ; minimally-qualified # 👳🏼‍♂ E4.0 man wearing turban: medium-light skin tone -1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ E4.0 man wearing turban: medium skin tone -1F473 1F3FD 200D 2642 ; minimally-qualified # 👳🏽‍♂ E4.0 man wearing turban: medium skin tone -1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ E4.0 man wearing turban: medium-dark skin tone -1F473 1F3FE 200D 2642 ; minimally-qualified # 👳🏾‍♂ E4.0 man wearing turban: medium-dark skin tone -1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ E4.0 man wearing turban: dark skin tone -1F473 1F3FF 200D 2642 ; minimally-qualified # 👳🏿‍♂ E4.0 man wearing turban: dark skin tone -1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ E4.0 woman wearing turban -1F473 200D 2640 ; minimally-qualified # 👳‍♀ E4.0 woman wearing turban -1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ E4.0 woman wearing turban: light skin tone -1F473 1F3FB 200D 2640 ; minimally-qualified # 👳🏻‍♀ E4.0 woman wearing turban: light skin tone -1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ E4.0 woman wearing turban: medium-light skin tone -1F473 1F3FC 200D 2640 ; minimally-qualified # 👳🏼‍♀ E4.0 woman wearing turban: medium-light skin tone -1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ E4.0 woman wearing turban: medium skin tone -1F473 1F3FD 200D 2640 ; minimally-qualified # 👳🏽‍♀ E4.0 woman wearing turban: medium skin tone -1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ E4.0 woman wearing turban: medium-dark skin tone -1F473 1F3FE 200D 2640 ; minimally-qualified # 👳🏾‍♀ E4.0 woman wearing turban: medium-dark skin tone -1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ E4.0 woman wearing turban: dark skin tone -1F473 1F3FF 200D 2640 ; minimally-qualified # 👳🏿‍♀ E4.0 woman wearing turban: dark skin tone -1F472 ; fully-qualified # 👲 E0.6 person with skullcap -1F472 1F3FB ; fully-qualified # 👲🏻 E1.0 person with skullcap: light skin tone -1F472 1F3FC ; fully-qualified # 👲🏼 E1.0 person with skullcap: medium-light skin tone -1F472 1F3FD ; fully-qualified # 👲🏽 E1.0 person with skullcap: medium skin tone -1F472 1F3FE ; fully-qualified # 👲🏾 E1.0 person with skullcap: medium-dark skin tone -1F472 1F3FF ; fully-qualified # 👲🏿 E1.0 person with skullcap: dark skin tone -1F9D5 ; fully-qualified # 🧕 E5.0 woman with headscarf -1F9D5 1F3FB ; fully-qualified # 🧕🏻 E5.0 woman with headscarf: light skin tone -1F9D5 1F3FC ; fully-qualified # 🧕🏼 E5.0 woman with headscarf: medium-light skin tone -1F9D5 1F3FD ; fully-qualified # 🧕🏽 E5.0 woman with headscarf: medium skin tone -1F9D5 1F3FE ; fully-qualified # 🧕🏾 E5.0 woman with headscarf: medium-dark skin tone -1F9D5 1F3FF ; fully-qualified # 🧕🏿 E5.0 woman with headscarf: dark skin tone -1F935 ; fully-qualified # 🤵 E3.0 person in tuxedo -1F935 1F3FB ; fully-qualified # 🤵🏻 E3.0 person in tuxedo: light skin tone -1F935 1F3FC ; fully-qualified # 🤵🏼 E3.0 person in tuxedo: medium-light skin tone -1F935 1F3FD ; fully-qualified # 🤵🏽 E3.0 person in tuxedo: medium skin tone -1F935 1F3FE ; fully-qualified # 🤵🏾 E3.0 person in tuxedo: medium-dark skin tone -1F935 1F3FF ; fully-qualified # 🤵🏿 E3.0 person in tuxedo: dark skin tone -1F935 200D 2642 FE0F ; fully-qualified # 🤵‍♂️ E13.0 man in tuxedo -1F935 200D 2642 ; minimally-qualified # 🤵‍♂ E13.0 man in tuxedo -1F935 1F3FB 200D 2642 FE0F ; fully-qualified # 🤵🏻‍♂️ E13.0 man in tuxedo: light skin tone -1F935 1F3FB 200D 2642 ; minimally-qualified # 🤵🏻‍♂ E13.0 man in tuxedo: light skin tone -1F935 1F3FC 200D 2642 FE0F ; fully-qualified # 🤵🏼‍♂️ E13.0 man in tuxedo: medium-light skin tone -1F935 1F3FC 200D 2642 ; minimally-qualified # 🤵🏼‍♂ E13.0 man in tuxedo: medium-light skin tone -1F935 1F3FD 200D 2642 FE0F ; fully-qualified # 🤵🏽‍♂️ E13.0 man in tuxedo: medium skin tone -1F935 1F3FD 200D 2642 ; minimally-qualified # 🤵🏽‍♂ E13.0 man in tuxedo: medium skin tone -1F935 1F3FE 200D 2642 FE0F ; fully-qualified # 🤵🏾‍♂️ E13.0 man in tuxedo: medium-dark skin tone -1F935 1F3FE 200D 2642 ; minimally-qualified # 🤵🏾‍♂ E13.0 man in tuxedo: medium-dark skin tone -1F935 1F3FF 200D 2642 FE0F ; fully-qualified # 🤵🏿‍♂️ E13.0 man in tuxedo: dark skin tone -1F935 1F3FF 200D 2642 ; minimally-qualified # 🤵🏿‍♂ E13.0 man in tuxedo: dark skin tone -1F935 200D 2640 FE0F ; fully-qualified # 🤵‍♀️ E13.0 woman in tuxedo -1F935 200D 2640 ; minimally-qualified # 🤵‍♀ E13.0 woman in tuxedo -1F935 1F3FB 200D 2640 FE0F ; fully-qualified # 🤵🏻‍♀️ E13.0 woman in tuxedo: light skin tone -1F935 1F3FB 200D 2640 ; minimally-qualified # 🤵🏻‍♀ E13.0 woman in tuxedo: light skin tone -1F935 1F3FC 200D 2640 FE0F ; fully-qualified # 🤵🏼‍♀️ E13.0 woman in tuxedo: medium-light skin tone -1F935 1F3FC 200D 2640 ; minimally-qualified # 🤵🏼‍♀ E13.0 woman in tuxedo: medium-light skin tone -1F935 1F3FD 200D 2640 FE0F ; fully-qualified # 🤵🏽‍♀️ E13.0 woman in tuxedo: medium skin tone -1F935 1F3FD 200D 2640 ; minimally-qualified # 🤵🏽‍♀ E13.0 woman in tuxedo: medium skin tone -1F935 1F3FE 200D 2640 FE0F ; fully-qualified # 🤵🏾‍♀️ E13.0 woman in tuxedo: medium-dark skin tone -1F935 1F3FE 200D 2640 ; minimally-qualified # 🤵🏾‍♀ E13.0 woman in tuxedo: medium-dark skin tone -1F935 1F3FF 200D 2640 FE0F ; fully-qualified # 🤵🏿‍♀️ E13.0 woman in tuxedo: dark skin tone -1F935 1F3FF 200D 2640 ; minimally-qualified # 🤵🏿‍♀ E13.0 woman in tuxedo: dark skin tone -1F470 ; fully-qualified # 👰 E0.6 person with veil -1F470 1F3FB ; fully-qualified # 👰🏻 E1.0 person with veil: light skin tone -1F470 1F3FC ; fully-qualified # 👰🏼 E1.0 person with veil: medium-light skin tone -1F470 1F3FD ; fully-qualified # 👰🏽 E1.0 person with veil: medium skin tone -1F470 1F3FE ; fully-qualified # 👰🏾 E1.0 person with veil: medium-dark skin tone -1F470 1F3FF ; fully-qualified # 👰🏿 E1.0 person with veil: dark skin tone -1F470 200D 2642 FE0F ; fully-qualified # 👰‍♂️ E13.0 man with veil -1F470 200D 2642 ; minimally-qualified # 👰‍♂ E13.0 man with veil -1F470 1F3FB 200D 2642 FE0F ; fully-qualified # 👰🏻‍♂️ E13.0 man with veil: light skin tone -1F470 1F3FB 200D 2642 ; minimally-qualified # 👰🏻‍♂ E13.0 man with veil: light skin tone -1F470 1F3FC 200D 2642 FE0F ; fully-qualified # 👰🏼‍♂️ E13.0 man with veil: medium-light skin tone -1F470 1F3FC 200D 2642 ; minimally-qualified # 👰🏼‍♂ E13.0 man with veil: medium-light skin tone -1F470 1F3FD 200D 2642 FE0F ; fully-qualified # 👰🏽‍♂️ E13.0 man with veil: medium skin tone -1F470 1F3FD 200D 2642 ; minimally-qualified # 👰🏽‍♂ E13.0 man with veil: medium skin tone -1F470 1F3FE 200D 2642 FE0F ; fully-qualified # 👰🏾‍♂️ E13.0 man with veil: medium-dark skin tone -1F470 1F3FE 200D 2642 ; minimally-qualified # 👰🏾‍♂ E13.0 man with veil: medium-dark skin tone -1F470 1F3FF 200D 2642 FE0F ; fully-qualified # 👰🏿‍♂️ E13.0 man with veil: dark skin tone -1F470 1F3FF 200D 2642 ; minimally-qualified # 👰🏿‍♂ E13.0 man with veil: dark skin tone -1F470 200D 2640 FE0F ; fully-qualified # 👰‍♀️ E13.0 woman with veil -1F470 200D 2640 ; minimally-qualified # 👰‍♀ E13.0 woman with veil -1F470 1F3FB 200D 2640 FE0F ; fully-qualified # 👰🏻‍♀️ E13.0 woman with veil: light skin tone -1F470 1F3FB 200D 2640 ; minimally-qualified # 👰🏻‍♀ E13.0 woman with veil: light skin tone -1F470 1F3FC 200D 2640 FE0F ; fully-qualified # 👰🏼‍♀️ E13.0 woman with veil: medium-light skin tone -1F470 1F3FC 200D 2640 ; minimally-qualified # 👰🏼‍♀ E13.0 woman with veil: medium-light skin tone -1F470 1F3FD 200D 2640 FE0F ; fully-qualified # 👰🏽‍♀️ E13.0 woman with veil: medium skin tone -1F470 1F3FD 200D 2640 ; minimally-qualified # 👰🏽‍♀ E13.0 woman with veil: medium skin tone -1F470 1F3FE 200D 2640 FE0F ; fully-qualified # 👰🏾‍♀️ E13.0 woman with veil: medium-dark skin tone -1F470 1F3FE 200D 2640 ; minimally-qualified # 👰🏾‍♀ E13.0 woman with veil: medium-dark skin tone -1F470 1F3FF 200D 2640 FE0F ; fully-qualified # 👰🏿‍♀️ E13.0 woman with veil: dark skin tone -1F470 1F3FF 200D 2640 ; minimally-qualified # 👰🏿‍♀ E13.0 woman with veil: dark skin tone -1F930 ; fully-qualified # 🤰 E3.0 pregnant woman -1F930 1F3FB ; fully-qualified # 🤰🏻 E3.0 pregnant woman: light skin tone -1F930 1F3FC ; fully-qualified # 🤰🏼 E3.0 pregnant woman: medium-light skin tone -1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone -1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone -1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone -1FAC3 ; fully-qualified # 🫃 E14.0 pregnant man -1FAC3 1F3FB ; fully-qualified # 🫃🏻 E14.0 pregnant man: light skin tone -1FAC3 1F3FC ; fully-qualified # 🫃🏼 E14.0 pregnant man: medium-light skin tone -1FAC3 1F3FD ; fully-qualified # 🫃🏽 E14.0 pregnant man: medium skin tone -1FAC3 1F3FE ; fully-qualified # 🫃🏾 E14.0 pregnant man: medium-dark skin tone -1FAC3 1F3FF ; fully-qualified # 🫃🏿 E14.0 pregnant man: dark skin tone -1FAC4 ; fully-qualified # 🫄 E14.0 pregnant person -1FAC4 1F3FB ; fully-qualified # 🫄🏻 E14.0 pregnant person: light skin tone -1FAC4 1F3FC ; fully-qualified # 🫄🏼 E14.0 pregnant person: medium-light skin tone -1FAC4 1F3FD ; fully-qualified # 🫄🏽 E14.0 pregnant person: medium skin tone -1FAC4 1F3FE ; fully-qualified # 🫄🏾 E14.0 pregnant person: medium-dark skin tone -1FAC4 1F3FF ; fully-qualified # 🫄🏿 E14.0 pregnant person: dark skin tone -1F931 ; fully-qualified # 🤱 E5.0 breast-feeding -1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone -1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone -1F931 1F3FD ; fully-qualified # 🤱🏽 E5.0 breast-feeding: medium skin tone -1F931 1F3FE ; fully-qualified # 🤱🏾 E5.0 breast-feeding: medium-dark skin tone -1F931 1F3FF ; fully-qualified # 🤱🏿 E5.0 breast-feeding: dark skin tone -1F469 200D 1F37C ; fully-qualified # 👩‍🍼 E13.0 woman feeding baby -1F469 1F3FB 200D 1F37C ; fully-qualified # 👩🏻‍🍼 E13.0 woman feeding baby: light skin tone -1F469 1F3FC 200D 1F37C ; fully-qualified # 👩🏼‍🍼 E13.0 woman feeding baby: medium-light skin tone -1F469 1F3FD 200D 1F37C ; fully-qualified # 👩🏽‍🍼 E13.0 woman feeding baby: medium skin tone -1F469 1F3FE 200D 1F37C ; fully-qualified # 👩🏾‍🍼 E13.0 woman feeding baby: medium-dark skin tone -1F469 1F3FF 200D 1F37C ; fully-qualified # 👩🏿‍🍼 E13.0 woman feeding baby: dark skin tone -1F468 200D 1F37C ; fully-qualified # 👨‍🍼 E13.0 man feeding baby -1F468 1F3FB 200D 1F37C ; fully-qualified # 👨🏻‍🍼 E13.0 man feeding baby: light skin tone -1F468 1F3FC 200D 1F37C ; fully-qualified # 👨🏼‍🍼 E13.0 man feeding baby: medium-light skin tone -1F468 1F3FD 200D 1F37C ; fully-qualified # 👨🏽‍🍼 E13.0 man feeding baby: medium skin tone -1F468 1F3FE 200D 1F37C ; fully-qualified # 👨🏾‍🍼 E13.0 man feeding baby: medium-dark skin tone -1F468 1F3FF 200D 1F37C ; fully-qualified # 👨🏿‍🍼 E13.0 man feeding baby: dark skin tone -1F9D1 200D 1F37C ; fully-qualified # 🧑‍🍼 E13.0 person feeding baby -1F9D1 1F3FB 200D 1F37C ; fully-qualified # 🧑🏻‍🍼 E13.0 person feeding baby: light skin tone -1F9D1 1F3FC 200D 1F37C ; fully-qualified # 🧑🏼‍🍼 E13.0 person feeding baby: medium-light skin tone -1F9D1 1F3FD 200D 1F37C ; fully-qualified # 🧑🏽‍🍼 E13.0 person feeding baby: medium skin tone -1F9D1 1F3FE 200D 1F37C ; fully-qualified # 🧑🏾‍🍼 E13.0 person feeding baby: medium-dark skin tone -1F9D1 1F3FF 200D 1F37C ; fully-qualified # 🧑🏿‍🍼 E13.0 person feeding baby: dark skin tone - -# subgroup: person-fantasy -1F47C ; fully-qualified # 👼 E0.6 baby angel -1F47C 1F3FB ; fully-qualified # 👼🏻 E1.0 baby angel: light skin tone -1F47C 1F3FC ; fully-qualified # 👼🏼 E1.0 baby angel: medium-light skin tone -1F47C 1F3FD ; fully-qualified # 👼🏽 E1.0 baby angel: medium skin tone -1F47C 1F3FE ; fully-qualified # 👼🏾 E1.0 baby angel: medium-dark skin tone -1F47C 1F3FF ; fully-qualified # 👼🏿 E1.0 baby angel: dark skin tone -1F385 ; fully-qualified # 🎅 E0.6 Santa Claus -1F385 1F3FB ; fully-qualified # 🎅🏻 E1.0 Santa Claus: light skin tone -1F385 1F3FC ; fully-qualified # 🎅🏼 E1.0 Santa Claus: medium-light skin tone -1F385 1F3FD ; fully-qualified # 🎅🏽 E1.0 Santa Claus: medium skin tone -1F385 1F3FE ; fully-qualified # 🎅🏾 E1.0 Santa Claus: medium-dark skin tone -1F385 1F3FF ; fully-qualified # 🎅🏿 E1.0 Santa Claus: dark skin tone -1F936 ; fully-qualified # 🤶 E3.0 Mrs. Claus -1F936 1F3FB ; fully-qualified # 🤶🏻 E3.0 Mrs. Claus: light skin tone -1F936 1F3FC ; fully-qualified # 🤶🏼 E3.0 Mrs. Claus: medium-light skin tone -1F936 1F3FD ; fully-qualified # 🤶🏽 E3.0 Mrs. Claus: medium skin tone -1F936 1F3FE ; fully-qualified # 🤶🏾 E3.0 Mrs. Claus: medium-dark skin tone -1F936 1F3FF ; fully-qualified # 🤶🏿 E3.0 Mrs. Claus: dark skin tone -1F9D1 200D 1F384 ; fully-qualified # 🧑‍🎄 E13.0 mx claus -1F9D1 1F3FB 200D 1F384 ; fully-qualified # 🧑🏻‍🎄 E13.0 mx claus: light skin tone -1F9D1 1F3FC 200D 1F384 ; fully-qualified # 🧑🏼‍🎄 E13.0 mx claus: medium-light skin tone -1F9D1 1F3FD 200D 1F384 ; fully-qualified # 🧑🏽‍🎄 E13.0 mx claus: medium skin tone -1F9D1 1F3FE 200D 1F384 ; fully-qualified # 🧑🏾‍🎄 E13.0 mx claus: medium-dark skin tone -1F9D1 1F3FF 200D 1F384 ; fully-qualified # 🧑🏿‍🎄 E13.0 mx claus: dark skin tone -1F9B8 ; fully-qualified # 🦸 E11.0 superhero -1F9B8 1F3FB ; fully-qualified # 🦸🏻 E11.0 superhero: light skin tone -1F9B8 1F3FC ; fully-qualified # 🦸🏼 E11.0 superhero: medium-light skin tone -1F9B8 1F3FD ; fully-qualified # 🦸🏽 E11.0 superhero: medium skin tone -1F9B8 1F3FE ; fully-qualified # 🦸🏾 E11.0 superhero: medium-dark skin tone -1F9B8 1F3FF ; fully-qualified # 🦸🏿 E11.0 superhero: dark skin tone -1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ E11.0 man superhero -1F9B8 200D 2642 ; minimally-qualified # 🦸‍♂ E11.0 man superhero -1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ E11.0 man superhero: light skin tone -1F9B8 1F3FB 200D 2642 ; minimally-qualified # 🦸🏻‍♂ E11.0 man superhero: light skin tone -1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ E11.0 man superhero: medium-light skin tone -1F9B8 1F3FC 200D 2642 ; minimally-qualified # 🦸🏼‍♂ E11.0 man superhero: medium-light skin tone -1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ E11.0 man superhero: medium skin tone -1F9B8 1F3FD 200D 2642 ; minimally-qualified # 🦸🏽‍♂ E11.0 man superhero: medium skin tone -1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ E11.0 man superhero: medium-dark skin tone -1F9B8 1F3FE 200D 2642 ; minimally-qualified # 🦸🏾‍♂ E11.0 man superhero: medium-dark skin tone -1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ E11.0 man superhero: dark skin tone -1F9B8 1F3FF 200D 2642 ; minimally-qualified # 🦸🏿‍♂ E11.0 man superhero: dark skin tone -1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ E11.0 woman superhero -1F9B8 200D 2640 ; minimally-qualified # 🦸‍♀ E11.0 woman superhero -1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ E11.0 woman superhero: light skin tone -1F9B8 1F3FB 200D 2640 ; minimally-qualified # 🦸🏻‍♀ E11.0 woman superhero: light skin tone -1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ E11.0 woman superhero: medium-light skin tone -1F9B8 1F3FC 200D 2640 ; minimally-qualified # 🦸🏼‍♀ E11.0 woman superhero: medium-light skin tone -1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ E11.0 woman superhero: medium skin tone -1F9B8 1F3FD 200D 2640 ; minimally-qualified # 🦸🏽‍♀ E11.0 woman superhero: medium skin tone -1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ E11.0 woman superhero: medium-dark skin tone -1F9B8 1F3FE 200D 2640 ; minimally-qualified # 🦸🏾‍♀ E11.0 woman superhero: medium-dark skin tone -1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ E11.0 woman superhero: dark skin tone -1F9B8 1F3FF 200D 2640 ; minimally-qualified # 🦸🏿‍♀ E11.0 woman superhero: dark skin tone -1F9B9 ; fully-qualified # 🦹 E11.0 supervillain -1F9B9 1F3FB ; fully-qualified # 🦹🏻 E11.0 supervillain: light skin tone -1F9B9 1F3FC ; fully-qualified # 🦹🏼 E11.0 supervillain: medium-light skin tone -1F9B9 1F3FD ; fully-qualified # 🦹🏽 E11.0 supervillain: medium skin tone -1F9B9 1F3FE ; fully-qualified # 🦹🏾 E11.0 supervillain: medium-dark skin tone -1F9B9 1F3FF ; fully-qualified # 🦹🏿 E11.0 supervillain: dark skin tone -1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ E11.0 man supervillain -1F9B9 200D 2642 ; minimally-qualified # 🦹‍♂ E11.0 man supervillain -1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ E11.0 man supervillain: light skin tone -1F9B9 1F3FB 200D 2642 ; minimally-qualified # 🦹🏻‍♂ E11.0 man supervillain: light skin tone -1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ E11.0 man supervillain: medium-light skin tone -1F9B9 1F3FC 200D 2642 ; minimally-qualified # 🦹🏼‍♂ E11.0 man supervillain: medium-light skin tone -1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ E11.0 man supervillain: medium skin tone -1F9B9 1F3FD 200D 2642 ; minimally-qualified # 🦹🏽‍♂ E11.0 man supervillain: medium skin tone -1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ E11.0 man supervillain: medium-dark skin tone -1F9B9 1F3FE 200D 2642 ; minimally-qualified # 🦹🏾‍♂ E11.0 man supervillain: medium-dark skin tone -1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ E11.0 man supervillain: dark skin tone -1F9B9 1F3FF 200D 2642 ; minimally-qualified # 🦹🏿‍♂ E11.0 man supervillain: dark skin tone -1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ E11.0 woman supervillain -1F9B9 200D 2640 ; minimally-qualified # 🦹‍♀ E11.0 woman supervillain -1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ E11.0 woman supervillain: light skin tone -1F9B9 1F3FB 200D 2640 ; minimally-qualified # 🦹🏻‍♀ E11.0 woman supervillain: light skin tone -1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ E11.0 woman supervillain: medium-light skin tone -1F9B9 1F3FC 200D 2640 ; minimally-qualified # 🦹🏼‍♀ E11.0 woman supervillain: medium-light skin tone -1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ E11.0 woman supervillain: medium skin tone -1F9B9 1F3FD 200D 2640 ; minimally-qualified # 🦹🏽‍♀ E11.0 woman supervillain: medium skin tone -1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ E11.0 woman supervillain: medium-dark skin tone -1F9B9 1F3FE 200D 2640 ; minimally-qualified # 🦹🏾‍♀ E11.0 woman supervillain: medium-dark skin tone -1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ E11.0 woman supervillain: dark skin tone -1F9B9 1F3FF 200D 2640 ; minimally-qualified # 🦹🏿‍♀ E11.0 woman supervillain: dark skin tone -1F9D9 ; fully-qualified # 🧙 E5.0 mage -1F9D9 1F3FB ; fully-qualified # 🧙🏻 E5.0 mage: light skin tone -1F9D9 1F3FC ; fully-qualified # 🧙🏼 E5.0 mage: medium-light skin tone -1F9D9 1F3FD ; fully-qualified # 🧙🏽 E5.0 mage: medium skin tone -1F9D9 1F3FE ; fully-qualified # 🧙🏾 E5.0 mage: medium-dark skin tone -1F9D9 1F3FF ; fully-qualified # 🧙🏿 E5.0 mage: dark skin tone -1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ E5.0 man mage -1F9D9 200D 2642 ; minimally-qualified # 🧙‍♂ E5.0 man mage -1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ E5.0 man mage: light skin tone -1F9D9 1F3FB 200D 2642 ; minimally-qualified # 🧙🏻‍♂ E5.0 man mage: light skin tone -1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ E5.0 man mage: medium-light skin tone -1F9D9 1F3FC 200D 2642 ; minimally-qualified # 🧙🏼‍♂ E5.0 man mage: medium-light skin tone -1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ E5.0 man mage: medium skin tone -1F9D9 1F3FD 200D 2642 ; minimally-qualified # 🧙🏽‍♂ E5.0 man mage: medium skin tone -1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ E5.0 man mage: medium-dark skin tone -1F9D9 1F3FE 200D 2642 ; minimally-qualified # 🧙🏾‍♂ E5.0 man mage: medium-dark skin tone -1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ E5.0 man mage: dark skin tone -1F9D9 1F3FF 200D 2642 ; minimally-qualified # 🧙🏿‍♂ E5.0 man mage: dark skin tone -1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ E5.0 woman mage -1F9D9 200D 2640 ; minimally-qualified # 🧙‍♀ E5.0 woman mage -1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ E5.0 woman mage: light skin tone -1F9D9 1F3FB 200D 2640 ; minimally-qualified # 🧙🏻‍♀ E5.0 woman mage: light skin tone -1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ E5.0 woman mage: medium-light skin tone -1F9D9 1F3FC 200D 2640 ; minimally-qualified # 🧙🏼‍♀ E5.0 woman mage: medium-light skin tone -1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ E5.0 woman mage: medium skin tone -1F9D9 1F3FD 200D 2640 ; minimally-qualified # 🧙🏽‍♀ E5.0 woman mage: medium skin tone -1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ E5.0 woman mage: medium-dark skin tone -1F9D9 1F3FE 200D 2640 ; minimally-qualified # 🧙🏾‍♀ E5.0 woman mage: medium-dark skin tone -1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ E5.0 woman mage: dark skin tone -1F9D9 1F3FF 200D 2640 ; minimally-qualified # 🧙🏿‍♀ E5.0 woman mage: dark skin tone -1F9DA ; fully-qualified # 🧚 E5.0 fairy -1F9DA 1F3FB ; fully-qualified # 🧚🏻 E5.0 fairy: light skin tone -1F9DA 1F3FC ; fully-qualified # 🧚🏼 E5.0 fairy: medium-light skin tone -1F9DA 1F3FD ; fully-qualified # 🧚🏽 E5.0 fairy: medium skin tone -1F9DA 1F3FE ; fully-qualified # 🧚🏾 E5.0 fairy: medium-dark skin tone -1F9DA 1F3FF ; fully-qualified # 🧚🏿 E5.0 fairy: dark skin tone -1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ E5.0 man fairy -1F9DA 200D 2642 ; minimally-qualified # 🧚‍♂ E5.0 man fairy -1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ E5.0 man fairy: light skin tone -1F9DA 1F3FB 200D 2642 ; minimally-qualified # 🧚🏻‍♂ E5.0 man fairy: light skin tone -1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ E5.0 man fairy: medium-light skin tone -1F9DA 1F3FC 200D 2642 ; minimally-qualified # 🧚🏼‍♂ E5.0 man fairy: medium-light skin tone -1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ E5.0 man fairy: medium skin tone -1F9DA 1F3FD 200D 2642 ; minimally-qualified # 🧚🏽‍♂ E5.0 man fairy: medium skin tone -1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ E5.0 man fairy: medium-dark skin tone -1F9DA 1F3FE 200D 2642 ; minimally-qualified # 🧚🏾‍♂ E5.0 man fairy: medium-dark skin tone -1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ E5.0 man fairy: dark skin tone -1F9DA 1F3FF 200D 2642 ; minimally-qualified # 🧚🏿‍♂ E5.0 man fairy: dark skin tone -1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ E5.0 woman fairy -1F9DA 200D 2640 ; minimally-qualified # 🧚‍♀ E5.0 woman fairy -1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ E5.0 woman fairy: light skin tone -1F9DA 1F3FB 200D 2640 ; minimally-qualified # 🧚🏻‍♀ E5.0 woman fairy: light skin tone -1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ E5.0 woman fairy: medium-light skin tone -1F9DA 1F3FC 200D 2640 ; minimally-qualified # 🧚🏼‍♀ E5.0 woman fairy: medium-light skin tone -1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ E5.0 woman fairy: medium skin tone -1F9DA 1F3FD 200D 2640 ; minimally-qualified # 🧚🏽‍♀ E5.0 woman fairy: medium skin tone -1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ E5.0 woman fairy: medium-dark skin tone -1F9DA 1F3FE 200D 2640 ; minimally-qualified # 🧚🏾‍♀ E5.0 woman fairy: medium-dark skin tone -1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ E5.0 woman fairy: dark skin tone -1F9DA 1F3FF 200D 2640 ; minimally-qualified # 🧚🏿‍♀ E5.0 woman fairy: dark skin tone -1F9DB ; fully-qualified # 🧛 E5.0 vampire -1F9DB 1F3FB ; fully-qualified # 🧛🏻 E5.0 vampire: light skin tone -1F9DB 1F3FC ; fully-qualified # 🧛🏼 E5.0 vampire: medium-light skin tone -1F9DB 1F3FD ; fully-qualified # 🧛🏽 E5.0 vampire: medium skin tone -1F9DB 1F3FE ; fully-qualified # 🧛🏾 E5.0 vampire: medium-dark skin tone -1F9DB 1F3FF ; fully-qualified # 🧛🏿 E5.0 vampire: dark skin tone -1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ E5.0 man vampire -1F9DB 200D 2642 ; minimally-qualified # 🧛‍♂ E5.0 man vampire -1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ E5.0 man vampire: light skin tone -1F9DB 1F3FB 200D 2642 ; minimally-qualified # 🧛🏻‍♂ E5.0 man vampire: light skin tone -1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ E5.0 man vampire: medium-light skin tone -1F9DB 1F3FC 200D 2642 ; minimally-qualified # 🧛🏼‍♂ E5.0 man vampire: medium-light skin tone -1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ E5.0 man vampire: medium skin tone -1F9DB 1F3FD 200D 2642 ; minimally-qualified # 🧛🏽‍♂ E5.0 man vampire: medium skin tone -1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ E5.0 man vampire: medium-dark skin tone -1F9DB 1F3FE 200D 2642 ; minimally-qualified # 🧛🏾‍♂ E5.0 man vampire: medium-dark skin tone -1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ E5.0 man vampire: dark skin tone -1F9DB 1F3FF 200D 2642 ; minimally-qualified # 🧛🏿‍♂ E5.0 man vampire: dark skin tone -1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ E5.0 woman vampire -1F9DB 200D 2640 ; minimally-qualified # 🧛‍♀ E5.0 woman vampire -1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ E5.0 woman vampire: light skin tone -1F9DB 1F3FB 200D 2640 ; minimally-qualified # 🧛🏻‍♀ E5.0 woman vampire: light skin tone -1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ E5.0 woman vampire: medium-light skin tone -1F9DB 1F3FC 200D 2640 ; minimally-qualified # 🧛🏼‍♀ E5.0 woman vampire: medium-light skin tone -1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ E5.0 woman vampire: medium skin tone -1F9DB 1F3FD 200D 2640 ; minimally-qualified # 🧛🏽‍♀ E5.0 woman vampire: medium skin tone -1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ E5.0 woman vampire: medium-dark skin tone -1F9DB 1F3FE 200D 2640 ; minimally-qualified # 🧛🏾‍♀ E5.0 woman vampire: medium-dark skin tone -1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ E5.0 woman vampire: dark skin tone -1F9DB 1F3FF 200D 2640 ; minimally-qualified # 🧛🏿‍♀ E5.0 woman vampire: dark skin tone -1F9DC ; fully-qualified # 🧜 E5.0 merperson -1F9DC 1F3FB ; fully-qualified # 🧜🏻 E5.0 merperson: light skin tone -1F9DC 1F3FC ; fully-qualified # 🧜🏼 E5.0 merperson: medium-light skin tone -1F9DC 1F3FD ; fully-qualified # 🧜🏽 E5.0 merperson: medium skin tone -1F9DC 1F3FE ; fully-qualified # 🧜🏾 E5.0 merperson: medium-dark skin tone -1F9DC 1F3FF ; fully-qualified # 🧜🏿 E5.0 merperson: dark skin tone -1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ E5.0 merman -1F9DC 200D 2642 ; minimally-qualified # 🧜‍♂ E5.0 merman -1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ E5.0 merman: light skin tone -1F9DC 1F3FB 200D 2642 ; minimally-qualified # 🧜🏻‍♂ E5.0 merman: light skin tone -1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ E5.0 merman: medium-light skin tone -1F9DC 1F3FC 200D 2642 ; minimally-qualified # 🧜🏼‍♂ E5.0 merman: medium-light skin tone -1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ E5.0 merman: medium skin tone -1F9DC 1F3FD 200D 2642 ; minimally-qualified # 🧜🏽‍♂ E5.0 merman: medium skin tone -1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ E5.0 merman: medium-dark skin tone -1F9DC 1F3FE 200D 2642 ; minimally-qualified # 🧜🏾‍♂ E5.0 merman: medium-dark skin tone -1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ E5.0 merman: dark skin tone -1F9DC 1F3FF 200D 2642 ; minimally-qualified # 🧜🏿‍♂ E5.0 merman: dark skin tone -1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ E5.0 mermaid -1F9DC 200D 2640 ; minimally-qualified # 🧜‍♀ E5.0 mermaid -1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ E5.0 mermaid: light skin tone -1F9DC 1F3FB 200D 2640 ; minimally-qualified # 🧜🏻‍♀ E5.0 mermaid: light skin tone -1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ E5.0 mermaid: medium-light skin tone -1F9DC 1F3FC 200D 2640 ; minimally-qualified # 🧜🏼‍♀ E5.0 mermaid: medium-light skin tone -1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ E5.0 mermaid: medium skin tone -1F9DC 1F3FD 200D 2640 ; minimally-qualified # 🧜🏽‍♀ E5.0 mermaid: medium skin tone -1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ E5.0 mermaid: medium-dark skin tone -1F9DC 1F3FE 200D 2640 ; minimally-qualified # 🧜🏾‍♀ E5.0 mermaid: medium-dark skin tone -1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ E5.0 mermaid: dark skin tone -1F9DC 1F3FF 200D 2640 ; minimally-qualified # 🧜🏿‍♀ E5.0 mermaid: dark skin tone -1F9DD ; fully-qualified # 🧝 E5.0 elf -1F9DD 1F3FB ; fully-qualified # 🧝🏻 E5.0 elf: light skin tone -1F9DD 1F3FC ; fully-qualified # 🧝🏼 E5.0 elf: medium-light skin tone -1F9DD 1F3FD ; fully-qualified # 🧝🏽 E5.0 elf: medium skin tone -1F9DD 1F3FE ; fully-qualified # 🧝🏾 E5.0 elf: medium-dark skin tone -1F9DD 1F3FF ; fully-qualified # 🧝🏿 E5.0 elf: dark skin tone -1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ E5.0 man elf -1F9DD 200D 2642 ; minimally-qualified # 🧝‍♂ E5.0 man elf -1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ E5.0 man elf: light skin tone -1F9DD 1F3FB 200D 2642 ; minimally-qualified # 🧝🏻‍♂ E5.0 man elf: light skin tone -1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ E5.0 man elf: medium-light skin tone -1F9DD 1F3FC 200D 2642 ; minimally-qualified # 🧝🏼‍♂ E5.0 man elf: medium-light skin tone -1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ E5.0 man elf: medium skin tone -1F9DD 1F3FD 200D 2642 ; minimally-qualified # 🧝🏽‍♂ E5.0 man elf: medium skin tone -1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ E5.0 man elf: medium-dark skin tone -1F9DD 1F3FE 200D 2642 ; minimally-qualified # 🧝🏾‍♂ E5.0 man elf: medium-dark skin tone -1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ E5.0 man elf: dark skin tone -1F9DD 1F3FF 200D 2642 ; minimally-qualified # 🧝🏿‍♂ E5.0 man elf: dark skin tone -1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ E5.0 woman elf -1F9DD 200D 2640 ; minimally-qualified # 🧝‍♀ E5.0 woman elf -1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ E5.0 woman elf: light skin tone -1F9DD 1F3FB 200D 2640 ; minimally-qualified # 🧝🏻‍♀ E5.0 woman elf: light skin tone -1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ E5.0 woman elf: medium-light skin tone -1F9DD 1F3FC 200D 2640 ; minimally-qualified # 🧝🏼‍♀ E5.0 woman elf: medium-light skin tone -1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ E5.0 woman elf: medium skin tone -1F9DD 1F3FD 200D 2640 ; minimally-qualified # 🧝🏽‍♀ E5.0 woman elf: medium skin tone -1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ E5.0 woman elf: medium-dark skin tone -1F9DD 1F3FE 200D 2640 ; minimally-qualified # 🧝🏾‍♀ E5.0 woman elf: medium-dark skin tone -1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ E5.0 woman elf: dark skin tone -1F9DD 1F3FF 200D 2640 ; minimally-qualified # 🧝🏿‍♀ E5.0 woman elf: dark skin tone -1F9DE ; fully-qualified # 🧞 E5.0 genie -1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ E5.0 man genie -1F9DE 200D 2642 ; minimally-qualified # 🧞‍♂ E5.0 man genie -1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ E5.0 woman genie -1F9DE 200D 2640 ; minimally-qualified # 🧞‍♀ E5.0 woman genie -1F9DF ; fully-qualified # 🧟 E5.0 zombie -1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ E5.0 man zombie -1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ E5.0 man zombie -1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ E5.0 woman zombie -1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ E5.0 woman zombie -1F9CC ; fully-qualified # 🧌 E14.0 troll - -# subgroup: person-activity -1F486 ; fully-qualified # 💆 E0.6 person getting massage -1F486 1F3FB ; fully-qualified # 💆🏻 E1.0 person getting massage: light skin tone -1F486 1F3FC ; fully-qualified # 💆🏼 E1.0 person getting massage: medium-light skin tone -1F486 1F3FD ; fully-qualified # 💆🏽 E1.0 person getting massage: medium skin tone -1F486 1F3FE ; fully-qualified # 💆🏾 E1.0 person getting massage: medium-dark skin tone -1F486 1F3FF ; fully-qualified # 💆🏿 E1.0 person getting massage: dark skin tone -1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ E4.0 man getting massage -1F486 200D 2642 ; minimally-qualified # 💆‍♂ E4.0 man getting massage -1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ E4.0 man getting massage: light skin tone -1F486 1F3FB 200D 2642 ; minimally-qualified # 💆🏻‍♂ E4.0 man getting massage: light skin tone -1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ E4.0 man getting massage: medium-light skin tone -1F486 1F3FC 200D 2642 ; minimally-qualified # 💆🏼‍♂ E4.0 man getting massage: medium-light skin tone -1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ E4.0 man getting massage: medium skin tone -1F486 1F3FD 200D 2642 ; minimally-qualified # 💆🏽‍♂ E4.0 man getting massage: medium skin tone -1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ E4.0 man getting massage: medium-dark skin tone -1F486 1F3FE 200D 2642 ; minimally-qualified # 💆🏾‍♂ E4.0 man getting massage: medium-dark skin tone -1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ E4.0 man getting massage: dark skin tone -1F486 1F3FF 200D 2642 ; minimally-qualified # 💆🏿‍♂ E4.0 man getting massage: dark skin tone -1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ E4.0 woman getting massage -1F486 200D 2640 ; minimally-qualified # 💆‍♀ E4.0 woman getting massage -1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ E4.0 woman getting massage: light skin tone -1F486 1F3FB 200D 2640 ; minimally-qualified # 💆🏻‍♀ E4.0 woman getting massage: light skin tone -1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ E4.0 woman getting massage: medium-light skin tone -1F486 1F3FC 200D 2640 ; minimally-qualified # 💆🏼‍♀ E4.0 woman getting massage: medium-light skin tone -1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ E4.0 woman getting massage: medium skin tone -1F486 1F3FD 200D 2640 ; minimally-qualified # 💆🏽‍♀ E4.0 woman getting massage: medium skin tone -1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ E4.0 woman getting massage: medium-dark skin tone -1F486 1F3FE 200D 2640 ; minimally-qualified # 💆🏾‍♀ E4.0 woman getting massage: medium-dark skin tone -1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ E4.0 woman getting massage: dark skin tone -1F486 1F3FF 200D 2640 ; minimally-qualified # 💆🏿‍♀ E4.0 woman getting massage: dark skin tone -1F487 ; fully-qualified # 💇 E0.6 person getting haircut -1F487 1F3FB ; fully-qualified # 💇🏻 E1.0 person getting haircut: light skin tone -1F487 1F3FC ; fully-qualified # 💇🏼 E1.0 person getting haircut: medium-light skin tone -1F487 1F3FD ; fully-qualified # 💇🏽 E1.0 person getting haircut: medium skin tone -1F487 1F3FE ; fully-qualified # 💇🏾 E1.0 person getting haircut: medium-dark skin tone -1F487 1F3FF ; fully-qualified # 💇🏿 E1.0 person getting haircut: dark skin tone -1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ E4.0 man getting haircut -1F487 200D 2642 ; minimally-qualified # 💇‍♂ E4.0 man getting haircut -1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ E4.0 man getting haircut: light skin tone -1F487 1F3FB 200D 2642 ; minimally-qualified # 💇🏻‍♂ E4.0 man getting haircut: light skin tone -1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ E4.0 man getting haircut: medium-light skin tone -1F487 1F3FC 200D 2642 ; minimally-qualified # 💇🏼‍♂ E4.0 man getting haircut: medium-light skin tone -1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ E4.0 man getting haircut: medium skin tone -1F487 1F3FD 200D 2642 ; minimally-qualified # 💇🏽‍♂ E4.0 man getting haircut: medium skin tone -1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ E4.0 man getting haircut: medium-dark skin tone -1F487 1F3FE 200D 2642 ; minimally-qualified # 💇🏾‍♂ E4.0 man getting haircut: medium-dark skin tone -1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ E4.0 man getting haircut: dark skin tone -1F487 1F3FF 200D 2642 ; minimally-qualified # 💇🏿‍♂ E4.0 man getting haircut: dark skin tone -1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ E4.0 woman getting haircut -1F487 200D 2640 ; minimally-qualified # 💇‍♀ E4.0 woman getting haircut -1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ E4.0 woman getting haircut: light skin tone -1F487 1F3FB 200D 2640 ; minimally-qualified # 💇🏻‍♀ E4.0 woman getting haircut: light skin tone -1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ E4.0 woman getting haircut: medium-light skin tone -1F487 1F3FC 200D 2640 ; minimally-qualified # 💇🏼‍♀ E4.0 woman getting haircut: medium-light skin tone -1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ E4.0 woman getting haircut: medium skin tone -1F487 1F3FD 200D 2640 ; minimally-qualified # 💇🏽‍♀ E4.0 woman getting haircut: medium skin tone -1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ E4.0 woman getting haircut: medium-dark skin tone -1F487 1F3FE 200D 2640 ; minimally-qualified # 💇🏾‍♀ E4.0 woman getting haircut: medium-dark skin tone -1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ E4.0 woman getting haircut: dark skin tone -1F487 1F3FF 200D 2640 ; minimally-qualified # 💇🏿‍♀ E4.0 woman getting haircut: dark skin tone -1F6B6 ; fully-qualified # 🚶 E0.6 person walking -1F6B6 1F3FB ; fully-qualified # 🚶🏻 E1.0 person walking: light skin tone -1F6B6 1F3FC ; fully-qualified # 🚶🏼 E1.0 person walking: medium-light skin tone -1F6B6 1F3FD ; fully-qualified # 🚶🏽 E1.0 person walking: medium skin tone -1F6B6 1F3FE ; fully-qualified # 🚶🏾 E1.0 person walking: medium-dark skin tone -1F6B6 1F3FF ; fully-qualified # 🚶🏿 E1.0 person walking: dark skin tone -1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ E4.0 man walking -1F6B6 200D 2642 ; minimally-qualified # 🚶‍♂ E4.0 man walking -1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ E4.0 man walking: light skin tone -1F6B6 1F3FB 200D 2642 ; minimally-qualified # 🚶🏻‍♂ E4.0 man walking: light skin tone -1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ E4.0 man walking: medium-light skin tone -1F6B6 1F3FC 200D 2642 ; minimally-qualified # 🚶🏼‍♂ E4.0 man walking: medium-light skin tone -1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ E4.0 man walking: medium skin tone -1F6B6 1F3FD 200D 2642 ; minimally-qualified # 🚶🏽‍♂ E4.0 man walking: medium skin tone -1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ E4.0 man walking: medium-dark skin tone -1F6B6 1F3FE 200D 2642 ; minimally-qualified # 🚶🏾‍♂ E4.0 man walking: medium-dark skin tone -1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ E4.0 man walking: dark skin tone -1F6B6 1F3FF 200D 2642 ; minimally-qualified # 🚶🏿‍♂ E4.0 man walking: dark skin tone -1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ E4.0 woman walking -1F6B6 200D 2640 ; minimally-qualified # 🚶‍♀ E4.0 woman walking -1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ E4.0 woman walking: light skin tone -1F6B6 1F3FB 200D 2640 ; minimally-qualified # 🚶🏻‍♀ E4.0 woman walking: light skin tone -1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ E4.0 woman walking: medium-light skin tone -1F6B6 1F3FC 200D 2640 ; minimally-qualified # 🚶🏼‍♀ E4.0 woman walking: medium-light skin tone -1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ E4.0 woman walking: medium skin tone -1F6B6 1F3FD 200D 2640 ; minimally-qualified # 🚶🏽‍♀ E4.0 woman walking: medium skin tone -1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ E4.0 woman walking: medium-dark skin tone -1F6B6 1F3FE 200D 2640 ; minimally-qualified # 🚶🏾‍♀ E4.0 woman walking: medium-dark skin tone -1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ E4.0 woman walking: dark skin tone -1F6B6 1F3FF 200D 2640 ; minimally-qualified # 🚶🏿‍♀ E4.0 woman walking: dark skin tone -1F9CD ; fully-qualified # 🧍 E12.0 person standing -1F9CD 1F3FB ; fully-qualified # 🧍🏻 E12.0 person standing: light skin tone -1F9CD 1F3FC ; fully-qualified # 🧍🏼 E12.0 person standing: medium-light skin tone -1F9CD 1F3FD ; fully-qualified # 🧍🏽 E12.0 person standing: medium skin tone -1F9CD 1F3FE ; fully-qualified # 🧍🏾 E12.0 person standing: medium-dark skin tone -1F9CD 1F3FF ; fully-qualified # 🧍🏿 E12.0 person standing: dark skin tone -1F9CD 200D 2642 FE0F ; fully-qualified # 🧍‍♂️ E12.0 man standing -1F9CD 200D 2642 ; minimally-qualified # 🧍‍♂ E12.0 man standing -1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻‍♂️ E12.0 man standing: light skin tone -1F9CD 1F3FB 200D 2642 ; minimally-qualified # 🧍🏻‍♂ E12.0 man standing: light skin tone -1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼‍♂️ E12.0 man standing: medium-light skin tone -1F9CD 1F3FC 200D 2642 ; minimally-qualified # 🧍🏼‍♂ E12.0 man standing: medium-light skin tone -1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽‍♂️ E12.0 man standing: medium skin tone -1F9CD 1F3FD 200D 2642 ; minimally-qualified # 🧍🏽‍♂ E12.0 man standing: medium skin tone -1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾‍♂️ E12.0 man standing: medium-dark skin tone -1F9CD 1F3FE 200D 2642 ; minimally-qualified # 🧍🏾‍♂ E12.0 man standing: medium-dark skin tone -1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿‍♂️ E12.0 man standing: dark skin tone -1F9CD 1F3FF 200D 2642 ; minimally-qualified # 🧍🏿‍♂ E12.0 man standing: dark skin tone -1F9CD 200D 2640 FE0F ; fully-qualified # 🧍‍♀️ E12.0 woman standing -1F9CD 200D 2640 ; minimally-qualified # 🧍‍♀ E12.0 woman standing -1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻‍♀️ E12.0 woman standing: light skin tone -1F9CD 1F3FB 200D 2640 ; minimally-qualified # 🧍🏻‍♀ E12.0 woman standing: light skin tone -1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼‍♀️ E12.0 woman standing: medium-light skin tone -1F9CD 1F3FC 200D 2640 ; minimally-qualified # 🧍🏼‍♀ E12.0 woman standing: medium-light skin tone -1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽‍♀️ E12.0 woman standing: medium skin tone -1F9CD 1F3FD 200D 2640 ; minimally-qualified # 🧍🏽‍♀ E12.0 woman standing: medium skin tone -1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾‍♀️ E12.0 woman standing: medium-dark skin tone -1F9CD 1F3FE 200D 2640 ; minimally-qualified # 🧍🏾‍♀ E12.0 woman standing: medium-dark skin tone -1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿‍♀️ E12.0 woman standing: dark skin tone -1F9CD 1F3FF 200D 2640 ; minimally-qualified # 🧍🏿‍♀ E12.0 woman standing: dark skin tone -1F9CE ; fully-qualified # 🧎 E12.0 person kneeling -1F9CE 1F3FB ; fully-qualified # 🧎🏻 E12.0 person kneeling: light skin tone -1F9CE 1F3FC ; fully-qualified # 🧎🏼 E12.0 person kneeling: medium-light skin tone -1F9CE 1F3FD ; fully-qualified # 🧎🏽 E12.0 person kneeling: medium skin tone -1F9CE 1F3FE ; fully-qualified # 🧎🏾 E12.0 person kneeling: medium-dark skin tone -1F9CE 1F3FF ; fully-qualified # 🧎🏿 E12.0 person kneeling: dark skin tone -1F9CE 200D 2642 FE0F ; fully-qualified # 🧎‍♂️ E12.0 man kneeling -1F9CE 200D 2642 ; minimally-qualified # 🧎‍♂ E12.0 man kneeling -1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻‍♂️ E12.0 man kneeling: light skin tone -1F9CE 1F3FB 200D 2642 ; minimally-qualified # 🧎🏻‍♂ E12.0 man kneeling: light skin tone -1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼‍♂️ E12.0 man kneeling: medium-light skin tone -1F9CE 1F3FC 200D 2642 ; minimally-qualified # 🧎🏼‍♂ E12.0 man kneeling: medium-light skin tone -1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽‍♂️ E12.0 man kneeling: medium skin tone -1F9CE 1F3FD 200D 2642 ; minimally-qualified # 🧎🏽‍♂ E12.0 man kneeling: medium skin tone -1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾‍♂️ E12.0 man kneeling: medium-dark skin tone -1F9CE 1F3FE 200D 2642 ; minimally-qualified # 🧎🏾‍♂ E12.0 man kneeling: medium-dark skin tone -1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿‍♂️ E12.0 man kneeling: dark skin tone -1F9CE 1F3FF 200D 2642 ; minimally-qualified # 🧎🏿‍♂ E12.0 man kneeling: dark skin tone -1F9CE 200D 2640 FE0F ; fully-qualified # 🧎‍♀️ E12.0 woman kneeling -1F9CE 200D 2640 ; minimally-qualified # 🧎‍♀ E12.0 woman kneeling -1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻‍♀️ E12.0 woman kneeling: light skin tone -1F9CE 1F3FB 200D 2640 ; minimally-qualified # 🧎🏻‍♀ E12.0 woman kneeling: light skin tone -1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼‍♀️ E12.0 woman kneeling: medium-light skin tone -1F9CE 1F3FC 200D 2640 ; minimally-qualified # 🧎🏼‍♀ E12.0 woman kneeling: medium-light skin tone -1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽‍♀️ E12.0 woman kneeling: medium skin tone -1F9CE 1F3FD 200D 2640 ; minimally-qualified # 🧎🏽‍♀ E12.0 woman kneeling: medium skin tone -1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾‍♀️ E12.0 woman kneeling: medium-dark skin tone -1F9CE 1F3FE 200D 2640 ; minimally-qualified # 🧎🏾‍♀ E12.0 woman kneeling: medium-dark skin tone -1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿‍♀️ E12.0 woman kneeling: dark skin tone -1F9CE 1F3FF 200D 2640 ; minimally-qualified # 🧎🏿‍♀ E12.0 woman kneeling: dark skin tone -1F9D1 200D 1F9AF ; fully-qualified # 🧑‍🦯 E12.1 person with white cane -1F9D1 1F3FB 200D 1F9AF ; fully-qualified # 🧑🏻‍🦯 E12.1 person with white cane: light skin tone -1F9D1 1F3FC 200D 1F9AF ; fully-qualified # 🧑🏼‍🦯 E12.1 person with white cane: medium-light skin tone -1F9D1 1F3FD 200D 1F9AF ; fully-qualified # 🧑🏽‍🦯 E12.1 person with white cane: medium skin tone -1F9D1 1F3FE 200D 1F9AF ; fully-qualified # 🧑🏾‍🦯 E12.1 person with white cane: medium-dark skin tone -1F9D1 1F3FF 200D 1F9AF ; fully-qualified # 🧑🏿‍🦯 E12.1 person with white cane: dark skin tone -1F468 200D 1F9AF ; fully-qualified # 👨‍🦯 E12.0 man with white cane -1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻‍🦯 E12.0 man with white cane: light skin tone -1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼‍🦯 E12.0 man with white cane: medium-light skin tone -1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽‍🦯 E12.0 man with white cane: medium skin tone -1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾‍🦯 E12.0 man with white cane: medium-dark skin tone -1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿‍🦯 E12.0 man with white cane: dark skin tone -1F469 200D 1F9AF ; fully-qualified # 👩‍🦯 E12.0 woman with white cane -1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻‍🦯 E12.0 woman with white cane: light skin tone -1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼‍🦯 E12.0 woman with white cane: medium-light skin tone -1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽‍🦯 E12.0 woman with white cane: medium skin tone -1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾‍🦯 E12.0 woman with white cane: medium-dark skin tone -1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿‍🦯 E12.0 woman with white cane: dark skin tone -1F9D1 200D 1F9BC ; fully-qualified # 🧑‍🦼 E12.1 person in motorized wheelchair -1F9D1 1F3FB 200D 1F9BC ; fully-qualified # 🧑🏻‍🦼 E12.1 person in motorized wheelchair: light skin tone -1F9D1 1F3FC 200D 1F9BC ; fully-qualified # 🧑🏼‍🦼 E12.1 person in motorized wheelchair: medium-light skin tone -1F9D1 1F3FD 200D 1F9BC ; fully-qualified # 🧑🏽‍🦼 E12.1 person in motorized wheelchair: medium skin tone -1F9D1 1F3FE 200D 1F9BC ; fully-qualified # 🧑🏾‍🦼 E12.1 person in motorized wheelchair: medium-dark skin tone -1F9D1 1F3FF 200D 1F9BC ; fully-qualified # 🧑🏿‍🦼 E12.1 person in motorized wheelchair: dark skin tone -1F468 200D 1F9BC ; fully-qualified # 👨‍🦼 E12.0 man in motorized wheelchair -1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻‍🦼 E12.0 man in motorized wheelchair: light skin tone -1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼‍🦼 E12.0 man in motorized wheelchair: medium-light skin tone -1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽‍🦼 E12.0 man in motorized wheelchair: medium skin tone -1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾‍🦼 E12.0 man in motorized wheelchair: medium-dark skin tone -1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿‍🦼 E12.0 man in motorized wheelchair: dark skin tone -1F469 200D 1F9BC ; fully-qualified # 👩‍🦼 E12.0 woman in motorized wheelchair -1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻‍🦼 E12.0 woman in motorized wheelchair: light skin tone -1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼‍🦼 E12.0 woman in motorized wheelchair: medium-light skin tone -1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽‍🦼 E12.0 woman in motorized wheelchair: medium skin tone -1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾‍🦼 E12.0 woman in motorized wheelchair: medium-dark skin tone -1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿‍🦼 E12.0 woman in motorized wheelchair: dark skin tone -1F9D1 200D 1F9BD ; fully-qualified # 🧑‍🦽 E12.1 person in manual wheelchair -1F9D1 1F3FB 200D 1F9BD ; fully-qualified # 🧑🏻‍🦽 E12.1 person in manual wheelchair: light skin tone -1F9D1 1F3FC 200D 1F9BD ; fully-qualified # 🧑🏼‍🦽 E12.1 person in manual wheelchair: medium-light skin tone -1F9D1 1F3FD 200D 1F9BD ; fully-qualified # 🧑🏽‍🦽 E12.1 person in manual wheelchair: medium skin tone -1F9D1 1F3FE 200D 1F9BD ; fully-qualified # 🧑🏾‍🦽 E12.1 person in manual wheelchair: medium-dark skin tone -1F9D1 1F3FF 200D 1F9BD ; fully-qualified # 🧑🏿‍🦽 E12.1 person in manual wheelchair: dark skin tone -1F468 200D 1F9BD ; fully-qualified # 👨‍🦽 E12.0 man in manual wheelchair -1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻‍🦽 E12.0 man in manual wheelchair: light skin tone -1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼‍🦽 E12.0 man in manual wheelchair: medium-light skin tone -1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽‍🦽 E12.0 man in manual wheelchair: medium skin tone -1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾‍🦽 E12.0 man in manual wheelchair: medium-dark skin tone -1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿‍🦽 E12.0 man in manual wheelchair: dark skin tone -1F469 200D 1F9BD ; fully-qualified # 👩‍🦽 E12.0 woman in manual wheelchair -1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻‍🦽 E12.0 woman in manual wheelchair: light skin tone -1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼‍🦽 E12.0 woman in manual wheelchair: medium-light skin tone -1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽‍🦽 E12.0 woman in manual wheelchair: medium skin tone -1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾‍🦽 E12.0 woman in manual wheelchair: medium-dark skin tone -1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿‍🦽 E12.0 woman in manual wheelchair: dark skin tone -1F3C3 ; fully-qualified # 🏃 E0.6 person running -1F3C3 1F3FB ; fully-qualified # 🏃🏻 E1.0 person running: light skin tone -1F3C3 1F3FC ; fully-qualified # 🏃🏼 E1.0 person running: medium-light skin tone -1F3C3 1F3FD ; fully-qualified # 🏃🏽 E1.0 person running: medium skin tone -1F3C3 1F3FE ; fully-qualified # 🏃🏾 E1.0 person running: medium-dark skin tone -1F3C3 1F3FF ; fully-qualified # 🏃🏿 E1.0 person running: dark skin tone -1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ E4.0 man running -1F3C3 200D 2642 ; minimally-qualified # 🏃‍♂ E4.0 man running -1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ E4.0 man running: light skin tone -1F3C3 1F3FB 200D 2642 ; minimally-qualified # 🏃🏻‍♂ E4.0 man running: light skin tone -1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ E4.0 man running: medium-light skin tone -1F3C3 1F3FC 200D 2642 ; minimally-qualified # 🏃🏼‍♂ E4.0 man running: medium-light skin tone -1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ E4.0 man running: medium skin tone -1F3C3 1F3FD 200D 2642 ; minimally-qualified # 🏃🏽‍♂ E4.0 man running: medium skin tone -1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ E4.0 man running: medium-dark skin tone -1F3C3 1F3FE 200D 2642 ; minimally-qualified # 🏃🏾‍♂ E4.0 man running: medium-dark skin tone -1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ E4.0 man running: dark skin tone -1F3C3 1F3FF 200D 2642 ; minimally-qualified # 🏃🏿‍♂ E4.0 man running: dark skin tone -1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ E4.0 woman running -1F3C3 200D 2640 ; minimally-qualified # 🏃‍♀ E4.0 woman running -1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ E4.0 woman running: light skin tone -1F3C3 1F3FB 200D 2640 ; minimally-qualified # 🏃🏻‍♀ E4.0 woman running: light skin tone -1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ E4.0 woman running: medium-light skin tone -1F3C3 1F3FC 200D 2640 ; minimally-qualified # 🏃🏼‍♀ E4.0 woman running: medium-light skin tone -1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ E4.0 woman running: medium skin tone -1F3C3 1F3FD 200D 2640 ; minimally-qualified # 🏃🏽‍♀ E4.0 woman running: medium skin tone -1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ E4.0 woman running: medium-dark skin tone -1F3C3 1F3FE 200D 2640 ; minimally-qualified # 🏃🏾‍♀ E4.0 woman running: medium-dark skin tone -1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ E4.0 woman running: dark skin tone -1F3C3 1F3FF 200D 2640 ; minimally-qualified # 🏃🏿‍♀ E4.0 woman running: dark skin tone -1F483 ; fully-qualified # 💃 E0.6 woman dancing -1F483 1F3FB ; fully-qualified # 💃🏻 E1.0 woman dancing: light skin tone -1F483 1F3FC ; fully-qualified # 💃🏼 E1.0 woman dancing: medium-light skin tone -1F483 1F3FD ; fully-qualified # 💃🏽 E1.0 woman dancing: medium skin tone -1F483 1F3FE ; fully-qualified # 💃🏾 E1.0 woman dancing: medium-dark skin tone -1F483 1F3FF ; fully-qualified # 💃🏿 E1.0 woman dancing: dark skin tone -1F57A ; fully-qualified # 🕺 E3.0 man dancing -1F57A 1F3FB ; fully-qualified # 🕺🏻 E3.0 man dancing: light skin tone -1F57A 1F3FC ; fully-qualified # 🕺🏼 E3.0 man dancing: medium-light skin tone -1F57A 1F3FD ; fully-qualified # 🕺🏽 E3.0 man dancing: medium skin tone -1F57A 1F3FE ; fully-qualified # 🕺🏾 E3.0 man dancing: medium-dark skin tone -1F57A 1F3FF ; fully-qualified # 🕺🏿 E3.0 man dancing: dark skin tone -1F574 FE0F ; fully-qualified # 🕴️ E0.7 person in suit levitating -1F574 ; unqualified # 🕴 E0.7 person in suit levitating -1F574 1F3FB ; fully-qualified # 🕴🏻 E4.0 person in suit levitating: light skin tone -1F574 1F3FC ; fully-qualified # 🕴🏼 E4.0 person in suit levitating: medium-light skin tone -1F574 1F3FD ; fully-qualified # 🕴🏽 E4.0 person in suit levitating: medium skin tone -1F574 1F3FE ; fully-qualified # 🕴🏾 E4.0 person in suit levitating: medium-dark skin tone -1F574 1F3FF ; fully-qualified # 🕴🏿 E4.0 person in suit levitating: dark skin tone -1F46F ; fully-qualified # 👯 E0.6 people with bunny ears -1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ E4.0 men with bunny ears -1F46F 200D 2642 ; minimally-qualified # 👯‍♂ E4.0 men with bunny ears -1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ E4.0 women with bunny ears -1F46F 200D 2640 ; minimally-qualified # 👯‍♀ E4.0 women with bunny ears -1F9D6 ; fully-qualified # 🧖 E5.0 person in steamy room -1F9D6 1F3FB ; fully-qualified # 🧖🏻 E5.0 person in steamy room: light skin tone -1F9D6 1F3FC ; fully-qualified # 🧖🏼 E5.0 person in steamy room: medium-light skin tone -1F9D6 1F3FD ; fully-qualified # 🧖🏽 E5.0 person in steamy room: medium skin tone -1F9D6 1F3FE ; fully-qualified # 🧖🏾 E5.0 person in steamy room: medium-dark skin tone -1F9D6 1F3FF ; fully-qualified # 🧖🏿 E5.0 person in steamy room: dark skin tone -1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ E5.0 man in steamy room -1F9D6 200D 2642 ; minimally-qualified # 🧖‍♂ E5.0 man in steamy room -1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ E5.0 man in steamy room: light skin tone -1F9D6 1F3FB 200D 2642 ; minimally-qualified # 🧖🏻‍♂ E5.0 man in steamy room: light skin tone -1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ E5.0 man in steamy room: medium-light skin tone -1F9D6 1F3FC 200D 2642 ; minimally-qualified # 🧖🏼‍♂ E5.0 man in steamy room: medium-light skin tone -1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ E5.0 man in steamy room: medium skin tone -1F9D6 1F3FD 200D 2642 ; minimally-qualified # 🧖🏽‍♂ E5.0 man in steamy room: medium skin tone -1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ E5.0 man in steamy room: medium-dark skin tone -1F9D6 1F3FE 200D 2642 ; minimally-qualified # 🧖🏾‍♂ E5.0 man in steamy room: medium-dark skin tone -1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ E5.0 man in steamy room: dark skin tone -1F9D6 1F3FF 200D 2642 ; minimally-qualified # 🧖🏿‍♂ E5.0 man in steamy room: dark skin tone -1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ E5.0 woman in steamy room -1F9D6 200D 2640 ; minimally-qualified # 🧖‍♀ E5.0 woman in steamy room -1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ E5.0 woman in steamy room: light skin tone -1F9D6 1F3FB 200D 2640 ; minimally-qualified # 🧖🏻‍♀ E5.0 woman in steamy room: light skin tone -1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ E5.0 woman in steamy room: medium-light skin tone -1F9D6 1F3FC 200D 2640 ; minimally-qualified # 🧖🏼‍♀ E5.0 woman in steamy room: medium-light skin tone -1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ E5.0 woman in steamy room: medium skin tone -1F9D6 1F3FD 200D 2640 ; minimally-qualified # 🧖🏽‍♀ E5.0 woman in steamy room: medium skin tone -1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ E5.0 woman in steamy room: medium-dark skin tone -1F9D6 1F3FE 200D 2640 ; minimally-qualified # 🧖🏾‍♀ E5.0 woman in steamy room: medium-dark skin tone -1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ E5.0 woman in steamy room: dark skin tone -1F9D6 1F3FF 200D 2640 ; minimally-qualified # 🧖🏿‍♀ E5.0 woman in steamy room: dark skin tone -1F9D7 ; fully-qualified # 🧗 E5.0 person climbing -1F9D7 1F3FB ; fully-qualified # 🧗🏻 E5.0 person climbing: light skin tone -1F9D7 1F3FC ; fully-qualified # 🧗🏼 E5.0 person climbing: medium-light skin tone -1F9D7 1F3FD ; fully-qualified # 🧗🏽 E5.0 person climbing: medium skin tone -1F9D7 1F3FE ; fully-qualified # 🧗🏾 E5.0 person climbing: medium-dark skin tone -1F9D7 1F3FF ; fully-qualified # 🧗🏿 E5.0 person climbing: dark skin tone -1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ E5.0 man climbing -1F9D7 200D 2642 ; minimally-qualified # 🧗‍♂ E5.0 man climbing -1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ E5.0 man climbing: light skin tone -1F9D7 1F3FB 200D 2642 ; minimally-qualified # 🧗🏻‍♂ E5.0 man climbing: light skin tone -1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ E5.0 man climbing: medium-light skin tone -1F9D7 1F3FC 200D 2642 ; minimally-qualified # 🧗🏼‍♂ E5.0 man climbing: medium-light skin tone -1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ E5.0 man climbing: medium skin tone -1F9D7 1F3FD 200D 2642 ; minimally-qualified # 🧗🏽‍♂ E5.0 man climbing: medium skin tone -1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ E5.0 man climbing: medium-dark skin tone -1F9D7 1F3FE 200D 2642 ; minimally-qualified # 🧗🏾‍♂ E5.0 man climbing: medium-dark skin tone -1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ E5.0 man climbing: dark skin tone -1F9D7 1F3FF 200D 2642 ; minimally-qualified # 🧗🏿‍♂ E5.0 man climbing: dark skin tone -1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ E5.0 woman climbing -1F9D7 200D 2640 ; minimally-qualified # 🧗‍♀ E5.0 woman climbing -1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ E5.0 woman climbing: light skin tone -1F9D7 1F3FB 200D 2640 ; minimally-qualified # 🧗🏻‍♀ E5.0 woman climbing: light skin tone -1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ E5.0 woman climbing: medium-light skin tone -1F9D7 1F3FC 200D 2640 ; minimally-qualified # 🧗🏼‍♀ E5.0 woman climbing: medium-light skin tone -1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ E5.0 woman climbing: medium skin tone -1F9D7 1F3FD 200D 2640 ; minimally-qualified # 🧗🏽‍♀ E5.0 woman climbing: medium skin tone -1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ E5.0 woman climbing: medium-dark skin tone -1F9D7 1F3FE 200D 2640 ; minimally-qualified # 🧗🏾‍♀ E5.0 woman climbing: medium-dark skin tone -1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ E5.0 woman climbing: dark skin tone -1F9D7 1F3FF 200D 2640 ; minimally-qualified # 🧗🏿‍♀ E5.0 woman climbing: dark skin tone - -# subgroup: person-sport -1F93A ; fully-qualified # 🤺 E3.0 person fencing -1F3C7 ; fully-qualified # 🏇 E1.0 horse racing -1F3C7 1F3FB ; fully-qualified # 🏇🏻 E1.0 horse racing: light skin tone -1F3C7 1F3FC ; fully-qualified # 🏇🏼 E1.0 horse racing: medium-light skin tone -1F3C7 1F3FD ; fully-qualified # 🏇🏽 E1.0 horse racing: medium skin tone -1F3C7 1F3FE ; fully-qualified # 🏇🏾 E1.0 horse racing: medium-dark skin tone -1F3C7 1F3FF ; fully-qualified # 🏇🏿 E1.0 horse racing: dark skin tone -26F7 FE0F ; fully-qualified # ⛷️ E0.7 skier -26F7 ; unqualified # ⛷ E0.7 skier -1F3C2 ; fully-qualified # 🏂 E0.6 snowboarder -1F3C2 1F3FB ; fully-qualified # 🏂🏻 E1.0 snowboarder: light skin tone -1F3C2 1F3FC ; fully-qualified # 🏂🏼 E1.0 snowboarder: medium-light skin tone -1F3C2 1F3FD ; fully-qualified # 🏂🏽 E1.0 snowboarder: medium skin tone -1F3C2 1F3FE ; fully-qualified # 🏂🏾 E1.0 snowboarder: medium-dark skin tone -1F3C2 1F3FF ; fully-qualified # 🏂🏿 E1.0 snowboarder: dark skin tone -1F3CC FE0F ; fully-qualified # 🏌️ E0.7 person golfing -1F3CC ; unqualified # 🏌 E0.7 person golfing -1F3CC 1F3FB ; fully-qualified # 🏌🏻 E4.0 person golfing: light skin tone -1F3CC 1F3FC ; fully-qualified # 🏌🏼 E4.0 person golfing: medium-light skin tone -1F3CC 1F3FD ; fully-qualified # 🏌🏽 E4.0 person golfing: medium skin tone -1F3CC 1F3FE ; fully-qualified # 🏌🏾 E4.0 person golfing: medium-dark skin tone -1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone -1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ E4.0 man golfing -1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ E4.0 man golfing -1F3CC FE0F 200D 2642 ; minimally-qualified # 🏌️‍♂ E4.0 man golfing -1F3CC 200D 2642 ; unqualified # 🏌‍♂ E4.0 man golfing -1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ E4.0 man golfing: light skin tone -1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ E4.0 man golfing: light skin tone -1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ E4.0 man golfing: medium-light skin tone -1F3CC 1F3FC 200D 2642 ; minimally-qualified # 🏌🏼‍♂ E4.0 man golfing: medium-light skin tone -1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ E4.0 man golfing: medium skin tone -1F3CC 1F3FD 200D 2642 ; minimally-qualified # 🏌🏽‍♂ E4.0 man golfing: medium skin tone -1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ E4.0 man golfing: medium-dark skin tone -1F3CC 1F3FE 200D 2642 ; minimally-qualified # 🏌🏾‍♂ E4.0 man golfing: medium-dark skin tone -1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ E4.0 man golfing: dark skin tone -1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ E4.0 man golfing: dark skin tone -1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ E4.0 woman golfing -1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ E4.0 woman golfing -1F3CC FE0F 200D 2640 ; minimally-qualified # 🏌️‍♀ E4.0 woman golfing -1F3CC 200D 2640 ; unqualified # 🏌‍♀ E4.0 woman golfing -1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone -1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ E4.0 woman golfing: light skin tone -1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ E4.0 woman golfing: medium-light skin tone -1F3CC 1F3FC 200D 2640 ; minimally-qualified # 🏌🏼‍♀ E4.0 woman golfing: medium-light skin tone -1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ E4.0 woman golfing: medium skin tone -1F3CC 1F3FD 200D 2640 ; minimally-qualified # 🏌🏽‍♀ E4.0 woman golfing: medium skin tone -1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ E4.0 woman golfing: medium-dark skin tone -1F3CC 1F3FE 200D 2640 ; minimally-qualified # 🏌🏾‍♀ E4.0 woman golfing: medium-dark skin tone -1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ E4.0 woman golfing: dark skin tone -1F3CC 1F3FF 200D 2640 ; minimally-qualified # 🏌🏿‍♀ E4.0 woman golfing: dark skin tone -1F3C4 ; fully-qualified # 🏄 E0.6 person surfing -1F3C4 1F3FB ; fully-qualified # 🏄🏻 E1.0 person surfing: light skin tone -1F3C4 1F3FC ; fully-qualified # 🏄🏼 E1.0 person surfing: medium-light skin tone -1F3C4 1F3FD ; fully-qualified # 🏄🏽 E1.0 person surfing: medium skin tone -1F3C4 1F3FE ; fully-qualified # 🏄🏾 E1.0 person surfing: medium-dark skin tone -1F3C4 1F3FF ; fully-qualified # 🏄🏿 E1.0 person surfing: dark skin tone -1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ E4.0 man surfing -1F3C4 200D 2642 ; minimally-qualified # 🏄‍♂ E4.0 man surfing -1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ E4.0 man surfing: light skin tone -1F3C4 1F3FB 200D 2642 ; minimally-qualified # 🏄🏻‍♂ E4.0 man surfing: light skin tone -1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ E4.0 man surfing: medium-light skin tone -1F3C4 1F3FC 200D 2642 ; minimally-qualified # 🏄🏼‍♂ E4.0 man surfing: medium-light skin tone -1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ E4.0 man surfing: medium skin tone -1F3C4 1F3FD 200D 2642 ; minimally-qualified # 🏄🏽‍♂ E4.0 man surfing: medium skin tone -1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ E4.0 man surfing: medium-dark skin tone -1F3C4 1F3FE 200D 2642 ; minimally-qualified # 🏄🏾‍♂ E4.0 man surfing: medium-dark skin tone -1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ E4.0 man surfing: dark skin tone -1F3C4 1F3FF 200D 2642 ; minimally-qualified # 🏄🏿‍♂ E4.0 man surfing: dark skin tone -1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ E4.0 woman surfing -1F3C4 200D 2640 ; minimally-qualified # 🏄‍♀ E4.0 woman surfing -1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ E4.0 woman surfing: light skin tone -1F3C4 1F3FB 200D 2640 ; minimally-qualified # 🏄🏻‍♀ E4.0 woman surfing: light skin tone -1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ E4.0 woman surfing: medium-light skin tone -1F3C4 1F3FC 200D 2640 ; minimally-qualified # 🏄🏼‍♀ E4.0 woman surfing: medium-light skin tone -1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ E4.0 woman surfing: medium skin tone -1F3C4 1F3FD 200D 2640 ; minimally-qualified # 🏄🏽‍♀ E4.0 woman surfing: medium skin tone -1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ E4.0 woman surfing: medium-dark skin tone -1F3C4 1F3FE 200D 2640 ; minimally-qualified # 🏄🏾‍♀ E4.0 woman surfing: medium-dark skin tone -1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ E4.0 woman surfing: dark skin tone -1F3C4 1F3FF 200D 2640 ; minimally-qualified # 🏄🏿‍♀ E4.0 woman surfing: dark skin tone -1F6A3 ; fully-qualified # 🚣 E1.0 person rowing boat -1F6A3 1F3FB ; fully-qualified # 🚣🏻 E1.0 person rowing boat: light skin tone -1F6A3 1F3FC ; fully-qualified # 🚣🏼 E1.0 person rowing boat: medium-light skin tone -1F6A3 1F3FD ; fully-qualified # 🚣🏽 E1.0 person rowing boat: medium skin tone -1F6A3 1F3FE ; fully-qualified # 🚣🏾 E1.0 person rowing boat: medium-dark skin tone -1F6A3 1F3FF ; fully-qualified # 🚣🏿 E1.0 person rowing boat: dark skin tone -1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ E4.0 man rowing boat -1F6A3 200D 2642 ; minimally-qualified # 🚣‍♂ E4.0 man rowing boat -1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ E4.0 man rowing boat: light skin tone -1F6A3 1F3FB 200D 2642 ; minimally-qualified # 🚣🏻‍♂ E4.0 man rowing boat: light skin tone -1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ E4.0 man rowing boat: medium-light skin tone -1F6A3 1F3FC 200D 2642 ; minimally-qualified # 🚣🏼‍♂ E4.0 man rowing boat: medium-light skin tone -1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ E4.0 man rowing boat: medium skin tone -1F6A3 1F3FD 200D 2642 ; minimally-qualified # 🚣🏽‍♂ E4.0 man rowing boat: medium skin tone -1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ E4.0 man rowing boat: medium-dark skin tone -1F6A3 1F3FE 200D 2642 ; minimally-qualified # 🚣🏾‍♂ E4.0 man rowing boat: medium-dark skin tone -1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ E4.0 man rowing boat: dark skin tone -1F6A3 1F3FF 200D 2642 ; minimally-qualified # 🚣🏿‍♂ E4.0 man rowing boat: dark skin tone -1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ E4.0 woman rowing boat -1F6A3 200D 2640 ; minimally-qualified # 🚣‍♀ E4.0 woman rowing boat -1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ E4.0 woman rowing boat: light skin tone -1F6A3 1F3FB 200D 2640 ; minimally-qualified # 🚣🏻‍♀ E4.0 woman rowing boat: light skin tone -1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ E4.0 woman rowing boat: medium-light skin tone -1F6A3 1F3FC 200D 2640 ; minimally-qualified # 🚣🏼‍♀ E4.0 woman rowing boat: medium-light skin tone -1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ E4.0 woman rowing boat: medium skin tone -1F6A3 1F3FD 200D 2640 ; minimally-qualified # 🚣🏽‍♀ E4.0 woman rowing boat: medium skin tone -1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ E4.0 woman rowing boat: medium-dark skin tone -1F6A3 1F3FE 200D 2640 ; minimally-qualified # 🚣🏾‍♀ E4.0 woman rowing boat: medium-dark skin tone -1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ E4.0 woman rowing boat: dark skin tone -1F6A3 1F3FF 200D 2640 ; minimally-qualified # 🚣🏿‍♀ E4.0 woman rowing boat: dark skin tone -1F3CA ; fully-qualified # 🏊 E0.6 person swimming -1F3CA 1F3FB ; fully-qualified # 🏊🏻 E1.0 person swimming: light skin tone -1F3CA 1F3FC ; fully-qualified # 🏊🏼 E1.0 person swimming: medium-light skin tone -1F3CA 1F3FD ; fully-qualified # 🏊🏽 E1.0 person swimming: medium skin tone -1F3CA 1F3FE ; fully-qualified # 🏊🏾 E1.0 person swimming: medium-dark skin tone -1F3CA 1F3FF ; fully-qualified # 🏊🏿 E1.0 person swimming: dark skin tone -1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ E4.0 man swimming -1F3CA 200D 2642 ; minimally-qualified # 🏊‍♂ E4.0 man swimming -1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ E4.0 man swimming: light skin tone -1F3CA 1F3FB 200D 2642 ; minimally-qualified # 🏊🏻‍♂ E4.0 man swimming: light skin tone -1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ E4.0 man swimming: medium-light skin tone -1F3CA 1F3FC 200D 2642 ; minimally-qualified # 🏊🏼‍♂ E4.0 man swimming: medium-light skin tone -1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ E4.0 man swimming: medium skin tone -1F3CA 1F3FD 200D 2642 ; minimally-qualified # 🏊🏽‍♂ E4.0 man swimming: medium skin tone -1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ E4.0 man swimming: medium-dark skin tone -1F3CA 1F3FE 200D 2642 ; minimally-qualified # 🏊🏾‍♂ E4.0 man swimming: medium-dark skin tone -1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ E4.0 man swimming: dark skin tone -1F3CA 1F3FF 200D 2642 ; minimally-qualified # 🏊🏿‍♂ E4.0 man swimming: dark skin tone -1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ E4.0 woman swimming -1F3CA 200D 2640 ; minimally-qualified # 🏊‍♀ E4.0 woman swimming -1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ E4.0 woman swimming: light skin tone -1F3CA 1F3FB 200D 2640 ; minimally-qualified # 🏊🏻‍♀ E4.0 woman swimming: light skin tone -1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ E4.0 woman swimming: medium-light skin tone -1F3CA 1F3FC 200D 2640 ; minimally-qualified # 🏊🏼‍♀ E4.0 woman swimming: medium-light skin tone -1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ E4.0 woman swimming: medium skin tone -1F3CA 1F3FD 200D 2640 ; minimally-qualified # 🏊🏽‍♀ E4.0 woman swimming: medium skin tone -1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ E4.0 woman swimming: medium-dark skin tone -1F3CA 1F3FE 200D 2640 ; minimally-qualified # 🏊🏾‍♀ E4.0 woman swimming: medium-dark skin tone -1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ E4.0 woman swimming: dark skin tone -1F3CA 1F3FF 200D 2640 ; minimally-qualified # 🏊🏿‍♀ E4.0 woman swimming: dark skin tone -26F9 FE0F ; fully-qualified # ⛹️ E0.7 person bouncing ball -26F9 ; unqualified # ⛹ E0.7 person bouncing ball -26F9 1F3FB ; fully-qualified # ⛹🏻 E2.0 person bouncing ball: light skin tone -26F9 1F3FC ; fully-qualified # ⛹🏼 E2.0 person bouncing ball: medium-light skin tone -26F9 1F3FD ; fully-qualified # ⛹🏽 E2.0 person bouncing ball: medium skin tone -26F9 1F3FE ; fully-qualified # ⛹🏾 E2.0 person bouncing ball: medium-dark skin tone -26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone -26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ E4.0 man bouncing ball -26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ E4.0 man bouncing ball -26F9 FE0F 200D 2642 ; minimally-qualified # ⛹️‍♂ E4.0 man bouncing ball -26F9 200D 2642 ; unqualified # ⛹‍♂ E4.0 man bouncing ball -26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: light skin tone -26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone -26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ E4.0 man bouncing ball: medium-light skin tone -26F9 1F3FC 200D 2642 ; minimally-qualified # ⛹🏼‍♂ E4.0 man bouncing ball: medium-light skin tone -26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ E4.0 man bouncing ball: medium skin tone -26F9 1F3FD 200D 2642 ; minimally-qualified # ⛹🏽‍♂ E4.0 man bouncing ball: medium skin tone -26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ E4.0 man bouncing ball: medium-dark skin tone -26F9 1F3FE 200D 2642 ; minimally-qualified # ⛹🏾‍♂ E4.0 man bouncing ball: medium-dark skin tone -26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ E4.0 man bouncing ball: dark skin tone -26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone -26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ E4.0 woman bouncing ball -26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ E4.0 woman bouncing ball -26F9 FE0F 200D 2640 ; minimally-qualified # ⛹️‍♀ E4.0 woman bouncing ball -26F9 200D 2640 ; unqualified # ⛹‍♀ E4.0 woman bouncing ball -26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: light skin tone -26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone -26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ E4.0 woman bouncing ball: medium-light skin tone -26F9 1F3FC 200D 2640 ; minimally-qualified # ⛹🏼‍♀ E4.0 woman bouncing ball: medium-light skin tone -26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ E4.0 woman bouncing ball: medium skin tone -26F9 1F3FD 200D 2640 ; minimally-qualified # ⛹🏽‍♀ E4.0 woman bouncing ball: medium skin tone -26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ E4.0 woman bouncing ball: medium-dark skin tone -26F9 1F3FE 200D 2640 ; minimally-qualified # ⛹🏾‍♀ E4.0 woman bouncing ball: medium-dark skin tone -26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ E4.0 woman bouncing ball: dark skin tone -26F9 1F3FF 200D 2640 ; minimally-qualified # ⛹🏿‍♀ E4.0 woman bouncing ball: dark skin tone -1F3CB FE0F ; fully-qualified # 🏋️ E0.7 person lifting weights -1F3CB ; unqualified # 🏋 E0.7 person lifting weights -1F3CB 1F3FB ; fully-qualified # 🏋🏻 E2.0 person lifting weights: light skin tone -1F3CB 1F3FC ; fully-qualified # 🏋🏼 E2.0 person lifting weights: medium-light skin tone -1F3CB 1F3FD ; fully-qualified # 🏋🏽 E2.0 person lifting weights: medium skin tone -1F3CB 1F3FE ; fully-qualified # 🏋🏾 E2.0 person lifting weights: medium-dark skin tone -1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone -1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ E4.0 man lifting weights -1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ E4.0 man lifting weights -1F3CB FE0F 200D 2642 ; minimally-qualified # 🏋️‍♂ E4.0 man lifting weights -1F3CB 200D 2642 ; unqualified # 🏋‍♂ E4.0 man lifting weights -1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone -1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ E4.0 man lifting weights: light skin tone -1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ E4.0 man lifting weights: medium-light skin tone -1F3CB 1F3FC 200D 2642 ; minimally-qualified # 🏋🏼‍♂ E4.0 man lifting weights: medium-light skin tone -1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ E4.0 man lifting weights: medium skin tone -1F3CB 1F3FD 200D 2642 ; minimally-qualified # 🏋🏽‍♂ E4.0 man lifting weights: medium skin tone -1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ E4.0 man lifting weights: medium-dark skin tone -1F3CB 1F3FE 200D 2642 ; minimally-qualified # 🏋🏾‍♂ E4.0 man lifting weights: medium-dark skin tone -1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ E4.0 man lifting weights: dark skin tone -1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone -1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ E4.0 woman lifting weights -1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ E4.0 woman lifting weights -1F3CB FE0F 200D 2640 ; minimally-qualified # 🏋️‍♀ E4.0 woman lifting weights -1F3CB 200D 2640 ; unqualified # 🏋‍♀ E4.0 woman lifting weights -1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ E4.0 woman lifting weights: light skin tone -1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: light skin tone -1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ E4.0 woman lifting weights: medium-light skin tone -1F3CB 1F3FC 200D 2640 ; minimally-qualified # 🏋🏼‍♀ E4.0 woman lifting weights: medium-light skin tone -1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ E4.0 woman lifting weights: medium skin tone -1F3CB 1F3FD 200D 2640 ; minimally-qualified # 🏋🏽‍♀ E4.0 woman lifting weights: medium skin tone -1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ E4.0 woman lifting weights: medium-dark skin tone -1F3CB 1F3FE 200D 2640 ; minimally-qualified # 🏋🏾‍♀ E4.0 woman lifting weights: medium-dark skin tone -1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ E4.0 woman lifting weights: dark skin tone -1F3CB 1F3FF 200D 2640 ; minimally-qualified # 🏋🏿‍♀ E4.0 woman lifting weights: dark skin tone -1F6B4 ; fully-qualified # 🚴 E1.0 person biking -1F6B4 1F3FB ; fully-qualified # 🚴🏻 E1.0 person biking: light skin tone -1F6B4 1F3FC ; fully-qualified # 🚴🏼 E1.0 person biking: medium-light skin tone -1F6B4 1F3FD ; fully-qualified # 🚴🏽 E1.0 person biking: medium skin tone -1F6B4 1F3FE ; fully-qualified # 🚴🏾 E1.0 person biking: medium-dark skin tone -1F6B4 1F3FF ; fully-qualified # 🚴🏿 E1.0 person biking: dark skin tone -1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ E4.0 man biking -1F6B4 200D 2642 ; minimally-qualified # 🚴‍♂ E4.0 man biking -1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ E4.0 man biking: light skin tone -1F6B4 1F3FB 200D 2642 ; minimally-qualified # 🚴🏻‍♂ E4.0 man biking: light skin tone -1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ E4.0 man biking: medium-light skin tone -1F6B4 1F3FC 200D 2642 ; minimally-qualified # 🚴🏼‍♂ E4.0 man biking: medium-light skin tone -1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ E4.0 man biking: medium skin tone -1F6B4 1F3FD 200D 2642 ; minimally-qualified # 🚴🏽‍♂ E4.0 man biking: medium skin tone -1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ E4.0 man biking: medium-dark skin tone -1F6B4 1F3FE 200D 2642 ; minimally-qualified # 🚴🏾‍♂ E4.0 man biking: medium-dark skin tone -1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ E4.0 man biking: dark skin tone -1F6B4 1F3FF 200D 2642 ; minimally-qualified # 🚴🏿‍♂ E4.0 man biking: dark skin tone -1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ E4.0 woman biking -1F6B4 200D 2640 ; minimally-qualified # 🚴‍♀ E4.0 woman biking -1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ E4.0 woman biking: light skin tone -1F6B4 1F3FB 200D 2640 ; minimally-qualified # 🚴🏻‍♀ E4.0 woman biking: light skin tone -1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ E4.0 woman biking: medium-light skin tone -1F6B4 1F3FC 200D 2640 ; minimally-qualified # 🚴🏼‍♀ E4.0 woman biking: medium-light skin tone -1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ E4.0 woman biking: medium skin tone -1F6B4 1F3FD 200D 2640 ; minimally-qualified # 🚴🏽‍♀ E4.0 woman biking: medium skin tone -1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ E4.0 woman biking: medium-dark skin tone -1F6B4 1F3FE 200D 2640 ; minimally-qualified # 🚴🏾‍♀ E4.0 woman biking: medium-dark skin tone -1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ E4.0 woman biking: dark skin tone -1F6B4 1F3FF 200D 2640 ; minimally-qualified # 🚴🏿‍♀ E4.0 woman biking: dark skin tone -1F6B5 ; fully-qualified # 🚵 E1.0 person mountain biking -1F6B5 1F3FB ; fully-qualified # 🚵🏻 E1.0 person mountain biking: light skin tone -1F6B5 1F3FC ; fully-qualified # 🚵🏼 E1.0 person mountain biking: medium-light skin tone -1F6B5 1F3FD ; fully-qualified # 🚵🏽 E1.0 person mountain biking: medium skin tone -1F6B5 1F3FE ; fully-qualified # 🚵🏾 E1.0 person mountain biking: medium-dark skin tone -1F6B5 1F3FF ; fully-qualified # 🚵🏿 E1.0 person mountain biking: dark skin tone -1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ E4.0 man mountain biking -1F6B5 200D 2642 ; minimally-qualified # 🚵‍♂ E4.0 man mountain biking -1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ E4.0 man mountain biking: light skin tone -1F6B5 1F3FB 200D 2642 ; minimally-qualified # 🚵🏻‍♂ E4.0 man mountain biking: light skin tone -1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ E4.0 man mountain biking: medium-light skin tone -1F6B5 1F3FC 200D 2642 ; minimally-qualified # 🚵🏼‍♂ E4.0 man mountain biking: medium-light skin tone -1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ E4.0 man mountain biking: medium skin tone -1F6B5 1F3FD 200D 2642 ; minimally-qualified # 🚵🏽‍♂ E4.0 man mountain biking: medium skin tone -1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ E4.0 man mountain biking: medium-dark skin tone -1F6B5 1F3FE 200D 2642 ; minimally-qualified # 🚵🏾‍♂ E4.0 man mountain biking: medium-dark skin tone -1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ E4.0 man mountain biking: dark skin tone -1F6B5 1F3FF 200D 2642 ; minimally-qualified # 🚵🏿‍♂ E4.0 man mountain biking: dark skin tone -1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ E4.0 woman mountain biking -1F6B5 200D 2640 ; minimally-qualified # 🚵‍♀ E4.0 woman mountain biking -1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ E4.0 woman mountain biking: light skin tone -1F6B5 1F3FB 200D 2640 ; minimally-qualified # 🚵🏻‍♀ E4.0 woman mountain biking: light skin tone -1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ E4.0 woman mountain biking: medium-light skin tone -1F6B5 1F3FC 200D 2640 ; minimally-qualified # 🚵🏼‍♀ E4.0 woman mountain biking: medium-light skin tone -1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ E4.0 woman mountain biking: medium skin tone -1F6B5 1F3FD 200D 2640 ; minimally-qualified # 🚵🏽‍♀ E4.0 woman mountain biking: medium skin tone -1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ E4.0 woman mountain biking: medium-dark skin tone -1F6B5 1F3FE 200D 2640 ; minimally-qualified # 🚵🏾‍♀ E4.0 woman mountain biking: medium-dark skin tone -1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ E4.0 woman mountain biking: dark skin tone -1F6B5 1F3FF 200D 2640 ; minimally-qualified # 🚵🏿‍♀ E4.0 woman mountain biking: dark skin tone -1F938 ; fully-qualified # 🤸 E3.0 person cartwheeling -1F938 1F3FB ; fully-qualified # 🤸🏻 E3.0 person cartwheeling: light skin tone -1F938 1F3FC ; fully-qualified # 🤸🏼 E3.0 person cartwheeling: medium-light skin tone -1F938 1F3FD ; fully-qualified # 🤸🏽 E3.0 person cartwheeling: medium skin tone -1F938 1F3FE ; fully-qualified # 🤸🏾 E3.0 person cartwheeling: medium-dark skin tone -1F938 1F3FF ; fully-qualified # 🤸🏿 E3.0 person cartwheeling: dark skin tone -1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ E4.0 man cartwheeling -1F938 200D 2642 ; minimally-qualified # 🤸‍♂ E4.0 man cartwheeling -1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ E4.0 man cartwheeling: light skin tone -1F938 1F3FB 200D 2642 ; minimally-qualified # 🤸🏻‍♂ E4.0 man cartwheeling: light skin tone -1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ E4.0 man cartwheeling: medium-light skin tone -1F938 1F3FC 200D 2642 ; minimally-qualified # 🤸🏼‍♂ E4.0 man cartwheeling: medium-light skin tone -1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ E4.0 man cartwheeling: medium skin tone -1F938 1F3FD 200D 2642 ; minimally-qualified # 🤸🏽‍♂ E4.0 man cartwheeling: medium skin tone -1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ E4.0 man cartwheeling: medium-dark skin tone -1F938 1F3FE 200D 2642 ; minimally-qualified # 🤸🏾‍♂ E4.0 man cartwheeling: medium-dark skin tone -1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ E4.0 man cartwheeling: dark skin tone -1F938 1F3FF 200D 2642 ; minimally-qualified # 🤸🏿‍♂ E4.0 man cartwheeling: dark skin tone -1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ E4.0 woman cartwheeling -1F938 200D 2640 ; minimally-qualified # 🤸‍♀ E4.0 woman cartwheeling -1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ E4.0 woman cartwheeling: light skin tone -1F938 1F3FB 200D 2640 ; minimally-qualified # 🤸🏻‍♀ E4.0 woman cartwheeling: light skin tone -1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ E4.0 woman cartwheeling: medium-light skin tone -1F938 1F3FC 200D 2640 ; minimally-qualified # 🤸🏼‍♀ E4.0 woman cartwheeling: medium-light skin tone -1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ E4.0 woman cartwheeling: medium skin tone -1F938 1F3FD 200D 2640 ; minimally-qualified # 🤸🏽‍♀ E4.0 woman cartwheeling: medium skin tone -1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ E4.0 woman cartwheeling: medium-dark skin tone -1F938 1F3FE 200D 2640 ; minimally-qualified # 🤸🏾‍♀ E4.0 woman cartwheeling: medium-dark skin tone -1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ E4.0 woman cartwheeling: dark skin tone -1F938 1F3FF 200D 2640 ; minimally-qualified # 🤸🏿‍♀ E4.0 woman cartwheeling: dark skin tone -1F93C ; fully-qualified # 🤼 E3.0 people wrestling -1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ E4.0 men wrestling -1F93C 200D 2642 ; minimally-qualified # 🤼‍♂ E4.0 men wrestling -1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ E4.0 women wrestling -1F93C 200D 2640 ; minimally-qualified # 🤼‍♀ E4.0 women wrestling -1F93D ; fully-qualified # 🤽 E3.0 person playing water polo -1F93D 1F3FB ; fully-qualified # 🤽🏻 E3.0 person playing water polo: light skin tone -1F93D 1F3FC ; fully-qualified # 🤽🏼 E3.0 person playing water polo: medium-light skin tone -1F93D 1F3FD ; fully-qualified # 🤽🏽 E3.0 person playing water polo: medium skin tone -1F93D 1F3FE ; fully-qualified # 🤽🏾 E3.0 person playing water polo: medium-dark skin tone -1F93D 1F3FF ; fully-qualified # 🤽🏿 E3.0 person playing water polo: dark skin tone -1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ E4.0 man playing water polo -1F93D 200D 2642 ; minimally-qualified # 🤽‍♂ E4.0 man playing water polo -1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ E4.0 man playing water polo: light skin tone -1F93D 1F3FB 200D 2642 ; minimally-qualified # 🤽🏻‍♂ E4.0 man playing water polo: light skin tone -1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ E4.0 man playing water polo: medium-light skin tone -1F93D 1F3FC 200D 2642 ; minimally-qualified # 🤽🏼‍♂ E4.0 man playing water polo: medium-light skin tone -1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ E4.0 man playing water polo: medium skin tone -1F93D 1F3FD 200D 2642 ; minimally-qualified # 🤽🏽‍♂ E4.0 man playing water polo: medium skin tone -1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ E4.0 man playing water polo: medium-dark skin tone -1F93D 1F3FE 200D 2642 ; minimally-qualified # 🤽🏾‍♂ E4.0 man playing water polo: medium-dark skin tone -1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ E4.0 man playing water polo: dark skin tone -1F93D 1F3FF 200D 2642 ; minimally-qualified # 🤽🏿‍♂ E4.0 man playing water polo: dark skin tone -1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ E4.0 woman playing water polo -1F93D 200D 2640 ; minimally-qualified # 🤽‍♀ E4.0 woman playing water polo -1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ E4.0 woman playing water polo: light skin tone -1F93D 1F3FB 200D 2640 ; minimally-qualified # 🤽🏻‍♀ E4.0 woman playing water polo: light skin tone -1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ E4.0 woman playing water polo: medium-light skin tone -1F93D 1F3FC 200D 2640 ; minimally-qualified # 🤽🏼‍♀ E4.0 woman playing water polo: medium-light skin tone -1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ E4.0 woman playing water polo: medium skin tone -1F93D 1F3FD 200D 2640 ; minimally-qualified # 🤽🏽‍♀ E4.0 woman playing water polo: medium skin tone -1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ E4.0 woman playing water polo: medium-dark skin tone -1F93D 1F3FE 200D 2640 ; minimally-qualified # 🤽🏾‍♀ E4.0 woman playing water polo: medium-dark skin tone -1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ E4.0 woman playing water polo: dark skin tone -1F93D 1F3FF 200D 2640 ; minimally-qualified # 🤽🏿‍♀ E4.0 woman playing water polo: dark skin tone -1F93E ; fully-qualified # 🤾 E3.0 person playing handball -1F93E 1F3FB ; fully-qualified # 🤾🏻 E3.0 person playing handball: light skin tone -1F93E 1F3FC ; fully-qualified # 🤾🏼 E3.0 person playing handball: medium-light skin tone -1F93E 1F3FD ; fully-qualified # 🤾🏽 E3.0 person playing handball: medium skin tone -1F93E 1F3FE ; fully-qualified # 🤾🏾 E3.0 person playing handball: medium-dark skin tone -1F93E 1F3FF ; fully-qualified # 🤾🏿 E3.0 person playing handball: dark skin tone -1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ E4.0 man playing handball -1F93E 200D 2642 ; minimally-qualified # 🤾‍♂ E4.0 man playing handball -1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ E4.0 man playing handball: light skin tone -1F93E 1F3FB 200D 2642 ; minimally-qualified # 🤾🏻‍♂ E4.0 man playing handball: light skin tone -1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ E4.0 man playing handball: medium-light skin tone -1F93E 1F3FC 200D 2642 ; minimally-qualified # 🤾🏼‍♂ E4.0 man playing handball: medium-light skin tone -1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ E4.0 man playing handball: medium skin tone -1F93E 1F3FD 200D 2642 ; minimally-qualified # 🤾🏽‍♂ E4.0 man playing handball: medium skin tone -1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ E4.0 man playing handball: medium-dark skin tone -1F93E 1F3FE 200D 2642 ; minimally-qualified # 🤾🏾‍♂ E4.0 man playing handball: medium-dark skin tone -1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ E4.0 man playing handball: dark skin tone -1F93E 1F3FF 200D 2642 ; minimally-qualified # 🤾🏿‍♂ E4.0 man playing handball: dark skin tone -1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ E4.0 woman playing handball -1F93E 200D 2640 ; minimally-qualified # 🤾‍♀ E4.0 woman playing handball -1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ E4.0 woman playing handball: light skin tone -1F93E 1F3FB 200D 2640 ; minimally-qualified # 🤾🏻‍♀ E4.0 woman playing handball: light skin tone -1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ E4.0 woman playing handball: medium-light skin tone -1F93E 1F3FC 200D 2640 ; minimally-qualified # 🤾🏼‍♀ E4.0 woman playing handball: medium-light skin tone -1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ E4.0 woman playing handball: medium skin tone -1F93E 1F3FD 200D 2640 ; minimally-qualified # 🤾🏽‍♀ E4.0 woman playing handball: medium skin tone -1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ E4.0 woman playing handball: medium-dark skin tone -1F93E 1F3FE 200D 2640 ; minimally-qualified # 🤾🏾‍♀ E4.0 woman playing handball: medium-dark skin tone -1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ E4.0 woman playing handball: dark skin tone -1F93E 1F3FF 200D 2640 ; minimally-qualified # 🤾🏿‍♀ E4.0 woman playing handball: dark skin tone -1F939 ; fully-qualified # 🤹 E3.0 person juggling -1F939 1F3FB ; fully-qualified # 🤹🏻 E3.0 person juggling: light skin tone -1F939 1F3FC ; fully-qualified # 🤹🏼 E3.0 person juggling: medium-light skin tone -1F939 1F3FD ; fully-qualified # 🤹🏽 E3.0 person juggling: medium skin tone -1F939 1F3FE ; fully-qualified # 🤹🏾 E3.0 person juggling: medium-dark skin tone -1F939 1F3FF ; fully-qualified # 🤹🏿 E3.0 person juggling: dark skin tone -1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ E4.0 man juggling -1F939 200D 2642 ; minimally-qualified # 🤹‍♂ E4.0 man juggling -1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ E4.0 man juggling: light skin tone -1F939 1F3FB 200D 2642 ; minimally-qualified # 🤹🏻‍♂ E4.0 man juggling: light skin tone -1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ E4.0 man juggling: medium-light skin tone -1F939 1F3FC 200D 2642 ; minimally-qualified # 🤹🏼‍♂ E4.0 man juggling: medium-light skin tone -1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ E4.0 man juggling: medium skin tone -1F939 1F3FD 200D 2642 ; minimally-qualified # 🤹🏽‍♂ E4.0 man juggling: medium skin tone -1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ E4.0 man juggling: medium-dark skin tone -1F939 1F3FE 200D 2642 ; minimally-qualified # 🤹🏾‍♂ E4.0 man juggling: medium-dark skin tone -1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ E4.0 man juggling: dark skin tone -1F939 1F3FF 200D 2642 ; minimally-qualified # 🤹🏿‍♂ E4.0 man juggling: dark skin tone -1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ E4.0 woman juggling -1F939 200D 2640 ; minimally-qualified # 🤹‍♀ E4.0 woman juggling -1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ E4.0 woman juggling: light skin tone -1F939 1F3FB 200D 2640 ; minimally-qualified # 🤹🏻‍♀ E4.0 woman juggling: light skin tone -1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ E4.0 woman juggling: medium-light skin tone -1F939 1F3FC 200D 2640 ; minimally-qualified # 🤹🏼‍♀ E4.0 woman juggling: medium-light skin tone -1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ E4.0 woman juggling: medium skin tone -1F939 1F3FD 200D 2640 ; minimally-qualified # 🤹🏽‍♀ E4.0 woman juggling: medium skin tone -1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ E4.0 woman juggling: medium-dark skin tone -1F939 1F3FE 200D 2640 ; minimally-qualified # 🤹🏾‍♀ E4.0 woman juggling: medium-dark skin tone -1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ E4.0 woman juggling: dark skin tone -1F939 1F3FF 200D 2640 ; minimally-qualified # 🤹🏿‍♀ E4.0 woman juggling: dark skin tone - -# subgroup: person-resting -1F9D8 ; fully-qualified # 🧘 E5.0 person in lotus position -1F9D8 1F3FB ; fully-qualified # 🧘🏻 E5.0 person in lotus position: light skin tone -1F9D8 1F3FC ; fully-qualified # 🧘🏼 E5.0 person in lotus position: medium-light skin tone -1F9D8 1F3FD ; fully-qualified # 🧘🏽 E5.0 person in lotus position: medium skin tone -1F9D8 1F3FE ; fully-qualified # 🧘🏾 E5.0 person in lotus position: medium-dark skin tone -1F9D8 1F3FF ; fully-qualified # 🧘🏿 E5.0 person in lotus position: dark skin tone -1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ E5.0 man in lotus position -1F9D8 200D 2642 ; minimally-qualified # 🧘‍♂ E5.0 man in lotus position -1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ E5.0 man in lotus position: light skin tone -1F9D8 1F3FB 200D 2642 ; minimally-qualified # 🧘🏻‍♂ E5.0 man in lotus position: light skin tone -1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ E5.0 man in lotus position: medium-light skin tone -1F9D8 1F3FC 200D 2642 ; minimally-qualified # 🧘🏼‍♂ E5.0 man in lotus position: medium-light skin tone -1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ E5.0 man in lotus position: medium skin tone -1F9D8 1F3FD 200D 2642 ; minimally-qualified # 🧘🏽‍♂ E5.0 man in lotus position: medium skin tone -1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ E5.0 man in lotus position: medium-dark skin tone -1F9D8 1F3FE 200D 2642 ; minimally-qualified # 🧘🏾‍♂ E5.0 man in lotus position: medium-dark skin tone -1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ E5.0 man in lotus position: dark skin tone -1F9D8 1F3FF 200D 2642 ; minimally-qualified # 🧘🏿‍♂ E5.0 man in lotus position: dark skin tone -1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ E5.0 woman in lotus position -1F9D8 200D 2640 ; minimally-qualified # 🧘‍♀ E5.0 woman in lotus position -1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ E5.0 woman in lotus position: light skin tone -1F9D8 1F3FB 200D 2640 ; minimally-qualified # 🧘🏻‍♀ E5.0 woman in lotus position: light skin tone -1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ E5.0 woman in lotus position: medium-light skin tone -1F9D8 1F3FC 200D 2640 ; minimally-qualified # 🧘🏼‍♀ E5.0 woman in lotus position: medium-light skin tone -1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ E5.0 woman in lotus position: medium skin tone -1F9D8 1F3FD 200D 2640 ; minimally-qualified # 🧘🏽‍♀ E5.0 woman in lotus position: medium skin tone -1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ E5.0 woman in lotus position: medium-dark skin tone -1F9D8 1F3FE 200D 2640 ; minimally-qualified # 🧘🏾‍♀ E5.0 woman in lotus position: medium-dark skin tone -1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ E5.0 woman in lotus position: dark skin tone -1F9D8 1F3FF 200D 2640 ; minimally-qualified # 🧘🏿‍♀ E5.0 woman in lotus position: dark skin tone -1F6C0 ; fully-qualified # 🛀 E0.6 person taking bath -1F6C0 1F3FB ; fully-qualified # 🛀🏻 E1.0 person taking bath: light skin tone -1F6C0 1F3FC ; fully-qualified # 🛀🏼 E1.0 person taking bath: medium-light skin tone -1F6C0 1F3FD ; fully-qualified # 🛀🏽 E1.0 person taking bath: medium skin tone -1F6C0 1F3FE ; fully-qualified # 🛀🏾 E1.0 person taking bath: medium-dark skin tone -1F6C0 1F3FF ; fully-qualified # 🛀🏿 E1.0 person taking bath: dark skin tone -1F6CC ; fully-qualified # 🛌 E1.0 person in bed -1F6CC 1F3FB ; fully-qualified # 🛌🏻 E4.0 person in bed: light skin tone -1F6CC 1F3FC ; fully-qualified # 🛌🏼 E4.0 person in bed: medium-light skin tone -1F6CC 1F3FD ; fully-qualified # 🛌🏽 E4.0 person in bed: medium skin tone -1F6CC 1F3FE ; fully-qualified # 🛌🏾 E4.0 person in bed: medium-dark skin tone -1F6CC 1F3FF ; fully-qualified # 🛌🏿 E4.0 person in bed: dark skin tone - -# subgroup: family -1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑‍🤝‍🧑 E12.0 people holding hands -1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻‍🤝‍🧑🏻 E12.0 people holding hands: light skin tone -1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍🤝‍🧑🏼 E12.1 people holding hands: light skin tone, medium-light skin tone -1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍🤝‍🧑🏽 E12.1 people holding hands: light skin tone, medium skin tone -1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍🤝‍🧑🏾 E12.1 people holding hands: light skin tone, medium-dark skin tone -1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍🤝‍🧑🏿 E12.1 people holding hands: light skin tone, dark skin tone -1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍🤝‍🧑🏻 E12.0 people holding hands: medium-light skin tone, light skin tone -1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼‍🤝‍🧑🏼 E12.0 people holding hands: medium-light skin tone -1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍🤝‍🧑🏽 E12.1 people holding hands: medium-light skin tone, medium skin tone -1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍🤝‍🧑🏾 E12.1 people holding hands: medium-light skin tone, medium-dark skin tone -1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍🤝‍🧑🏿 E12.1 people holding hands: medium-light skin tone, dark skin tone -1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍🤝‍🧑🏻 E12.0 people holding hands: medium skin tone, light skin tone -1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍🤝‍🧑🏼 E12.0 people holding hands: medium skin tone, medium-light skin tone -1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽‍🤝‍🧑🏽 E12.0 people holding hands: medium skin tone -1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍🤝‍🧑🏾 E12.1 people holding hands: medium skin tone, medium-dark skin tone -1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍🤝‍🧑🏿 E12.1 people holding hands: medium skin tone, dark skin tone -1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍🤝‍🧑🏻 E12.0 people holding hands: medium-dark skin tone, light skin tone -1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍🤝‍🧑🏼 E12.0 people holding hands: medium-dark skin tone, medium-light skin tone -1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍🤝‍🧑🏽 E12.0 people holding hands: medium-dark skin tone, medium skin tone -1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾‍🤝‍🧑🏾 E12.0 people holding hands: medium-dark skin tone -1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍🤝‍🧑🏿 E12.1 people holding hands: medium-dark skin tone, dark skin tone -1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍🤝‍🧑🏻 E12.0 people holding hands: dark skin tone, light skin tone -1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍🤝‍🧑🏼 E12.0 people holding hands: dark skin tone, medium-light skin tone -1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍🤝‍🧑🏽 E12.0 people holding hands: dark skin tone, medium skin tone -1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍🤝‍🧑🏾 E12.0 people holding hands: dark skin tone, medium-dark skin tone -1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿‍🤝‍🧑🏿 E12.0 people holding hands: dark skin tone -1F46D ; fully-qualified # 👭 E1.0 women holding hands -1F46D 1F3FB ; fully-qualified # 👭🏻 E12.0 women holding hands: light skin tone -1F469 1F3FB 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍🤝‍👩🏼 E12.1 women holding hands: light skin tone, medium-light skin tone -1F469 1F3FB 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍🤝‍👩🏽 E12.1 women holding hands: light skin tone, medium skin tone -1F469 1F3FB 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍🤝‍👩🏾 E12.1 women holding hands: light skin tone, medium-dark skin tone -1F469 1F3FB 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍🤝‍👩🏿 E12.1 women holding hands: light skin tone, dark skin tone -1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍🤝‍👩🏻 E12.0 women holding hands: medium-light skin tone, light skin tone -1F46D 1F3FC ; fully-qualified # 👭🏼 E12.0 women holding hands: medium-light skin tone -1F469 1F3FC 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍🤝‍👩🏽 E12.1 women holding hands: medium-light skin tone, medium skin tone -1F469 1F3FC 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍🤝‍👩🏾 E12.1 women holding hands: medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍🤝‍👩🏿 E12.1 women holding hands: medium-light skin tone, dark skin tone -1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍🤝‍👩🏻 E12.0 women holding hands: medium skin tone, light skin tone -1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍🤝‍👩🏼 E12.0 women holding hands: medium skin tone, medium-light skin tone -1F46D 1F3FD ; fully-qualified # 👭🏽 E12.0 women holding hands: medium skin tone -1F469 1F3FD 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍🤝‍👩🏾 E12.1 women holding hands: medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍🤝‍👩🏿 E12.1 women holding hands: medium skin tone, dark skin tone -1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍🤝‍👩🏻 E12.0 women holding hands: medium-dark skin tone, light skin tone -1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍🤝‍👩🏼 E12.0 women holding hands: medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍🤝‍👩🏽 E12.0 women holding hands: medium-dark skin tone, medium skin tone -1F46D 1F3FE ; fully-qualified # 👭🏾 E12.0 women holding hands: medium-dark skin tone -1F469 1F3FE 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍🤝‍👩🏿 E12.1 women holding hands: medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍🤝‍👩🏻 E12.0 women holding hands: dark skin tone, light skin tone -1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍🤝‍👩🏼 E12.0 women holding hands: dark skin tone, medium-light skin tone -1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍🤝‍👩🏽 E12.0 women holding hands: dark skin tone, medium skin tone -1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍🤝‍👩🏾 E12.0 women holding hands: dark skin tone, medium-dark skin tone -1F46D 1F3FF ; fully-qualified # 👭🏿 E12.0 women holding hands: dark skin tone -1F46B ; fully-qualified # 👫 E0.6 woman and man holding hands -1F46B 1F3FB ; fully-qualified # 👫🏻 E12.0 woman and man holding hands: light skin tone -1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍🤝‍👨🏼 E12.0 woman and man holding hands: light skin tone, medium-light skin tone -1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍🤝‍👨🏽 E12.0 woman and man holding hands: light skin tone, medium skin tone -1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍🤝‍👨🏾 E12.0 woman and man holding hands: light skin tone, medium-dark skin tone -1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍🤝‍👨🏿 E12.0 woman and man holding hands: light skin tone, dark skin tone -1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-light skin tone, light skin tone -1F46B 1F3FC ; fully-qualified # 👫🏼 E12.0 woman and man holding hands: medium-light skin tone -1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-light skin tone, medium skin tone -1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍🤝‍👨🏾 E12.0 woman and man holding hands: medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-light skin tone, dark skin tone -1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍🤝‍👨🏻 E12.0 woman and man holding hands: medium skin tone, light skin tone -1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍🤝‍👨🏼 E12.0 woman and man holding hands: medium skin tone, medium-light skin tone -1F46B 1F3FD ; fully-qualified # 👫🏽 E12.0 woman and man holding hands: medium skin tone -1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍🤝‍👨🏾 E12.0 woman and man holding hands: medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍🤝‍👨🏿 E12.0 woman and man holding hands: medium skin tone, dark skin tone -1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-dark skin tone, light skin tone -1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍🤝‍👨🏼 E12.0 woman and man holding hands: medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-dark skin tone, medium skin tone -1F46B 1F3FE ; fully-qualified # 👫🏾 E12.0 woman and man holding hands: medium-dark skin tone -1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍🤝‍👨🏻 E12.0 woman and man holding hands: dark skin tone, light skin tone -1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍🤝‍👨🏼 E12.0 woman and man holding hands: dark skin tone, medium-light skin tone -1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍🤝‍👨🏽 E12.0 woman and man holding hands: dark skin tone, medium skin tone -1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍🤝‍👨🏾 E12.0 woman and man holding hands: dark skin tone, medium-dark skin tone -1F46B 1F3FF ; fully-qualified # 👫🏿 E12.0 woman and man holding hands: dark skin tone -1F46C ; fully-qualified # 👬 E1.0 men holding hands -1F46C 1F3FB ; fully-qualified # 👬🏻 E12.0 men holding hands: light skin tone -1F468 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍🤝‍👨🏼 E12.1 men holding hands: light skin tone, medium-light skin tone -1F468 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍🤝‍👨🏽 E12.1 men holding hands: light skin tone, medium skin tone -1F468 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍🤝‍👨🏾 E12.1 men holding hands: light skin tone, medium-dark skin tone -1F468 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍🤝‍👨🏿 E12.1 men holding hands: light skin tone, dark skin tone -1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍🤝‍👨🏻 E12.0 men holding hands: medium-light skin tone, light skin tone -1F46C 1F3FC ; fully-qualified # 👬🏼 E12.0 men holding hands: medium-light skin tone -1F468 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍🤝‍👨🏽 E12.1 men holding hands: medium-light skin tone, medium skin tone -1F468 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍🤝‍👨🏾 E12.1 men holding hands: medium-light skin tone, medium-dark skin tone -1F468 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍🤝‍👨🏿 E12.1 men holding hands: medium-light skin tone, dark skin tone -1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍🤝‍👨🏻 E12.0 men holding hands: medium skin tone, light skin tone -1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍🤝‍👨🏼 E12.0 men holding hands: medium skin tone, medium-light skin tone -1F46C 1F3FD ; fully-qualified # 👬🏽 E12.0 men holding hands: medium skin tone -1F468 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍🤝‍👨🏾 E12.1 men holding hands: medium skin tone, medium-dark skin tone -1F468 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍🤝‍👨🏿 E12.1 men holding hands: medium skin tone, dark skin tone -1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍🤝‍👨🏻 E12.0 men holding hands: medium-dark skin tone, light skin tone -1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍🤝‍👨🏼 E12.0 men holding hands: medium-dark skin tone, medium-light skin tone -1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍🤝‍👨🏽 E12.0 men holding hands: medium-dark skin tone, medium skin tone -1F46C 1F3FE ; fully-qualified # 👬🏾 E12.0 men holding hands: medium-dark skin tone -1F468 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍🤝‍👨🏿 E12.1 men holding hands: medium-dark skin tone, dark skin tone -1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍🤝‍👨🏻 E12.0 men holding hands: dark skin tone, light skin tone -1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍🤝‍👨🏼 E12.0 men holding hands: dark skin tone, medium-light skin tone -1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍🤝‍👨🏽 E12.0 men holding hands: dark skin tone, medium skin tone -1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍🤝‍👨🏾 E12.0 men holding hands: dark skin tone, medium-dark skin tone -1F46C 1F3FF ; fully-qualified # 👬🏿 E12.0 men holding hands: dark skin tone -1F48F ; fully-qualified # 💏 E0.6 kiss -1F48F 1F3FB ; fully-qualified # 💏🏻 E13.1 kiss: light skin tone -1F48F 1F3FC ; fully-qualified # 💏🏼 E13.1 kiss: medium-light skin tone -1F48F 1F3FD ; fully-qualified # 💏🏽 E13.1 kiss: medium skin tone -1F48F 1F3FE ; fully-qualified # 💏🏾 E13.1 kiss: medium-dark skin tone -1F48F 1F3FF ; fully-qualified # 💏🏿 E13.1 kiss: dark skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone -1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone -1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone -1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone -1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone -1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone -1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone -1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone -1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone -1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone -1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone -1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone -1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone -1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone -1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone -1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone -1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone -1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone -1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone -1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone -1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone -1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 E2.0 kiss: woman, man -1F469 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👩‍❤‍💋‍👨 E2.0 kiss: woman, man -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, light skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium-light skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium-light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium-dark skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium-dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, dark skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, dark skin tone -1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 E2.0 kiss: man, man -1F468 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👨‍❤‍💋‍👨 E2.0 kiss: man, man -1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, light skin tone -1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏻 E13.1 kiss: man, man, light skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone -1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone -1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone -1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone -1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone -1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium-light skin tone -1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium-light skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone -1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone -1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone -1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone -1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone -1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium skin tone -1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone -1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone -1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone -1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone -1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone -1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium-dark skin tone -1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium-dark skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone -1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone -1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone -1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone -1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone -1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, dark skin tone -1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏿 E13.1 kiss: man, man, dark skin tone -1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 E2.0 kiss: woman, woman -1F469 200D 2764 200D 1F48B 200D 1F469 ; minimally-qualified # 👩‍❤‍💋‍👩 E2.0 kiss: woman, woman -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, light skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone -1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-light skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone -1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone -1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone -1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, dark skin tone -1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, dark skin tone -1F491 ; fully-qualified # 💑 E0.6 couple with heart -1F491 1F3FB ; fully-qualified # 💑🏻 E13.1 couple with heart: light skin tone -1F491 1F3FC ; fully-qualified # 💑🏼 E13.1 couple with heart: medium-light skin tone -1F491 1F3FD ; fully-qualified # 💑🏽 E13.1 couple with heart: medium skin tone -1F491 1F3FE ; fully-qualified # 💑🏾 E13.1 couple with heart: medium-dark skin tone -1F491 1F3FF ; fully-qualified # 💑🏿 E13.1 couple with heart: dark skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍❤️‍🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone -1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻‍❤‍🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍❤️‍🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone -1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻‍❤‍🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍❤️‍🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone -1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻‍❤‍🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone -1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍❤️‍🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone -1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻‍❤‍🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone -1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍❤️‍🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone -1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼‍❤‍🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍❤️‍🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone -1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼‍❤‍🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone -1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone -1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone -1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍❤️‍🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone -1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽‍❤‍🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍❤️‍🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone -1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽‍❤‍🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone -1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone -1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone -1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍❤️‍🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone -1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾‍❤‍🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍❤️‍🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone -1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾‍❤‍🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone -1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone -1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍❤️‍🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone -1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿‍❤‍🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍❤️‍🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone -1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿‍❤‍🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍❤️‍🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone -1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿‍❤‍🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone -1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍❤️‍🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone -1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿‍❤‍🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone -1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 E2.0 couple with heart: woman, man -1F469 200D 2764 200D 1F468 ; minimally-qualified # 👩‍❤‍👨 E2.0 couple with heart: woman, man -1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏻‍❤️‍👨🏻 E13.1 couple with heart: woman, man, light skin tone -1F469 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏻‍❤‍👨🏻 E13.1 couple with heart: woman, man, light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍❤️‍👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏻‍❤‍👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍❤️‍👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏻‍❤‍👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍❤️‍👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏻‍❤‍👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍❤️‍👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone -1F469 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏻‍❤‍👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏼‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏼‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone -1F469 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏼‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏼‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏼‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone -1F469 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏼‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏽‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏽‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏽‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium skin tone -1F469 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏽‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏽‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone -1F469 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏽‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏾‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏾‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏾‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏾‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone -1F469 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏾‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone -1F469 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏾‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍❤️‍👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏿‍❤‍👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍❤️‍👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏿‍❤‍👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍❤️‍👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏿‍❤‍👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍❤️‍👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏿‍❤‍👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏿‍❤️‍👨🏿 E13.1 couple with heart: woman, man, dark skin tone -1F469 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏿‍❤‍👨🏿 E13.1 couple with heart: woman, man, dark skin tone -1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 E2.0 couple with heart: man, man -1F468 200D 2764 200D 1F468 ; minimally-qualified # 👨‍❤‍👨 E2.0 couple with heart: man, man -1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏻‍❤️‍👨🏻 E13.1 couple with heart: man, man, light skin tone -1F468 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏻‍❤‍👨🏻 E13.1 couple with heart: man, man, light skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍❤️‍👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone -1F468 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏻‍❤‍👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍❤️‍👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone -1F468 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏻‍❤‍👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍❤️‍👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone -1F468 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏻‍❤‍👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone -1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍❤️‍👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone -1F468 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏻‍❤‍👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone -1F468 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏼‍❤‍👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏼‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium-light skin tone -1F468 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏼‍❤‍👨🏼 E13.1 couple with heart: man, man, medium-light skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone -1F468 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏼‍❤‍👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone -1F468 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏼‍❤‍👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone -1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone -1F468 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏼‍❤‍👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone -1F468 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏽‍❤‍👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone -1F468 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏽‍❤‍👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏽‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium skin tone -1F468 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏽‍❤‍👨🏽 E13.1 couple with heart: man, man, medium skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone -1F468 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏽‍❤‍👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone -1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone -1F468 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏽‍❤‍👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone -1F468 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏾‍❤‍👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone -1F468 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏾‍❤‍👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone -1F468 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏾‍❤‍👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏾‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone -1F468 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏾‍❤‍👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone -1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone -1F468 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏾‍❤‍👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍❤️‍👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone -1F468 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏿‍❤‍👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍❤️‍👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone -1F468 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏿‍❤‍👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍❤️‍👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone -1F468 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏿‍❤‍👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍❤️‍👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone -1F468 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏿‍❤‍👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone -1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏿‍❤️‍👨🏿 E13.1 couple with heart: man, man, dark skin tone -1F468 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏿‍❤‍👨🏿 E13.1 couple with heart: man, man, dark skin tone -1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 E2.0 couple with heart: woman, woman -1F469 200D 2764 200D 1F469 ; minimally-qualified # 👩‍❤‍👩 E2.0 couple with heart: woman, woman -1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏻‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, light skin tone -1F469 1F3FB 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏻‍❤‍👩🏻 E13.1 couple with heart: woman, woman, light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏻‍❤‍👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏻‍❤‍👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏻‍❤‍👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone -1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone -1F469 1F3FB 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏻‍❤‍👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏼‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏼‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone -1F469 1F3FC 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏼‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏼‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏼‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone -1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone -1F469 1F3FC 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏼‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏽‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏽‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏽‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium skin tone -1F469 1F3FD 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏽‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏽‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone -1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone -1F469 1F3FD 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏽‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏾‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏾‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏾‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏾‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone -1F469 1F3FE 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏾‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone -1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone -1F469 1F3FE 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏾‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏿‍❤‍👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏿‍❤‍👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏿‍❤‍👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏿‍❤‍👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone -1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏿‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, dark skin tone -1F469 1F3FF 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏿‍❤‍👩🏿 E13.1 couple with heart: woman, woman, dark skin tone -1F46A ; fully-qualified # 👪 E0.6 family -1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 E2.0 family: man, woman, boy -1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 E2.0 family: man, woman, girl -1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 E2.0 family: man, woman, girl, boy -1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 E2.0 family: man, woman, boy, boy -1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 E2.0 family: man, woman, girl, girl -1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 E2.0 family: man, man, boy -1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 E2.0 family: man, man, girl -1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 E2.0 family: man, man, girl, boy -1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 E2.0 family: man, man, boy, boy -1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 E2.0 family: man, man, girl, girl -1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 E2.0 family: woman, woman, boy -1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 E2.0 family: woman, woman, girl -1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 E2.0 family: woman, woman, girl, boy -1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 E2.0 family: woman, woman, boy, boy -1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 E2.0 family: woman, woman, girl, girl -1F468 200D 1F466 ; fully-qualified # 👨‍👦 E4.0 family: man, boy -1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 E4.0 family: man, boy, boy -1F468 200D 1F467 ; fully-qualified # 👨‍👧 E4.0 family: man, girl -1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 E4.0 family: man, girl, boy -1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 E4.0 family: man, girl, girl -1F469 200D 1F466 ; fully-qualified # 👩‍👦 E4.0 family: woman, boy -1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 E4.0 family: woman, boy, boy -1F469 200D 1F467 ; fully-qualified # 👩‍👧 E4.0 family: woman, girl -1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 E4.0 family: woman, girl, boy -1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 E4.0 family: woman, girl, girl - -# subgroup: person-symbol -1F5E3 FE0F ; fully-qualified # 🗣️ E0.7 speaking head -1F5E3 ; unqualified # 🗣 E0.7 speaking head -1F464 ; fully-qualified # 👤 E0.6 bust in silhouette -1F465 ; fully-qualified # 👥 E1.0 busts in silhouette -1FAC2 ; fully-qualified # 🫂 E13.0 people hugging -1F463 ; fully-qualified # 👣 E0.6 footprints - -# People & Body subtotal: 2998 -# People & Body subtotal: 508 w/o modifiers - -# group: Component - -# subgroup: skin-tone -1F3FB ; component # 🏻 E1.0 light skin tone -1F3FC ; component # 🏼 E1.0 medium-light skin tone -1F3FD ; component # 🏽 E1.0 medium skin tone -1F3FE ; component # 🏾 E1.0 medium-dark skin tone -1F3FF ; component # 🏿 E1.0 dark skin tone - -# subgroup: hair-style -1F9B0 ; component # 🦰 E11.0 red hair -1F9B1 ; component # 🦱 E11.0 curly hair -1F9B3 ; component # 🦳 E11.0 white hair -1F9B2 ; component # 🦲 E11.0 bald - -# Component subtotal: 9 -# Component subtotal: 4 w/o modifiers - -# group: Animals & Nature - -# subgroup: animal-mammal -1F435 ; fully-qualified # 🐵 E0.6 monkey face -1F412 ; fully-qualified # 🐒 E0.6 monkey -1F98D ; fully-qualified # 🦍 E3.0 gorilla -1F9A7 ; fully-qualified # 🦧 E12.0 orangutan -1F436 ; fully-qualified # 🐶 E0.6 dog face -1F415 ; fully-qualified # 🐕 E0.7 dog -1F9AE ; fully-qualified # 🦮 E12.0 guide dog -1F415 200D 1F9BA ; fully-qualified # 🐕‍🦺 E12.0 service dog -1F429 ; fully-qualified # 🐩 E0.6 poodle -1F43A ; fully-qualified # 🐺 E0.6 wolf -1F98A ; fully-qualified # 🦊 E3.0 fox -1F99D ; fully-qualified # 🦝 E11.0 raccoon -1F431 ; fully-qualified # 🐱 E0.6 cat face -1F408 ; fully-qualified # 🐈 E0.7 cat -1F408 200D 2B1B ; fully-qualified # 🐈‍⬛ E13.0 black cat -1F981 ; fully-qualified # 🦁 E1.0 lion -1F42F ; fully-qualified # 🐯 E0.6 tiger face -1F405 ; fully-qualified # 🐅 E1.0 tiger -1F406 ; fully-qualified # 🐆 E1.0 leopard -1F434 ; fully-qualified # 🐴 E0.6 horse face -1FACE ; fully-qualified # 🫎 E15.0 moose -1FACF ; fully-qualified # 🫏 E15.0 donkey -1F40E ; fully-qualified # 🐎 E0.6 horse -1F984 ; fully-qualified # 🦄 E1.0 unicorn -1F993 ; fully-qualified # 🦓 E5.0 zebra -1F98C ; fully-qualified # 🦌 E3.0 deer -1F9AC ; fully-qualified # 🦬 E13.0 bison -1F42E ; fully-qualified # 🐮 E0.6 cow face -1F402 ; fully-qualified # 🐂 E1.0 ox -1F403 ; fully-qualified # 🐃 E1.0 water buffalo -1F404 ; fully-qualified # 🐄 E1.0 cow -1F437 ; fully-qualified # 🐷 E0.6 pig face -1F416 ; fully-qualified # 🐖 E1.0 pig -1F417 ; fully-qualified # 🐗 E0.6 boar -1F43D ; fully-qualified # 🐽 E0.6 pig nose -1F40F ; fully-qualified # 🐏 E1.0 ram -1F411 ; fully-qualified # 🐑 E0.6 ewe -1F410 ; fully-qualified # 🐐 E1.0 goat -1F42A ; fully-qualified # 🐪 E1.0 camel -1F42B ; fully-qualified # 🐫 E0.6 two-hump camel -1F999 ; fully-qualified # 🦙 E11.0 llama -1F992 ; fully-qualified # 🦒 E5.0 giraffe -1F418 ; fully-qualified # 🐘 E0.6 elephant -1F9A3 ; fully-qualified # 🦣 E13.0 mammoth -1F98F ; fully-qualified # 🦏 E3.0 rhinoceros -1F99B ; fully-qualified # 🦛 E11.0 hippopotamus -1F42D ; fully-qualified # 🐭 E0.6 mouse face -1F401 ; fully-qualified # 🐁 E1.0 mouse -1F400 ; fully-qualified # 🐀 E1.0 rat -1F439 ; fully-qualified # 🐹 E0.6 hamster -1F430 ; fully-qualified # 🐰 E0.6 rabbit face -1F407 ; fully-qualified # 🐇 E1.0 rabbit -1F43F FE0F ; fully-qualified # 🐿️ E0.7 chipmunk -1F43F ; unqualified # 🐿 E0.7 chipmunk -1F9AB ; fully-qualified # 🦫 E13.0 beaver -1F994 ; fully-qualified # 🦔 E5.0 hedgehog -1F987 ; fully-qualified # 🦇 E3.0 bat -1F43B ; fully-qualified # 🐻 E0.6 bear -1F43B 200D 2744 FE0F ; fully-qualified # 🐻‍❄️ E13.0 polar bear -1F43B 200D 2744 ; minimally-qualified # 🐻‍❄ E13.0 polar bear -1F428 ; fully-qualified # 🐨 E0.6 koala -1F43C ; fully-qualified # 🐼 E0.6 panda -1F9A5 ; fully-qualified # 🦥 E12.0 sloth -1F9A6 ; fully-qualified # 🦦 E12.0 otter -1F9A8 ; fully-qualified # 🦨 E12.0 skunk -1F998 ; fully-qualified # 🦘 E11.0 kangaroo -1F9A1 ; fully-qualified # 🦡 E11.0 badger -1F43E ; fully-qualified # 🐾 E0.6 paw prints - -# subgroup: animal-bird -1F983 ; fully-qualified # 🦃 E1.0 turkey -1F414 ; fully-qualified # 🐔 E0.6 chicken -1F413 ; fully-qualified # 🐓 E1.0 rooster -1F423 ; fully-qualified # 🐣 E0.6 hatching chick -1F424 ; fully-qualified # 🐤 E0.6 baby chick -1F425 ; fully-qualified # 🐥 E0.6 front-facing baby chick -1F426 ; fully-qualified # 🐦 E0.6 bird -1F427 ; fully-qualified # 🐧 E0.6 penguin -1F54A FE0F ; fully-qualified # 🕊️ E0.7 dove -1F54A ; unqualified # 🕊 E0.7 dove -1F985 ; fully-qualified # 🦅 E3.0 eagle -1F986 ; fully-qualified # 🦆 E3.0 duck -1F9A2 ; fully-qualified # 🦢 E11.0 swan -1F989 ; fully-qualified # 🦉 E3.0 owl -1F9A4 ; fully-qualified # 🦤 E13.0 dodo -1FAB6 ; fully-qualified # 🪶 E13.0 feather -1F9A9 ; fully-qualified # 🦩 E12.0 flamingo -1F99A ; fully-qualified # 🦚 E11.0 peacock -1F99C ; fully-qualified # 🦜 E11.0 parrot -1FABD ; fully-qualified # 🪽 E15.0 wing -1F426 200D 2B1B ; fully-qualified # 🐦‍⬛ E15.0 black bird -1FABF ; fully-qualified # 🪿 E15.0 goose - -# subgroup: animal-amphibian -1F438 ; fully-qualified # 🐸 E0.6 frog - -# subgroup: animal-reptile -1F40A ; fully-qualified # 🐊 E1.0 crocodile -1F422 ; fully-qualified # 🐢 E0.6 turtle -1F98E ; fully-qualified # 🦎 E3.0 lizard -1F40D ; fully-qualified # 🐍 E0.6 snake -1F432 ; fully-qualified # 🐲 E0.6 dragon face -1F409 ; fully-qualified # 🐉 E1.0 dragon -1F995 ; fully-qualified # 🦕 E5.0 sauropod -1F996 ; fully-qualified # 🦖 E5.0 T-Rex - -# subgroup: animal-marine -1F433 ; fully-qualified # 🐳 E0.6 spouting whale -1F40B ; fully-qualified # 🐋 E1.0 whale -1F42C ; fully-qualified # 🐬 E0.6 dolphin -1F9AD ; fully-qualified # 🦭 E13.0 seal -1F41F ; fully-qualified # 🐟 E0.6 fish -1F420 ; fully-qualified # 🐠 E0.6 tropical fish -1F421 ; fully-qualified # 🐡 E0.6 blowfish -1F988 ; fully-qualified # 🦈 E3.0 shark -1F419 ; fully-qualified # 🐙 E0.6 octopus -1F41A ; fully-qualified # 🐚 E0.6 spiral shell -1FAB8 ; fully-qualified # 🪸 E14.0 coral -1FABC ; fully-qualified # 🪼 E15.0 jellyfish - -# subgroup: animal-bug -1F40C ; fully-qualified # 🐌 E0.6 snail -1F98B ; fully-qualified # 🦋 E3.0 butterfly -1F41B ; fully-qualified # 🐛 E0.6 bug -1F41C ; fully-qualified # 🐜 E0.6 ant -1F41D ; fully-qualified # 🐝 E0.6 honeybee -1FAB2 ; fully-qualified # 🪲 E13.0 beetle -1F41E ; fully-qualified # 🐞 E0.6 lady beetle -1F997 ; fully-qualified # 🦗 E5.0 cricket -1FAB3 ; fully-qualified # 🪳 E13.0 cockroach -1F577 FE0F ; fully-qualified # 🕷️ E0.7 spider -1F577 ; unqualified # 🕷 E0.7 spider -1F578 FE0F ; fully-qualified # 🕸️ E0.7 spider web -1F578 ; unqualified # 🕸 E0.7 spider web -1F982 ; fully-qualified # 🦂 E1.0 scorpion -1F99F ; fully-qualified # 🦟 E11.0 mosquito -1FAB0 ; fully-qualified # 🪰 E13.0 fly -1FAB1 ; fully-qualified # 🪱 E13.0 worm -1F9A0 ; fully-qualified # 🦠 E11.0 microbe - -# subgroup: plant-flower -1F490 ; fully-qualified # 💐 E0.6 bouquet -1F338 ; fully-qualified # 🌸 E0.6 cherry blossom -1F4AE ; fully-qualified # 💮 E0.6 white flower -1FAB7 ; fully-qualified # 🪷 E14.0 lotus -1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette -1F3F5 ; unqualified # 🏵 E0.7 rosette -1F339 ; fully-qualified # 🌹 E0.6 rose -1F940 ; fully-qualified # 🥀 E3.0 wilted flower -1F33A ; fully-qualified # 🌺 E0.6 hibiscus -1F33B ; fully-qualified # 🌻 E0.6 sunflower -1F33C ; fully-qualified # 🌼 E0.6 blossom -1F337 ; fully-qualified # 🌷 E0.6 tulip -1FABB ; fully-qualified # 🪻 E15.0 hyacinth - -# subgroup: plant-other -1F331 ; fully-qualified # 🌱 E0.6 seedling -1FAB4 ; fully-qualified # 🪴 E13.0 potted plant -1F332 ; fully-qualified # 🌲 E1.0 evergreen tree -1F333 ; fully-qualified # 🌳 E1.0 deciduous tree -1F334 ; fully-qualified # 🌴 E0.6 palm tree -1F335 ; fully-qualified # 🌵 E0.6 cactus -1F33E ; fully-qualified # 🌾 E0.6 sheaf of rice -1F33F ; fully-qualified # 🌿 E0.6 herb -2618 FE0F ; fully-qualified # ☘️ E1.0 shamrock -2618 ; unqualified # ☘ E1.0 shamrock -1F340 ; fully-qualified # 🍀 E0.6 four leaf clover -1F341 ; fully-qualified # 🍁 E0.6 maple leaf -1F342 ; fully-qualified # 🍂 E0.6 fallen leaf -1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind -1FAB9 ; fully-qualified # 🪹 E14.0 empty nest -1FABA ; fully-qualified # 🪺 E14.0 nest with eggs -1F344 ; fully-qualified # 🍄 E0.6 mushroom - -# Animals & Nature subtotal: 159 -# Animals & Nature subtotal: 159 w/o modifiers - -# group: Food & Drink - -# subgroup: food-fruit -1F347 ; fully-qualified # 🍇 E0.6 grapes -1F348 ; fully-qualified # 🍈 E0.6 melon -1F349 ; fully-qualified # 🍉 E0.6 watermelon -1F34A ; fully-qualified # 🍊 E0.6 tangerine -1F34B ; fully-qualified # 🍋 E1.0 lemon -1F34C ; fully-qualified # 🍌 E0.6 banana -1F34D ; fully-qualified # 🍍 E0.6 pineapple -1F96D ; fully-qualified # 🥭 E11.0 mango -1F34E ; fully-qualified # 🍎 E0.6 red apple -1F34F ; fully-qualified # 🍏 E0.6 green apple -1F350 ; fully-qualified # 🍐 E1.0 pear -1F351 ; fully-qualified # 🍑 E0.6 peach -1F352 ; fully-qualified # 🍒 E0.6 cherries -1F353 ; fully-qualified # 🍓 E0.6 strawberry -1FAD0 ; fully-qualified # 🫐 E13.0 blueberries -1F95D ; fully-qualified # 🥝 E3.0 kiwi fruit -1F345 ; fully-qualified # 🍅 E0.6 tomato -1FAD2 ; fully-qualified # 🫒 E13.0 olive -1F965 ; fully-qualified # 🥥 E5.0 coconut - -# subgroup: food-vegetable -1F951 ; fully-qualified # 🥑 E3.0 avocado -1F346 ; fully-qualified # 🍆 E0.6 eggplant -1F954 ; fully-qualified # 🥔 E3.0 potato -1F955 ; fully-qualified # 🥕 E3.0 carrot -1F33D ; fully-qualified # 🌽 E0.6 ear of corn -1F336 FE0F ; fully-qualified # 🌶️ E0.7 hot pepper -1F336 ; unqualified # 🌶 E0.7 hot pepper -1FAD1 ; fully-qualified # 🫑 E13.0 bell pepper -1F952 ; fully-qualified # 🥒 E3.0 cucumber -1F96C ; fully-qualified # 🥬 E11.0 leafy green -1F966 ; fully-qualified # 🥦 E5.0 broccoli -1F9C4 ; fully-qualified # 🧄 E12.0 garlic -1F9C5 ; fully-qualified # 🧅 E12.0 onion -1F95C ; fully-qualified # 🥜 E3.0 peanuts -1FAD8 ; fully-qualified # 🫘 E14.0 beans -1F330 ; fully-qualified # 🌰 E0.6 chestnut -1FADA ; fully-qualified # 🫚 E15.0 ginger root -1FADB ; fully-qualified # 🫛 E15.0 pea pod - -# subgroup: food-prepared -1F35E ; fully-qualified # 🍞 E0.6 bread -1F950 ; fully-qualified # 🥐 E3.0 croissant -1F956 ; fully-qualified # 🥖 E3.0 baguette bread -1FAD3 ; fully-qualified # 🫓 E13.0 flatbread -1F968 ; fully-qualified # 🥨 E5.0 pretzel -1F96F ; fully-qualified # 🥯 E11.0 bagel -1F95E ; fully-qualified # 🥞 E3.0 pancakes -1F9C7 ; fully-qualified # 🧇 E12.0 waffle -1F9C0 ; fully-qualified # 🧀 E1.0 cheese wedge -1F356 ; fully-qualified # 🍖 E0.6 meat on bone -1F357 ; fully-qualified # 🍗 E0.6 poultry leg -1F969 ; fully-qualified # 🥩 E5.0 cut of meat -1F953 ; fully-qualified # 🥓 E3.0 bacon -1F354 ; fully-qualified # 🍔 E0.6 hamburger -1F35F ; fully-qualified # 🍟 E0.6 french fries -1F355 ; fully-qualified # 🍕 E0.6 pizza -1F32D ; fully-qualified # 🌭 E1.0 hot dog -1F96A ; fully-qualified # 🥪 E5.0 sandwich -1F32E ; fully-qualified # 🌮 E1.0 taco -1F32F ; fully-qualified # 🌯 E1.0 burrito -1FAD4 ; fully-qualified # 🫔 E13.0 tamale -1F959 ; fully-qualified # 🥙 E3.0 stuffed flatbread -1F9C6 ; fully-qualified # 🧆 E12.0 falafel -1F95A ; fully-qualified # 🥚 E3.0 egg -1F373 ; fully-qualified # 🍳 E0.6 cooking -1F958 ; fully-qualified # 🥘 E3.0 shallow pan of food -1F372 ; fully-qualified # 🍲 E0.6 pot of food -1FAD5 ; fully-qualified # 🫕 E13.0 fondue -1F963 ; fully-qualified # 🥣 E5.0 bowl with spoon -1F957 ; fully-qualified # 🥗 E3.0 green salad -1F37F ; fully-qualified # 🍿 E1.0 popcorn -1F9C8 ; fully-qualified # 🧈 E12.0 butter -1F9C2 ; fully-qualified # 🧂 E11.0 salt -1F96B ; fully-qualified # 🥫 E5.0 canned food - -# subgroup: food-asian -1F371 ; fully-qualified # 🍱 E0.6 bento box -1F358 ; fully-qualified # 🍘 E0.6 rice cracker -1F359 ; fully-qualified # 🍙 E0.6 rice ball -1F35A ; fully-qualified # 🍚 E0.6 cooked rice -1F35B ; fully-qualified # 🍛 E0.6 curry rice -1F35C ; fully-qualified # 🍜 E0.6 steaming bowl -1F35D ; fully-qualified # 🍝 E0.6 spaghetti -1F360 ; fully-qualified # 🍠 E0.6 roasted sweet potato -1F362 ; fully-qualified # 🍢 E0.6 oden -1F363 ; fully-qualified # 🍣 E0.6 sushi -1F364 ; fully-qualified # 🍤 E0.6 fried shrimp -1F365 ; fully-qualified # 🍥 E0.6 fish cake with swirl -1F96E ; fully-qualified # 🥮 E11.0 moon cake -1F361 ; fully-qualified # 🍡 E0.6 dango -1F95F ; fully-qualified # 🥟 E5.0 dumpling -1F960 ; fully-qualified # 🥠 E5.0 fortune cookie -1F961 ; fully-qualified # 🥡 E5.0 takeout box - -# subgroup: food-marine -1F980 ; fully-qualified # 🦀 E1.0 crab -1F99E ; fully-qualified # 🦞 E11.0 lobster -1F990 ; fully-qualified # 🦐 E3.0 shrimp -1F991 ; fully-qualified # 🦑 E3.0 squid -1F9AA ; fully-qualified # 🦪 E12.0 oyster - -# subgroup: food-sweet -1F366 ; fully-qualified # 🍦 E0.6 soft ice cream -1F367 ; fully-qualified # 🍧 E0.6 shaved ice -1F368 ; fully-qualified # 🍨 E0.6 ice cream -1F369 ; fully-qualified # 🍩 E0.6 doughnut -1F36A ; fully-qualified # 🍪 E0.6 cookie -1F382 ; fully-qualified # 🎂 E0.6 birthday cake -1F370 ; fully-qualified # 🍰 E0.6 shortcake -1F9C1 ; fully-qualified # 🧁 E11.0 cupcake -1F967 ; fully-qualified # 🥧 E5.0 pie -1F36B ; fully-qualified # 🍫 E0.6 chocolate bar -1F36C ; fully-qualified # 🍬 E0.6 candy -1F36D ; fully-qualified # 🍭 E0.6 lollipop -1F36E ; fully-qualified # 🍮 E0.6 custard -1F36F ; fully-qualified # 🍯 E0.6 honey pot - -# subgroup: drink -1F37C ; fully-qualified # 🍼 E1.0 baby bottle -1F95B ; fully-qualified # 🥛 E3.0 glass of milk -2615 ; fully-qualified # ☕ E0.6 hot beverage -1FAD6 ; fully-qualified # 🫖 E13.0 teapot -1F375 ; fully-qualified # 🍵 E0.6 teacup without handle -1F376 ; fully-qualified # 🍶 E0.6 sake -1F37E ; fully-qualified # 🍾 E1.0 bottle with popping cork -1F377 ; fully-qualified # 🍷 E0.6 wine glass -1F378 ; fully-qualified # 🍸 E0.6 cocktail glass -1F379 ; fully-qualified # 🍹 E0.6 tropical drink -1F37A ; fully-qualified # 🍺 E0.6 beer mug -1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs -1F942 ; fully-qualified # 🥂 E3.0 clinking glasses -1F943 ; fully-qualified # 🥃 E3.0 tumbler glass -1FAD7 ; fully-qualified # 🫗 E14.0 pouring liquid -1F964 ; fully-qualified # 🥤 E5.0 cup with straw -1F9CB ; fully-qualified # 🧋 E13.0 bubble tea -1F9C3 ; fully-qualified # 🧃 E12.0 beverage box -1F9C9 ; fully-qualified # 🧉 E12.0 mate -1F9CA ; fully-qualified # 🧊 E12.0 ice - -# subgroup: dishware -1F962 ; fully-qualified # 🥢 E5.0 chopsticks -1F37D FE0F ; fully-qualified # 🍽️ E0.7 fork and knife with plate -1F37D ; unqualified # 🍽 E0.7 fork and knife with plate -1F374 ; fully-qualified # 🍴 E0.6 fork and knife -1F944 ; fully-qualified # 🥄 E3.0 spoon -1F52A ; fully-qualified # 🔪 E0.6 kitchen knife -1FAD9 ; fully-qualified # 🫙 E14.0 jar -1F3FA ; fully-qualified # 🏺 E1.0 amphora - -# Food & Drink subtotal: 135 -# Food & Drink subtotal: 135 w/o modifiers - -# group: Travel & Places - -# subgroup: place-map -1F30D ; fully-qualified # 🌍 E0.7 globe showing Europe-Africa -1F30E ; fully-qualified # 🌎 E0.7 globe showing Americas -1F30F ; fully-qualified # 🌏 E0.6 globe showing Asia-Australia -1F310 ; fully-qualified # 🌐 E1.0 globe with meridians -1F5FA FE0F ; fully-qualified # 🗺️ E0.7 world map -1F5FA ; unqualified # 🗺 E0.7 world map -1F5FE ; fully-qualified # 🗾 E0.6 map of Japan -1F9ED ; fully-qualified # 🧭 E11.0 compass - -# subgroup: place-geographic -1F3D4 FE0F ; fully-qualified # 🏔️ E0.7 snow-capped mountain -1F3D4 ; unqualified # 🏔 E0.7 snow-capped mountain -26F0 FE0F ; fully-qualified # ⛰️ E0.7 mountain -26F0 ; unqualified # ⛰ E0.7 mountain -1F30B ; fully-qualified # 🌋 E0.6 volcano -1F5FB ; fully-qualified # 🗻 E0.6 mount fuji -1F3D5 FE0F ; fully-qualified # 🏕️ E0.7 camping -1F3D5 ; unqualified # 🏕 E0.7 camping -1F3D6 FE0F ; fully-qualified # 🏖️ E0.7 beach with umbrella -1F3D6 ; unqualified # 🏖 E0.7 beach with umbrella -1F3DC FE0F ; fully-qualified # 🏜️ E0.7 desert -1F3DC ; unqualified # 🏜 E0.7 desert -1F3DD FE0F ; fully-qualified # 🏝️ E0.7 desert island -1F3DD ; unqualified # 🏝 E0.7 desert island -1F3DE FE0F ; fully-qualified # 🏞️ E0.7 national park -1F3DE ; unqualified # 🏞 E0.7 national park - -# subgroup: place-building -1F3DF FE0F ; fully-qualified # 🏟️ E0.7 stadium -1F3DF ; unqualified # 🏟 E0.7 stadium -1F3DB FE0F ; fully-qualified # 🏛️ E0.7 classical building -1F3DB ; unqualified # 🏛 E0.7 classical building -1F3D7 FE0F ; fully-qualified # 🏗️ E0.7 building construction -1F3D7 ; unqualified # 🏗 E0.7 building construction -1F9F1 ; fully-qualified # 🧱 E11.0 brick -1FAA8 ; fully-qualified # 🪨 E13.0 rock -1FAB5 ; fully-qualified # 🪵 E13.0 wood -1F6D6 ; fully-qualified # 🛖 E13.0 hut -1F3D8 FE0F ; fully-qualified # 🏘️ E0.7 houses -1F3D8 ; unqualified # 🏘 E0.7 houses -1F3DA FE0F ; fully-qualified # 🏚️ E0.7 derelict house -1F3DA ; unqualified # 🏚 E0.7 derelict house -1F3E0 ; fully-qualified # 🏠 E0.6 house -1F3E1 ; fully-qualified # 🏡 E0.6 house with garden -1F3E2 ; fully-qualified # 🏢 E0.6 office building -1F3E3 ; fully-qualified # 🏣 E0.6 Japanese post office -1F3E4 ; fully-qualified # 🏤 E1.0 post office -1F3E5 ; fully-qualified # 🏥 E0.6 hospital -1F3E6 ; fully-qualified # 🏦 E0.6 bank -1F3E8 ; fully-qualified # 🏨 E0.6 hotel -1F3E9 ; fully-qualified # 🏩 E0.6 love hotel -1F3EA ; fully-qualified # 🏪 E0.6 convenience store -1F3EB ; fully-qualified # 🏫 E0.6 school -1F3EC ; fully-qualified # 🏬 E0.6 department store -1F3ED ; fully-qualified # 🏭 E0.6 factory -1F3EF ; fully-qualified # 🏯 E0.6 Japanese castle -1F3F0 ; fully-qualified # 🏰 E0.6 castle -1F492 ; fully-qualified # 💒 E0.6 wedding -1F5FC ; fully-qualified # 🗼 E0.6 Tokyo tower -1F5FD ; fully-qualified # 🗽 E0.6 Statue of Liberty - -# subgroup: place-religious -26EA ; fully-qualified # ⛪ E0.6 church -1F54C ; fully-qualified # 🕌 E1.0 mosque -1F6D5 ; fully-qualified # 🛕 E12.0 hindu temple -1F54D ; fully-qualified # 🕍 E1.0 synagogue -26E9 FE0F ; fully-qualified # ⛩️ E0.7 shinto shrine -26E9 ; unqualified # ⛩ E0.7 shinto shrine -1F54B ; fully-qualified # 🕋 E1.0 kaaba - -# subgroup: place-other -26F2 ; fully-qualified # ⛲ E0.6 fountain -26FA ; fully-qualified # ⛺ E0.6 tent -1F301 ; fully-qualified # 🌁 E0.6 foggy -1F303 ; fully-qualified # 🌃 E0.6 night with stars -1F3D9 FE0F ; fully-qualified # 🏙️ E0.7 cityscape -1F3D9 ; unqualified # 🏙 E0.7 cityscape -1F304 ; fully-qualified # 🌄 E0.6 sunrise over mountains -1F305 ; fully-qualified # 🌅 E0.6 sunrise -1F306 ; fully-qualified # 🌆 E0.6 cityscape at dusk -1F307 ; fully-qualified # 🌇 E0.6 sunset -1F309 ; fully-qualified # 🌉 E0.6 bridge at night -2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs -2668 ; unqualified # ♨ E0.6 hot springs -1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse -1F6DD ; fully-qualified # 🛝 E14.0 playground slide -1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel -1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster -1F488 ; fully-qualified # 💈 E0.6 barber pole -1F3AA ; fully-qualified # 🎪 E0.6 circus tent - -# subgroup: transport-ground -1F682 ; fully-qualified # 🚂 E1.0 locomotive -1F683 ; fully-qualified # 🚃 E0.6 railway car -1F684 ; fully-qualified # 🚄 E0.6 high-speed train -1F685 ; fully-qualified # 🚅 E0.6 bullet train -1F686 ; fully-qualified # 🚆 E1.0 train -1F687 ; fully-qualified # 🚇 E0.6 metro -1F688 ; fully-qualified # 🚈 E1.0 light rail -1F689 ; fully-qualified # 🚉 E0.6 station -1F68A ; fully-qualified # 🚊 E1.0 tram -1F69D ; fully-qualified # 🚝 E1.0 monorail -1F69E ; fully-qualified # 🚞 E1.0 mountain railway -1F68B ; fully-qualified # 🚋 E1.0 tram car -1F68C ; fully-qualified # 🚌 E0.6 bus -1F68D ; fully-qualified # 🚍 E0.7 oncoming bus -1F68E ; fully-qualified # 🚎 E1.0 trolleybus -1F690 ; fully-qualified # 🚐 E1.0 minibus -1F691 ; fully-qualified # 🚑 E0.6 ambulance -1F692 ; fully-qualified # 🚒 E0.6 fire engine -1F693 ; fully-qualified # 🚓 E0.6 police car -1F694 ; fully-qualified # 🚔 E0.7 oncoming police car -1F695 ; fully-qualified # 🚕 E0.6 taxi -1F696 ; fully-qualified # 🚖 E1.0 oncoming taxi -1F697 ; fully-qualified # 🚗 E0.6 automobile -1F698 ; fully-qualified # 🚘 E0.7 oncoming automobile -1F699 ; fully-qualified # 🚙 E0.6 sport utility vehicle -1F6FB ; fully-qualified # 🛻 E13.0 pickup truck -1F69A ; fully-qualified # 🚚 E0.6 delivery truck -1F69B ; fully-qualified # 🚛 E1.0 articulated lorry -1F69C ; fully-qualified # 🚜 E1.0 tractor -1F3CE FE0F ; fully-qualified # 🏎️ E0.7 racing car -1F3CE ; unqualified # 🏎 E0.7 racing car -1F3CD FE0F ; fully-qualified # 🏍️ E0.7 motorcycle -1F3CD ; unqualified # 🏍 E0.7 motorcycle -1F6F5 ; fully-qualified # 🛵 E3.0 motor scooter -1F9BD ; fully-qualified # 🦽 E12.0 manual wheelchair -1F9BC ; fully-qualified # 🦼 E12.0 motorized wheelchair -1F6FA ; fully-qualified # 🛺 E12.0 auto rickshaw -1F6B2 ; fully-qualified # 🚲 E0.6 bicycle -1F6F4 ; fully-qualified # 🛴 E3.0 kick scooter -1F6F9 ; fully-qualified # 🛹 E11.0 skateboard -1F6FC ; fully-qualified # 🛼 E13.0 roller skate -1F68F ; fully-qualified # 🚏 E0.6 bus stop -1F6E3 FE0F ; fully-qualified # 🛣️ E0.7 motorway -1F6E3 ; unqualified # 🛣 E0.7 motorway -1F6E4 FE0F ; fully-qualified # 🛤️ E0.7 railway track -1F6E4 ; unqualified # 🛤 E0.7 railway track -1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum -1F6E2 ; unqualified # 🛢 E0.7 oil drum -26FD ; fully-qualified # ⛽ E0.6 fuel pump -1F6DE ; fully-qualified # 🛞 E14.0 wheel -1F6A8 ; fully-qualified # 🚨 E0.6 police car light -1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light -1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light -1F6D1 ; fully-qualified # 🛑 E3.0 stop sign -1F6A7 ; fully-qualified # 🚧 E0.6 construction - -# subgroup: transport-water -2693 ; fully-qualified # ⚓ E0.6 anchor -1F6DF ; fully-qualified # 🛟 E14.0 ring buoy -26F5 ; fully-qualified # ⛵ E0.6 sailboat -1F6F6 ; fully-qualified # 🛶 E3.0 canoe -1F6A4 ; fully-qualified # 🚤 E0.6 speedboat -1F6F3 FE0F ; fully-qualified # 🛳️ E0.7 passenger ship -1F6F3 ; unqualified # 🛳 E0.7 passenger ship -26F4 FE0F ; fully-qualified # ⛴️ E0.7 ferry -26F4 ; unqualified # ⛴ E0.7 ferry -1F6E5 FE0F ; fully-qualified # 🛥️ E0.7 motor boat -1F6E5 ; unqualified # 🛥 E0.7 motor boat -1F6A2 ; fully-qualified # 🚢 E0.6 ship - -# subgroup: transport-air -2708 FE0F ; fully-qualified # ✈️ E0.6 airplane -2708 ; unqualified # ✈ E0.6 airplane -1F6E9 FE0F ; fully-qualified # 🛩️ E0.7 small airplane -1F6E9 ; unqualified # 🛩 E0.7 small airplane -1F6EB ; fully-qualified # 🛫 E1.0 airplane departure -1F6EC ; fully-qualified # 🛬 E1.0 airplane arrival -1FA82 ; fully-qualified # 🪂 E12.0 parachute -1F4BA ; fully-qualified # 💺 E0.6 seat -1F681 ; fully-qualified # 🚁 E1.0 helicopter -1F69F ; fully-qualified # 🚟 E1.0 suspension railway -1F6A0 ; fully-qualified # 🚠 E1.0 mountain cableway -1F6A1 ; fully-qualified # 🚡 E1.0 aerial tramway -1F6F0 FE0F ; fully-qualified # 🛰️ E0.7 satellite -1F6F0 ; unqualified # 🛰 E0.7 satellite -1F680 ; fully-qualified # 🚀 E0.6 rocket -1F6F8 ; fully-qualified # 🛸 E5.0 flying saucer - -# subgroup: hotel -1F6CE FE0F ; fully-qualified # 🛎️ E0.7 bellhop bell -1F6CE ; unqualified # 🛎 E0.7 bellhop bell -1F9F3 ; fully-qualified # 🧳 E11.0 luggage - -# subgroup: time -231B ; fully-qualified # ⌛ E0.6 hourglass done -23F3 ; fully-qualified # ⏳ E0.6 hourglass not done -231A ; fully-qualified # ⌚ E0.6 watch -23F0 ; fully-qualified # ⏰ E0.6 alarm clock -23F1 FE0F ; fully-qualified # ⏱️ E1.0 stopwatch -23F1 ; unqualified # ⏱ E1.0 stopwatch -23F2 FE0F ; fully-qualified # ⏲️ E1.0 timer clock -23F2 ; unqualified # ⏲ E1.0 timer clock -1F570 FE0F ; fully-qualified # 🕰️ E0.7 mantelpiece clock -1F570 ; unqualified # 🕰 E0.7 mantelpiece clock -1F55B ; fully-qualified # 🕛 E0.6 twelve o’clock -1F567 ; fully-qualified # 🕧 E0.7 twelve-thirty -1F550 ; fully-qualified # 🕐 E0.6 one o’clock -1F55C ; fully-qualified # 🕜 E0.7 one-thirty -1F551 ; fully-qualified # 🕑 E0.6 two o’clock -1F55D ; fully-qualified # 🕝 E0.7 two-thirty -1F552 ; fully-qualified # 🕒 E0.6 three o’clock -1F55E ; fully-qualified # 🕞 E0.7 three-thirty -1F553 ; fully-qualified # 🕓 E0.6 four o’clock -1F55F ; fully-qualified # 🕟 E0.7 four-thirty -1F554 ; fully-qualified # 🕔 E0.6 five o’clock -1F560 ; fully-qualified # 🕠 E0.7 five-thirty -1F555 ; fully-qualified # 🕕 E0.6 six o’clock -1F561 ; fully-qualified # 🕡 E0.7 six-thirty -1F556 ; fully-qualified # 🕖 E0.6 seven o’clock -1F562 ; fully-qualified # 🕢 E0.7 seven-thirty -1F557 ; fully-qualified # 🕗 E0.6 eight o’clock -1F563 ; fully-qualified # 🕣 E0.7 eight-thirty -1F558 ; fully-qualified # 🕘 E0.6 nine o’clock -1F564 ; fully-qualified # 🕤 E0.7 nine-thirty -1F559 ; fully-qualified # 🕙 E0.6 ten o’clock -1F565 ; fully-qualified # 🕥 E0.7 ten-thirty -1F55A ; fully-qualified # 🕚 E0.6 eleven o’clock -1F566 ; fully-qualified # 🕦 E0.7 eleven-thirty - -# subgroup: sky & weather -1F311 ; fully-qualified # 🌑 E0.6 new moon -1F312 ; fully-qualified # 🌒 E1.0 waxing crescent moon -1F313 ; fully-qualified # 🌓 E0.6 first quarter moon -1F314 ; fully-qualified # 🌔 E0.6 waxing gibbous moon -1F315 ; fully-qualified # 🌕 E0.6 full moon -1F316 ; fully-qualified # 🌖 E1.0 waning gibbous moon -1F317 ; fully-qualified # 🌗 E1.0 last quarter moon -1F318 ; fully-qualified # 🌘 E1.0 waning crescent moon -1F319 ; fully-qualified # 🌙 E0.6 crescent moon -1F31A ; fully-qualified # 🌚 E1.0 new moon face -1F31B ; fully-qualified # 🌛 E0.6 first quarter moon face -1F31C ; fully-qualified # 🌜 E0.7 last quarter moon face -1F321 FE0F ; fully-qualified # 🌡️ E0.7 thermometer -1F321 ; unqualified # 🌡 E0.7 thermometer -2600 FE0F ; fully-qualified # ☀️ E0.6 sun -2600 ; unqualified # ☀ E0.6 sun -1F31D ; fully-qualified # 🌝 E1.0 full moon face -1F31E ; fully-qualified # 🌞 E1.0 sun with face -1FA90 ; fully-qualified # 🪐 E12.0 ringed planet -2B50 ; fully-qualified # ⭐ E0.6 star -1F31F ; fully-qualified # 🌟 E0.6 glowing star -1F320 ; fully-qualified # 🌠 E0.6 shooting star -1F30C ; fully-qualified # 🌌 E0.6 milky way -2601 FE0F ; fully-qualified # ☁️ E0.6 cloud -2601 ; unqualified # ☁ E0.6 cloud -26C5 ; fully-qualified # ⛅ E0.6 sun behind cloud -26C8 FE0F ; fully-qualified # ⛈️ E0.7 cloud with lightning and rain -26C8 ; unqualified # ⛈ E0.7 cloud with lightning and rain -1F324 FE0F ; fully-qualified # 🌤️ E0.7 sun behind small cloud -1F324 ; unqualified # 🌤 E0.7 sun behind small cloud -1F325 FE0F ; fully-qualified # 🌥️ E0.7 sun behind large cloud -1F325 ; unqualified # 🌥 E0.7 sun behind large cloud -1F326 FE0F ; fully-qualified # 🌦️ E0.7 sun behind rain cloud -1F326 ; unqualified # 🌦 E0.7 sun behind rain cloud -1F327 FE0F ; fully-qualified # 🌧️ E0.7 cloud with rain -1F327 ; unqualified # 🌧 E0.7 cloud with rain -1F328 FE0F ; fully-qualified # 🌨️ E0.7 cloud with snow -1F328 ; unqualified # 🌨 E0.7 cloud with snow -1F329 FE0F ; fully-qualified # 🌩️ E0.7 cloud with lightning -1F329 ; unqualified # 🌩 E0.7 cloud with lightning -1F32A FE0F ; fully-qualified # 🌪️ E0.7 tornado -1F32A ; unqualified # 🌪 E0.7 tornado -1F32B FE0F ; fully-qualified # 🌫️ E0.7 fog -1F32B ; unqualified # 🌫 E0.7 fog -1F32C FE0F ; fully-qualified # 🌬️ E0.7 wind face -1F32C ; unqualified # 🌬 E0.7 wind face -1F300 ; fully-qualified # 🌀 E0.6 cyclone -1F308 ; fully-qualified # 🌈 E0.6 rainbow -1F302 ; fully-qualified # 🌂 E0.6 closed umbrella -2602 FE0F ; fully-qualified # ☂️ E0.7 umbrella -2602 ; unqualified # ☂ E0.7 umbrella -2614 ; fully-qualified # ☔ E0.6 umbrella with rain drops -26F1 FE0F ; fully-qualified # ⛱️ E0.7 umbrella on ground -26F1 ; unqualified # ⛱ E0.7 umbrella on ground -26A1 ; fully-qualified # ⚡ E0.6 high voltage -2744 FE0F ; fully-qualified # ❄️ E0.6 snowflake -2744 ; unqualified # ❄ E0.6 snowflake -2603 FE0F ; fully-qualified # ☃️ E0.7 snowman -2603 ; unqualified # ☃ E0.7 snowman -26C4 ; fully-qualified # ⛄ E0.6 snowman without snow -2604 FE0F ; fully-qualified # ☄️ E1.0 comet -2604 ; unqualified # ☄ E1.0 comet -1F525 ; fully-qualified # 🔥 E0.6 fire -1F4A7 ; fully-qualified # 💧 E0.6 droplet -1F30A ; fully-qualified # 🌊 E0.6 water wave - -# Travel & Places subtotal: 267 -# Travel & Places subtotal: 267 w/o modifiers - -# group: Activities - -# subgroup: event -1F383 ; fully-qualified # 🎃 E0.6 jack-o-lantern -1F384 ; fully-qualified # 🎄 E0.6 Christmas tree -1F386 ; fully-qualified # 🎆 E0.6 fireworks -1F387 ; fully-qualified # 🎇 E0.6 sparkler -1F9E8 ; fully-qualified # 🧨 E11.0 firecracker -2728 ; fully-qualified # ✨ E0.6 sparkles -1F388 ; fully-qualified # 🎈 E0.6 balloon -1F389 ; fully-qualified # 🎉 E0.6 party popper -1F38A ; fully-qualified # 🎊 E0.6 confetti ball -1F38B ; fully-qualified # 🎋 E0.6 tanabata tree -1F38D ; fully-qualified # 🎍 E0.6 pine decoration -1F38E ; fully-qualified # 🎎 E0.6 Japanese dolls -1F38F ; fully-qualified # 🎏 E0.6 carp streamer -1F390 ; fully-qualified # 🎐 E0.6 wind chime -1F391 ; fully-qualified # 🎑 E0.6 moon viewing ceremony -1F9E7 ; fully-qualified # 🧧 E11.0 red envelope -1F380 ; fully-qualified # 🎀 E0.6 ribbon -1F381 ; fully-qualified # 🎁 E0.6 wrapped gift -1F397 FE0F ; fully-qualified # 🎗️ E0.7 reminder ribbon -1F397 ; unqualified # 🎗 E0.7 reminder ribbon -1F39F FE0F ; fully-qualified # 🎟️ E0.7 admission tickets -1F39F ; unqualified # 🎟 E0.7 admission tickets -1F3AB ; fully-qualified # 🎫 E0.6 ticket - -# subgroup: award-medal -1F396 FE0F ; fully-qualified # 🎖️ E0.7 military medal -1F396 ; unqualified # 🎖 E0.7 military medal -1F3C6 ; fully-qualified # 🏆 E0.6 trophy -1F3C5 ; fully-qualified # 🏅 E1.0 sports medal -1F947 ; fully-qualified # 🥇 E3.0 1st place medal -1F948 ; fully-qualified # 🥈 E3.0 2nd place medal -1F949 ; fully-qualified # 🥉 E3.0 3rd place medal - -# subgroup: sport -26BD ; fully-qualified # ⚽ E0.6 soccer ball -26BE ; fully-qualified # ⚾ E0.6 baseball -1F94E ; fully-qualified # 🥎 E11.0 softball -1F3C0 ; fully-qualified # 🏀 E0.6 basketball -1F3D0 ; fully-qualified # 🏐 E1.0 volleyball -1F3C8 ; fully-qualified # 🏈 E0.6 american football -1F3C9 ; fully-qualified # 🏉 E1.0 rugby football -1F3BE ; fully-qualified # 🎾 E0.6 tennis -1F94F ; fully-qualified # 🥏 E11.0 flying disc -1F3B3 ; fully-qualified # 🎳 E0.6 bowling -1F3CF ; fully-qualified # 🏏 E1.0 cricket game -1F3D1 ; fully-qualified # 🏑 E1.0 field hockey -1F3D2 ; fully-qualified # 🏒 E1.0 ice hockey -1F94D ; fully-qualified # 🥍 E11.0 lacrosse -1F3D3 ; fully-qualified # 🏓 E1.0 ping pong -1F3F8 ; fully-qualified # 🏸 E1.0 badminton -1F94A ; fully-qualified # 🥊 E3.0 boxing glove -1F94B ; fully-qualified # 🥋 E3.0 martial arts uniform -1F945 ; fully-qualified # 🥅 E3.0 goal net -26F3 ; fully-qualified # ⛳ E0.6 flag in hole -26F8 FE0F ; fully-qualified # ⛸️ E0.7 ice skate -26F8 ; unqualified # ⛸ E0.7 ice skate -1F3A3 ; fully-qualified # 🎣 E0.6 fishing pole -1F93F ; fully-qualified # 🤿 E12.0 diving mask -1F3BD ; fully-qualified # 🎽 E0.6 running shirt -1F3BF ; fully-qualified # 🎿 E0.6 skis -1F6F7 ; fully-qualified # 🛷 E5.0 sled -1F94C ; fully-qualified # 🥌 E5.0 curling stone - -# subgroup: game -1F3AF ; fully-qualified # 🎯 E0.6 bullseye -1FA80 ; fully-qualified # 🪀 E12.0 yo-yo -1FA81 ; fully-qualified # 🪁 E12.0 kite -1F52B ; fully-qualified # 🔫 E0.6 water pistol -1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball -1F52E ; fully-qualified # 🔮 E0.6 crystal ball -1FA84 ; fully-qualified # 🪄 E13.0 magic wand -1F3AE ; fully-qualified # 🎮 E0.6 video game -1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick -1F579 ; unqualified # 🕹 E0.7 joystick -1F3B0 ; fully-qualified # 🎰 E0.6 slot machine -1F3B2 ; fully-qualified # 🎲 E0.6 game die -1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece -1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear -1FA85 ; fully-qualified # 🪅 E13.0 piñata -1FAA9 ; fully-qualified # 🪩 E14.0 mirror ball -1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls -2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit -2660 ; unqualified # ♠ E0.6 spade suit -2665 FE0F ; fully-qualified # ♥️ E0.6 heart suit -2665 ; unqualified # ♥ E0.6 heart suit -2666 FE0F ; fully-qualified # ♦️ E0.6 diamond suit -2666 ; unqualified # ♦ E0.6 diamond suit -2663 FE0F ; fully-qualified # ♣️ E0.6 club suit -2663 ; unqualified # ♣ E0.6 club suit -265F FE0F ; fully-qualified # ♟️ E11.0 chess pawn -265F ; unqualified # ♟ E11.0 chess pawn -1F0CF ; fully-qualified # 🃏 E0.6 joker -1F004 ; fully-qualified # 🀄 E0.6 mahjong red dragon -1F3B4 ; fully-qualified # 🎴 E0.6 flower playing cards - -# subgroup: arts & crafts -1F3AD ; fully-qualified # 🎭 E0.6 performing arts -1F5BC FE0F ; fully-qualified # 🖼️ E0.7 framed picture -1F5BC ; unqualified # 🖼 E0.7 framed picture -1F3A8 ; fully-qualified # 🎨 E0.6 artist palette -1F9F5 ; fully-qualified # 🧵 E11.0 thread -1FAA1 ; fully-qualified # 🪡 E13.0 sewing needle -1F9F6 ; fully-qualified # 🧶 E11.0 yarn -1FAA2 ; fully-qualified # 🪢 E13.0 knot - -# Activities subtotal: 96 -# Activities subtotal: 96 w/o modifiers - -# group: Objects - -# subgroup: clothing -1F453 ; fully-qualified # 👓 E0.6 glasses -1F576 FE0F ; fully-qualified # 🕶️ E0.7 sunglasses -1F576 ; unqualified # 🕶 E0.7 sunglasses -1F97D ; fully-qualified # 🥽 E11.0 goggles -1F97C ; fully-qualified # 🥼 E11.0 lab coat -1F9BA ; fully-qualified # 🦺 E12.0 safety vest -1F454 ; fully-qualified # 👔 E0.6 necktie -1F455 ; fully-qualified # 👕 E0.6 t-shirt -1F456 ; fully-qualified # 👖 E0.6 jeans -1F9E3 ; fully-qualified # 🧣 E5.0 scarf -1F9E4 ; fully-qualified # 🧤 E5.0 gloves -1F9E5 ; fully-qualified # 🧥 E5.0 coat -1F9E6 ; fully-qualified # 🧦 E5.0 socks -1F457 ; fully-qualified # 👗 E0.6 dress -1F458 ; fully-qualified # 👘 E0.6 kimono -1F97B ; fully-qualified # 🥻 E12.0 sari -1FA71 ; fully-qualified # 🩱 E12.0 one-piece swimsuit -1FA72 ; fully-qualified # 🩲 E12.0 briefs -1FA73 ; fully-qualified # 🩳 E12.0 shorts -1F459 ; fully-qualified # 👙 E0.6 bikini -1F45A ; fully-qualified # 👚 E0.6 woman’s clothes -1FAAD ; fully-qualified # 🪭 E15.0 folding hand fan -1F45B ; fully-qualified # 👛 E0.6 purse -1F45C ; fully-qualified # 👜 E0.6 handbag -1F45D ; fully-qualified # 👝 E0.6 clutch bag -1F6CD FE0F ; fully-qualified # 🛍️ E0.7 shopping bags -1F6CD ; unqualified # 🛍 E0.7 shopping bags -1F392 ; fully-qualified # 🎒 E0.6 backpack -1FA74 ; fully-qualified # 🩴 E13.0 thong sandal -1F45E ; fully-qualified # 👞 E0.6 man’s shoe -1F45F ; fully-qualified # 👟 E0.6 running shoe -1F97E ; fully-qualified # 🥾 E11.0 hiking boot -1F97F ; fully-qualified # 🥿 E11.0 flat shoe -1F460 ; fully-qualified # 👠 E0.6 high-heeled shoe -1F461 ; fully-qualified # 👡 E0.6 woman’s sandal -1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes -1F462 ; fully-qualified # 👢 E0.6 woman’s boot -1FAAE ; fully-qualified # 🪮 E15.0 hair pick -1F451 ; fully-qualified # 👑 E0.6 crown -1F452 ; fully-qualified # 👒 E0.6 woman’s hat -1F3A9 ; fully-qualified # 🎩 E0.6 top hat -1F393 ; fully-qualified # 🎓 E0.6 graduation cap -1F9E2 ; fully-qualified # 🧢 E5.0 billed cap -1FA96 ; fully-qualified # 🪖 E13.0 military helmet -26D1 FE0F ; fully-qualified # ⛑️ E0.7 rescue worker’s helmet -26D1 ; unqualified # ⛑ E0.7 rescue worker’s helmet -1F4FF ; fully-qualified # 📿 E1.0 prayer beads -1F484 ; fully-qualified # 💄 E0.6 lipstick -1F48D ; fully-qualified # 💍 E0.6 ring -1F48E ; fully-qualified # 💎 E0.6 gem stone - -# subgroup: sound -1F507 ; fully-qualified # 🔇 E1.0 muted speaker -1F508 ; fully-qualified # 🔈 E0.7 speaker low volume -1F509 ; fully-qualified # 🔉 E1.0 speaker medium volume -1F50A ; fully-qualified # 🔊 E0.6 speaker high volume -1F4E2 ; fully-qualified # 📢 E0.6 loudspeaker -1F4E3 ; fully-qualified # 📣 E0.6 megaphone -1F4EF ; fully-qualified # 📯 E1.0 postal horn -1F514 ; fully-qualified # 🔔 E0.6 bell -1F515 ; fully-qualified # 🔕 E1.0 bell with slash - -# subgroup: music -1F3BC ; fully-qualified # 🎼 E0.6 musical score -1F3B5 ; fully-qualified # 🎵 E0.6 musical note -1F3B6 ; fully-qualified # 🎶 E0.6 musical notes -1F399 FE0F ; fully-qualified # 🎙️ E0.7 studio microphone -1F399 ; unqualified # 🎙 E0.7 studio microphone -1F39A FE0F ; fully-qualified # 🎚️ E0.7 level slider -1F39A ; unqualified # 🎚 E0.7 level slider -1F39B FE0F ; fully-qualified # 🎛️ E0.7 control knobs -1F39B ; unqualified # 🎛 E0.7 control knobs -1F3A4 ; fully-qualified # 🎤 E0.6 microphone -1F3A7 ; fully-qualified # 🎧 E0.6 headphone -1F4FB ; fully-qualified # 📻 E0.6 radio - -# subgroup: musical-instrument -1F3B7 ; fully-qualified # 🎷 E0.6 saxophone -1FA97 ; fully-qualified # 🪗 E13.0 accordion -1F3B8 ; fully-qualified # 🎸 E0.6 guitar -1F3B9 ; fully-qualified # 🎹 E0.6 musical keyboard -1F3BA ; fully-qualified # 🎺 E0.6 trumpet -1F3BB ; fully-qualified # 🎻 E0.6 violin -1FA95 ; fully-qualified # 🪕 E12.0 banjo -1F941 ; fully-qualified # 🥁 E3.0 drum -1FA98 ; fully-qualified # 🪘 E13.0 long drum -1FA87 ; fully-qualified # 🪇 E15.0 maracas -1FA88 ; fully-qualified # 🪈 E15.0 flute - -# subgroup: phone -1F4F1 ; fully-qualified # 📱 E0.6 mobile phone -1F4F2 ; fully-qualified # 📲 E0.6 mobile phone with arrow -260E FE0F ; fully-qualified # ☎️ E0.6 telephone -260E ; unqualified # ☎ E0.6 telephone -1F4DE ; fully-qualified # 📞 E0.6 telephone receiver -1F4DF ; fully-qualified # 📟 E0.6 pager -1F4E0 ; fully-qualified # 📠 E0.6 fax machine - -# subgroup: computer -1F50B ; fully-qualified # 🔋 E0.6 battery -1FAAB ; fully-qualified # 🪫 E14.0 low battery -1F50C ; fully-qualified # 🔌 E0.6 electric plug -1F4BB ; fully-qualified # 💻 E0.6 laptop -1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer -1F5A5 ; unqualified # 🖥 E0.7 desktop computer -1F5A8 FE0F ; fully-qualified # 🖨️ E0.7 printer -1F5A8 ; unqualified # 🖨 E0.7 printer -2328 FE0F ; fully-qualified # ⌨️ E1.0 keyboard -2328 ; unqualified # ⌨ E1.0 keyboard -1F5B1 FE0F ; fully-qualified # 🖱️ E0.7 computer mouse -1F5B1 ; unqualified # 🖱 E0.7 computer mouse -1F5B2 FE0F ; fully-qualified # 🖲️ E0.7 trackball -1F5B2 ; unqualified # 🖲 E0.7 trackball -1F4BD ; fully-qualified # 💽 E0.6 computer disk -1F4BE ; fully-qualified # 💾 E0.6 floppy disk -1F4BF ; fully-qualified # 💿 E0.6 optical disk -1F4C0 ; fully-qualified # 📀 E0.6 dvd -1F9EE ; fully-qualified # 🧮 E11.0 abacus - -# subgroup: light & video -1F3A5 ; fully-qualified # 🎥 E0.6 movie camera -1F39E FE0F ; fully-qualified # 🎞️ E0.7 film frames -1F39E ; unqualified # 🎞 E0.7 film frames -1F4FD FE0F ; fully-qualified # 📽️ E0.7 film projector -1F4FD ; unqualified # 📽 E0.7 film projector -1F3AC ; fully-qualified # 🎬 E0.6 clapper board -1F4FA ; fully-qualified # 📺 E0.6 television -1F4F7 ; fully-qualified # 📷 E0.6 camera -1F4F8 ; fully-qualified # 📸 E1.0 camera with flash -1F4F9 ; fully-qualified # 📹 E0.6 video camera -1F4FC ; fully-qualified # 📼 E0.6 videocassette -1F50D ; fully-qualified # 🔍 E0.6 magnifying glass tilted left -1F50E ; fully-qualified # 🔎 E0.6 magnifying glass tilted right -1F56F FE0F ; fully-qualified # 🕯️ E0.7 candle -1F56F ; unqualified # 🕯 E0.7 candle -1F4A1 ; fully-qualified # 💡 E0.6 light bulb -1F526 ; fully-qualified # 🔦 E0.6 flashlight -1F3EE ; fully-qualified # 🏮 E0.6 red paper lantern -1FA94 ; fully-qualified # 🪔 E12.0 diya lamp - -# subgroup: book-paper -1F4D4 ; fully-qualified # 📔 E0.6 notebook with decorative cover -1F4D5 ; fully-qualified # 📕 E0.6 closed book -1F4D6 ; fully-qualified # 📖 E0.6 open book -1F4D7 ; fully-qualified # 📗 E0.6 green book -1F4D8 ; fully-qualified # 📘 E0.6 blue book -1F4D9 ; fully-qualified # 📙 E0.6 orange book -1F4DA ; fully-qualified # 📚 E0.6 books -1F4D3 ; fully-qualified # 📓 E0.6 notebook -1F4D2 ; fully-qualified # 📒 E0.6 ledger -1F4C3 ; fully-qualified # 📃 E0.6 page with curl -1F4DC ; fully-qualified # 📜 E0.6 scroll -1F4C4 ; fully-qualified # 📄 E0.6 page facing up -1F4F0 ; fully-qualified # 📰 E0.6 newspaper -1F5DE FE0F ; fully-qualified # 🗞️ E0.7 rolled-up newspaper -1F5DE ; unqualified # 🗞 E0.7 rolled-up newspaper -1F4D1 ; fully-qualified # 📑 E0.6 bookmark tabs -1F516 ; fully-qualified # 🔖 E0.6 bookmark -1F3F7 FE0F ; fully-qualified # 🏷️ E0.7 label -1F3F7 ; unqualified # 🏷 E0.7 label - -# subgroup: money -1F4B0 ; fully-qualified # 💰 E0.6 money bag -1FA99 ; fully-qualified # 🪙 E13.0 coin -1F4B4 ; fully-qualified # 💴 E0.6 yen banknote -1F4B5 ; fully-qualified # 💵 E0.6 dollar banknote -1F4B6 ; fully-qualified # 💶 E1.0 euro banknote -1F4B7 ; fully-qualified # 💷 E1.0 pound banknote -1F4B8 ; fully-qualified # 💸 E0.6 money with wings -1F4B3 ; fully-qualified # 💳 E0.6 credit card -1F9FE ; fully-qualified # 🧾 E11.0 receipt -1F4B9 ; fully-qualified # 💹 E0.6 chart increasing with yen - -# subgroup: mail -2709 FE0F ; fully-qualified # ✉️ E0.6 envelope -2709 ; unqualified # ✉ E0.6 envelope -1F4E7 ; fully-qualified # 📧 E0.6 e-mail -1F4E8 ; fully-qualified # 📨 E0.6 incoming envelope -1F4E9 ; fully-qualified # 📩 E0.6 envelope with arrow -1F4E4 ; fully-qualified # 📤 E0.6 outbox tray -1F4E5 ; fully-qualified # 📥 E0.6 inbox tray -1F4E6 ; fully-qualified # 📦 E0.6 package -1F4EB ; fully-qualified # 📫 E0.6 closed mailbox with raised flag -1F4EA ; fully-qualified # 📪 E0.6 closed mailbox with lowered flag -1F4EC ; fully-qualified # 📬 E0.7 open mailbox with raised flag -1F4ED ; fully-qualified # 📭 E0.7 open mailbox with lowered flag -1F4EE ; fully-qualified # 📮 E0.6 postbox -1F5F3 FE0F ; fully-qualified # 🗳️ E0.7 ballot box with ballot -1F5F3 ; unqualified # 🗳 E0.7 ballot box with ballot - -# subgroup: writing -270F FE0F ; fully-qualified # ✏️ E0.6 pencil -270F ; unqualified # ✏ E0.6 pencil -2712 FE0F ; fully-qualified # ✒️ E0.6 black nib -2712 ; unqualified # ✒ E0.6 black nib -1F58B FE0F ; fully-qualified # 🖋️ E0.7 fountain pen -1F58B ; unqualified # 🖋 E0.7 fountain pen -1F58A FE0F ; fully-qualified # 🖊️ E0.7 pen -1F58A ; unqualified # 🖊 E0.7 pen -1F58C FE0F ; fully-qualified # 🖌️ E0.7 paintbrush -1F58C ; unqualified # 🖌 E0.7 paintbrush -1F58D FE0F ; fully-qualified # 🖍️ E0.7 crayon -1F58D ; unqualified # 🖍 E0.7 crayon -1F4DD ; fully-qualified # 📝 E0.6 memo - -# subgroup: office -1F4BC ; fully-qualified # 💼 E0.6 briefcase -1F4C1 ; fully-qualified # 📁 E0.6 file folder -1F4C2 ; fully-qualified # 📂 E0.6 open file folder -1F5C2 FE0F ; fully-qualified # 🗂️ E0.7 card index dividers -1F5C2 ; unqualified # 🗂 E0.7 card index dividers -1F4C5 ; fully-qualified # 📅 E0.6 calendar -1F4C6 ; fully-qualified # 📆 E0.6 tear-off calendar -1F5D2 FE0F ; fully-qualified # 🗒️ E0.7 spiral notepad -1F5D2 ; unqualified # 🗒 E0.7 spiral notepad -1F5D3 FE0F ; fully-qualified # 🗓️ E0.7 spiral calendar -1F5D3 ; unqualified # 🗓 E0.7 spiral calendar -1F4C7 ; fully-qualified # 📇 E0.6 card index -1F4C8 ; fully-qualified # 📈 E0.6 chart increasing -1F4C9 ; fully-qualified # 📉 E0.6 chart decreasing -1F4CA ; fully-qualified # 📊 E0.6 bar chart -1F4CB ; fully-qualified # 📋 E0.6 clipboard -1F4CC ; fully-qualified # 📌 E0.6 pushpin -1F4CD ; fully-qualified # 📍 E0.6 round pushpin -1F4CE ; fully-qualified # 📎 E0.6 paperclip -1F587 FE0F ; fully-qualified # 🖇️ E0.7 linked paperclips -1F587 ; unqualified # 🖇 E0.7 linked paperclips -1F4CF ; fully-qualified # 📏 E0.6 straight ruler -1F4D0 ; fully-qualified # 📐 E0.6 triangular ruler -2702 FE0F ; fully-qualified # ✂️ E0.6 scissors -2702 ; unqualified # ✂ E0.6 scissors -1F5C3 FE0F ; fully-qualified # 🗃️ E0.7 card file box -1F5C3 ; unqualified # 🗃 E0.7 card file box -1F5C4 FE0F ; fully-qualified # 🗄️ E0.7 file cabinet -1F5C4 ; unqualified # 🗄 E0.7 file cabinet -1F5D1 FE0F ; fully-qualified # 🗑️ E0.7 wastebasket -1F5D1 ; unqualified # 🗑 E0.7 wastebasket - -# subgroup: lock -1F512 ; fully-qualified # 🔒 E0.6 locked -1F513 ; fully-qualified # 🔓 E0.6 unlocked -1F50F ; fully-qualified # 🔏 E0.6 locked with pen -1F510 ; fully-qualified # 🔐 E0.6 locked with key -1F511 ; fully-qualified # 🔑 E0.6 key -1F5DD FE0F ; fully-qualified # 🗝️ E0.7 old key -1F5DD ; unqualified # 🗝 E0.7 old key - -# subgroup: tool -1F528 ; fully-qualified # 🔨 E0.6 hammer -1FA93 ; fully-qualified # 🪓 E12.0 axe -26CF FE0F ; fully-qualified # ⛏️ E0.7 pick -26CF ; unqualified # ⛏ E0.7 pick -2692 FE0F ; fully-qualified # ⚒️ E1.0 hammer and pick -2692 ; unqualified # ⚒ E1.0 hammer and pick -1F6E0 FE0F ; fully-qualified # 🛠️ E0.7 hammer and wrench -1F6E0 ; unqualified # 🛠 E0.7 hammer and wrench -1F5E1 FE0F ; fully-qualified # 🗡️ E0.7 dagger -1F5E1 ; unqualified # 🗡 E0.7 dagger -2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords -2694 ; unqualified # ⚔ E1.0 crossed swords -1F4A3 ; fully-qualified # 💣 E0.6 bomb -1FA83 ; fully-qualified # 🪃 E13.0 boomerang -1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow -1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield -1F6E1 ; unqualified # 🛡 E0.7 shield -1FA9A ; fully-qualified # 🪚 E13.0 carpentry saw -1F527 ; fully-qualified # 🔧 E0.6 wrench -1FA9B ; fully-qualified # 🪛 E13.0 screwdriver -1F529 ; fully-qualified # 🔩 E0.6 nut and bolt -2699 FE0F ; fully-qualified # ⚙️ E1.0 gear -2699 ; unqualified # ⚙ E1.0 gear -1F5DC FE0F ; fully-qualified # 🗜️ E0.7 clamp -1F5DC ; unqualified # 🗜 E0.7 clamp -2696 FE0F ; fully-qualified # ⚖️ E1.0 balance scale -2696 ; unqualified # ⚖ E1.0 balance scale -1F9AF ; fully-qualified # 🦯 E12.0 white cane -1F517 ; fully-qualified # 🔗 E0.6 link -26D3 FE0F ; fully-qualified # ⛓️ E0.7 chains -26D3 ; unqualified # ⛓ E0.7 chains -1FA9D ; fully-qualified # 🪝 E13.0 hook -1F9F0 ; fully-qualified # 🧰 E11.0 toolbox -1F9F2 ; fully-qualified # 🧲 E11.0 magnet -1FA9C ; fully-qualified # 🪜 E13.0 ladder - -# subgroup: science -2697 FE0F ; fully-qualified # ⚗️ E1.0 alembic -2697 ; unqualified # ⚗ E1.0 alembic -1F9EA ; fully-qualified # 🧪 E11.0 test tube -1F9EB ; fully-qualified # 🧫 E11.0 petri dish -1F9EC ; fully-qualified # 🧬 E11.0 dna -1F52C ; fully-qualified # 🔬 E1.0 microscope -1F52D ; fully-qualified # 🔭 E1.0 telescope -1F4E1 ; fully-qualified # 📡 E0.6 satellite antenna - -# subgroup: medical -1F489 ; fully-qualified # 💉 E0.6 syringe -1FA78 ; fully-qualified # 🩸 E12.0 drop of blood -1F48A ; fully-qualified # 💊 E0.6 pill -1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage -1FA7C ; fully-qualified # 🩼 E14.0 crutch -1FA7A ; fully-qualified # 🩺 E12.0 stethoscope -1FA7B ; fully-qualified # 🩻 E14.0 x-ray - -# subgroup: household -1F6AA ; fully-qualified # 🚪 E0.6 door -1F6D7 ; fully-qualified # 🛗 E13.0 elevator -1FA9E ; fully-qualified # 🪞 E13.0 mirror -1FA9F ; fully-qualified # 🪟 E13.0 window -1F6CF FE0F ; fully-qualified # 🛏️ E0.7 bed -1F6CF ; unqualified # 🛏 E0.7 bed -1F6CB FE0F ; fully-qualified # 🛋️ E0.7 couch and lamp -1F6CB ; unqualified # 🛋 E0.7 couch and lamp -1FA91 ; fully-qualified # 🪑 E12.0 chair -1F6BD ; fully-qualified # 🚽 E0.6 toilet -1FAA0 ; fully-qualified # 🪠 E13.0 plunger -1F6BF ; fully-qualified # 🚿 E1.0 shower -1F6C1 ; fully-qualified # 🛁 E1.0 bathtub -1FAA4 ; fully-qualified # 🪤 E13.0 mouse trap -1FA92 ; fully-qualified # 🪒 E12.0 razor -1F9F4 ; fully-qualified # 🧴 E11.0 lotion bottle -1F9F7 ; fully-qualified # 🧷 E11.0 safety pin -1F9F9 ; fully-qualified # 🧹 E11.0 broom -1F9FA ; fully-qualified # 🧺 E11.0 basket -1F9FB ; fully-qualified # 🧻 E11.0 roll of paper -1FAA3 ; fully-qualified # 🪣 E13.0 bucket -1F9FC ; fully-qualified # 🧼 E11.0 soap -1FAE7 ; fully-qualified # 🫧 E14.0 bubbles -1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush -1F9FD ; fully-qualified # 🧽 E11.0 sponge -1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher -1F6D2 ; fully-qualified # 🛒 E3.0 shopping cart - -# subgroup: other-object -1F6AC ; fully-qualified # 🚬 E0.6 cigarette -26B0 FE0F ; fully-qualified # ⚰️ E1.0 coffin -26B0 ; unqualified # ⚰ E1.0 coffin -1FAA6 ; fully-qualified # 🪦 E13.0 headstone -26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn -26B1 ; unqualified # ⚱ E1.0 funeral urn -1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet -1FAAC ; fully-qualified # 🪬 E14.0 hamsa -1F5FF ; fully-qualified # 🗿 E0.6 moai -1FAA7 ; fully-qualified # 🪧 E13.0 placard -1FAAA ; fully-qualified # 🪪 E14.0 identification card - -# Objects subtotal: 310 -# Objects subtotal: 310 w/o modifiers - -# group: Symbols - -# subgroup: transport-sign -1F3E7 ; fully-qualified # 🏧 E0.6 ATM sign -1F6AE ; fully-qualified # 🚮 E1.0 litter in bin sign -1F6B0 ; fully-qualified # 🚰 E1.0 potable water -267F ; fully-qualified # ♿ E0.6 wheelchair symbol -1F6B9 ; fully-qualified # 🚹 E0.6 men’s room -1F6BA ; fully-qualified # 🚺 E0.6 women’s room -1F6BB ; fully-qualified # 🚻 E0.6 restroom -1F6BC ; fully-qualified # 🚼 E0.6 baby symbol -1F6BE ; fully-qualified # 🚾 E0.6 water closet -1F6C2 ; fully-qualified # 🛂 E1.0 passport control -1F6C3 ; fully-qualified # 🛃 E1.0 customs -1F6C4 ; fully-qualified # 🛄 E1.0 baggage claim -1F6C5 ; fully-qualified # 🛅 E1.0 left luggage - -# subgroup: warning -26A0 FE0F ; fully-qualified # ⚠️ E0.6 warning -26A0 ; unqualified # ⚠ E0.6 warning -1F6B8 ; fully-qualified # 🚸 E1.0 children crossing -26D4 ; fully-qualified # ⛔ E0.6 no entry -1F6AB ; fully-qualified # 🚫 E0.6 prohibited -1F6B3 ; fully-qualified # 🚳 E1.0 no bicycles -1F6AD ; fully-qualified # 🚭 E0.6 no smoking -1F6AF ; fully-qualified # 🚯 E1.0 no littering -1F6B1 ; fully-qualified # 🚱 E1.0 non-potable water -1F6B7 ; fully-qualified # 🚷 E1.0 no pedestrians -1F4F5 ; fully-qualified # 📵 E1.0 no mobile phones -1F51E ; fully-qualified # 🔞 E0.6 no one under eighteen -2622 FE0F ; fully-qualified # ☢️ E1.0 radioactive -2622 ; unqualified # ☢ E1.0 radioactive -2623 FE0F ; fully-qualified # ☣️ E1.0 biohazard -2623 ; unqualified # ☣ E1.0 biohazard - -# subgroup: arrow -2B06 FE0F ; fully-qualified # ⬆️ E0.6 up arrow -2B06 ; unqualified # ⬆ E0.6 up arrow -2197 FE0F ; fully-qualified # ↗️ E0.6 up-right arrow -2197 ; unqualified # ↗ E0.6 up-right arrow -27A1 FE0F ; fully-qualified # ➡️ E0.6 right arrow -27A1 ; unqualified # ➡ E0.6 right arrow -2198 FE0F ; fully-qualified # ↘️ E0.6 down-right arrow -2198 ; unqualified # ↘ E0.6 down-right arrow -2B07 FE0F ; fully-qualified # ⬇️ E0.6 down arrow -2B07 ; unqualified # ⬇ E0.6 down arrow -2199 FE0F ; fully-qualified # ↙️ E0.6 down-left arrow -2199 ; unqualified # ↙ E0.6 down-left arrow -2B05 FE0F ; fully-qualified # ⬅️ E0.6 left arrow -2B05 ; unqualified # ⬅ E0.6 left arrow -2196 FE0F ; fully-qualified # ↖️ E0.6 up-left arrow -2196 ; unqualified # ↖ E0.6 up-left arrow -2195 FE0F ; fully-qualified # ↕️ E0.6 up-down arrow -2195 ; unqualified # ↕ E0.6 up-down arrow -2194 FE0F ; fully-qualified # ↔️ E0.6 left-right arrow -2194 ; unqualified # ↔ E0.6 left-right arrow -21A9 FE0F ; fully-qualified # ↩️ E0.6 right arrow curving left -21A9 ; unqualified # ↩ E0.6 right arrow curving left -21AA FE0F ; fully-qualified # ↪️ E0.6 left arrow curving right -21AA ; unqualified # ↪ E0.6 left arrow curving right -2934 FE0F ; fully-qualified # ⤴️ E0.6 right arrow curving up -2934 ; unqualified # ⤴ E0.6 right arrow curving up -2935 FE0F ; fully-qualified # ⤵️ E0.6 right arrow curving down -2935 ; unqualified # ⤵ E0.6 right arrow curving down -1F503 ; fully-qualified # 🔃 E0.6 clockwise vertical arrows -1F504 ; fully-qualified # 🔄 E1.0 counterclockwise arrows button -1F519 ; fully-qualified # 🔙 E0.6 BACK arrow -1F51A ; fully-qualified # 🔚 E0.6 END arrow -1F51B ; fully-qualified # 🔛 E0.6 ON! arrow -1F51C ; fully-qualified # 🔜 E0.6 SOON arrow -1F51D ; fully-qualified # 🔝 E0.6 TOP arrow - -# subgroup: religion -1F6D0 ; fully-qualified # 🛐 E1.0 place of worship -269B FE0F ; fully-qualified # ⚛️ E1.0 atom symbol -269B ; unqualified # ⚛ E1.0 atom symbol -1F549 FE0F ; fully-qualified # 🕉️ E0.7 om -1F549 ; unqualified # 🕉 E0.7 om -2721 FE0F ; fully-qualified # ✡️ E0.7 star of David -2721 ; unqualified # ✡ E0.7 star of David -2638 FE0F ; fully-qualified # ☸️ E0.7 wheel of dharma -2638 ; unqualified # ☸ E0.7 wheel of dharma -262F FE0F ; fully-qualified # ☯️ E0.7 yin yang -262F ; unqualified # ☯ E0.7 yin yang -271D FE0F ; fully-qualified # ✝️ E0.7 latin cross -271D ; unqualified # ✝ E0.7 latin cross -2626 FE0F ; fully-qualified # ☦️ E1.0 orthodox cross -2626 ; unqualified # ☦ E1.0 orthodox cross -262A FE0F ; fully-qualified # ☪️ E0.7 star and crescent -262A ; unqualified # ☪ E0.7 star and crescent -262E FE0F ; fully-qualified # ☮️ E1.0 peace symbol -262E ; unqualified # ☮ E1.0 peace symbol -1F54E ; fully-qualified # 🕎 E1.0 menorah -1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star -1FAAF ; fully-qualified # 🪯 E15.0 khanda - -# subgroup: zodiac -2648 ; fully-qualified # ♈ E0.6 Aries -2649 ; fully-qualified # ♉ E0.6 Taurus -264A ; fully-qualified # ♊ E0.6 Gemini -264B ; fully-qualified # ♋ E0.6 Cancer -264C ; fully-qualified # ♌ E0.6 Leo -264D ; fully-qualified # ♍ E0.6 Virgo -264E ; fully-qualified # ♎ E0.6 Libra -264F ; fully-qualified # ♏ E0.6 Scorpio -2650 ; fully-qualified # ♐ E0.6 Sagittarius -2651 ; fully-qualified # ♑ E0.6 Capricorn -2652 ; fully-qualified # ♒ E0.6 Aquarius -2653 ; fully-qualified # ♓ E0.6 Pisces -26CE ; fully-qualified # ⛎ E0.6 Ophiuchus - -# subgroup: av-symbol -1F500 ; fully-qualified # 🔀 E1.0 shuffle tracks button -1F501 ; fully-qualified # 🔁 E1.0 repeat button -1F502 ; fully-qualified # 🔂 E1.0 repeat single button -25B6 FE0F ; fully-qualified # ▶️ E0.6 play button -25B6 ; unqualified # ▶ E0.6 play button -23E9 ; fully-qualified # ⏩ E0.6 fast-forward button -23ED FE0F ; fully-qualified # ⏭️ E0.7 next track button -23ED ; unqualified # ⏭ E0.7 next track button -23EF FE0F ; fully-qualified # ⏯️ E1.0 play or pause button -23EF ; unqualified # ⏯ E1.0 play or pause button -25C0 FE0F ; fully-qualified # ◀️ E0.6 reverse button -25C0 ; unqualified # ◀ E0.6 reverse button -23EA ; fully-qualified # ⏪ E0.6 fast reverse button -23EE FE0F ; fully-qualified # ⏮️ E0.7 last track button -23EE ; unqualified # ⏮ E0.7 last track button -1F53C ; fully-qualified # 🔼 E0.6 upwards button -23EB ; fully-qualified # ⏫ E0.6 fast up button -1F53D ; fully-qualified # 🔽 E0.6 downwards button -23EC ; fully-qualified # ⏬ E0.6 fast down button -23F8 FE0F ; fully-qualified # ⏸️ E0.7 pause button -23F8 ; unqualified # ⏸ E0.7 pause button -23F9 FE0F ; fully-qualified # ⏹️ E0.7 stop button -23F9 ; unqualified # ⏹ E0.7 stop button -23FA FE0F ; fully-qualified # ⏺️ E0.7 record button -23FA ; unqualified # ⏺ E0.7 record button -23CF FE0F ; fully-qualified # ⏏️ E1.0 eject button -23CF ; unqualified # ⏏ E1.0 eject button -1F3A6 ; fully-qualified # 🎦 E0.6 cinema -1F505 ; fully-qualified # 🔅 E1.0 dim button -1F506 ; fully-qualified # 🔆 E1.0 bright button -1F4F6 ; fully-qualified # 📶 E0.6 antenna bars -1F6DC ; fully-qualified # 🛜 E15.0 wireless -1F4F3 ; fully-qualified # 📳 E0.6 vibration mode -1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off - -# subgroup: gender -2640 FE0F ; fully-qualified # ♀️ E4.0 female sign -2640 ; unqualified # ♀ E4.0 female sign -2642 FE0F ; fully-qualified # ♂️ E4.0 male sign -2642 ; unqualified # ♂ E4.0 male sign -26A7 FE0F ; fully-qualified # ⚧️ E13.0 transgender symbol -26A7 ; unqualified # ⚧ E13.0 transgender symbol - -# subgroup: math -2716 FE0F ; fully-qualified # ✖️ E0.6 multiply -2716 ; unqualified # ✖ E0.6 multiply -2795 ; fully-qualified # ➕ E0.6 plus -2796 ; fully-qualified # ➖ E0.6 minus -2797 ; fully-qualified # ➗ E0.6 divide -1F7F0 ; fully-qualified # 🟰 E14.0 heavy equals sign -267E FE0F ; fully-qualified # ♾️ E11.0 infinity -267E ; unqualified # ♾ E11.0 infinity - -# subgroup: punctuation -203C FE0F ; fully-qualified # ‼️ E0.6 double exclamation mark -203C ; unqualified # ‼ E0.6 double exclamation mark -2049 FE0F ; fully-qualified # ⁉️ E0.6 exclamation question mark -2049 ; unqualified # ⁉ E0.6 exclamation question mark -2753 ; fully-qualified # ❓ E0.6 red question mark -2754 ; fully-qualified # ❔ E0.6 white question mark -2755 ; fully-qualified # ❕ E0.6 white exclamation mark -2757 ; fully-qualified # ❗ E0.6 red exclamation mark -3030 FE0F ; fully-qualified # 〰️ E0.6 wavy dash -3030 ; unqualified # 〰 E0.6 wavy dash - -# subgroup: currency -1F4B1 ; fully-qualified # 💱 E0.6 currency exchange -1F4B2 ; fully-qualified # 💲 E0.6 heavy dollar sign - -# subgroup: other-symbol -2695 FE0F ; fully-qualified # ⚕️ E4.0 medical symbol -2695 ; unqualified # ⚕ E4.0 medical symbol -267B FE0F ; fully-qualified # ♻️ E0.6 recycling symbol -267B ; unqualified # ♻ E0.6 recycling symbol -269C FE0F ; fully-qualified # ⚜️ E1.0 fleur-de-lis -269C ; unqualified # ⚜ E1.0 fleur-de-lis -1F531 ; fully-qualified # 🔱 E0.6 trident emblem -1F4DB ; fully-qualified # 📛 E0.6 name badge -1F530 ; fully-qualified # 🔰 E0.6 Japanese symbol for beginner -2B55 ; fully-qualified # ⭕ E0.6 hollow red circle -2705 ; fully-qualified # ✅ E0.6 check mark button -2611 FE0F ; fully-qualified # ☑️ E0.6 check box with check -2611 ; unqualified # ☑ E0.6 check box with check -2714 FE0F ; fully-qualified # ✔️ E0.6 check mark -2714 ; unqualified # ✔ E0.6 check mark -274C ; fully-qualified # ❌ E0.6 cross mark -274E ; fully-qualified # ❎ E0.6 cross mark button -27B0 ; fully-qualified # ➰ E0.6 curly loop -27BF ; fully-qualified # ➿ E1.0 double curly loop -303D FE0F ; fully-qualified # 〽️ E0.6 part alternation mark -303D ; unqualified # 〽 E0.6 part alternation mark -2733 FE0F ; fully-qualified # ✳️ E0.6 eight-spoked asterisk -2733 ; unqualified # ✳ E0.6 eight-spoked asterisk -2734 FE0F ; fully-qualified # ✴️ E0.6 eight-pointed star -2734 ; unqualified # ✴ E0.6 eight-pointed star -2747 FE0F ; fully-qualified # ❇️ E0.6 sparkle -2747 ; unqualified # ❇ E0.6 sparkle -00A9 FE0F ; fully-qualified # ©️ E0.6 copyright -00A9 ; unqualified # © E0.6 copyright -00AE FE0F ; fully-qualified # ®️ E0.6 registered -00AE ; unqualified # ® E0.6 registered -2122 FE0F ; fully-qualified # ™️ E0.6 trade mark -2122 ; unqualified # ™ E0.6 trade mark - -# subgroup: keycap -0023 FE0F 20E3 ; fully-qualified # #️⃣ E0.6 keycap: # -0023 20E3 ; unqualified # #⃣ E0.6 keycap: # -002A FE0F 20E3 ; fully-qualified # *️⃣ E2.0 keycap: * -002A 20E3 ; unqualified # *⃣ E2.0 keycap: * -0030 FE0F 20E3 ; fully-qualified # 0️⃣ E0.6 keycap: 0 -0030 20E3 ; unqualified # 0⃣ E0.6 keycap: 0 -0031 FE0F 20E3 ; fully-qualified # 1️⃣ E0.6 keycap: 1 -0031 20E3 ; unqualified # 1⃣ E0.6 keycap: 1 -0032 FE0F 20E3 ; fully-qualified # 2️⃣ E0.6 keycap: 2 -0032 20E3 ; unqualified # 2⃣ E0.6 keycap: 2 -0033 FE0F 20E3 ; fully-qualified # 3️⃣ E0.6 keycap: 3 -0033 20E3 ; unqualified # 3⃣ E0.6 keycap: 3 -0034 FE0F 20E3 ; fully-qualified # 4️⃣ E0.6 keycap: 4 -0034 20E3 ; unqualified # 4⃣ E0.6 keycap: 4 -0035 FE0F 20E3 ; fully-qualified # 5️⃣ E0.6 keycap: 5 -0035 20E3 ; unqualified # 5⃣ E0.6 keycap: 5 -0036 FE0F 20E3 ; fully-qualified # 6️⃣ E0.6 keycap: 6 -0036 20E3 ; unqualified # 6⃣ E0.6 keycap: 6 -0037 FE0F 20E3 ; fully-qualified # 7️⃣ E0.6 keycap: 7 -0037 20E3 ; unqualified # 7⃣ E0.6 keycap: 7 -0038 FE0F 20E3 ; fully-qualified # 8️⃣ E0.6 keycap: 8 -0038 20E3 ; unqualified # 8⃣ E0.6 keycap: 8 -0039 FE0F 20E3 ; fully-qualified # 9️⃣ E0.6 keycap: 9 -0039 20E3 ; unqualified # 9⃣ E0.6 keycap: 9 -1F51F ; fully-qualified # 🔟 E0.6 keycap: 10 - -# subgroup: alphanum -1F520 ; fully-qualified # 🔠 E0.6 input latin uppercase -1F521 ; fully-qualified # 🔡 E0.6 input latin lowercase -1F522 ; fully-qualified # 🔢 E0.6 input numbers -1F523 ; fully-qualified # 🔣 E0.6 input symbols -1F524 ; fully-qualified # 🔤 E0.6 input latin letters -1F170 FE0F ; fully-qualified # 🅰️ E0.6 A button (blood type) -1F170 ; unqualified # 🅰 E0.6 A button (blood type) -1F18E ; fully-qualified # 🆎 E0.6 AB button (blood type) -1F171 FE0F ; fully-qualified # 🅱️ E0.6 B button (blood type) -1F171 ; unqualified # 🅱 E0.6 B button (blood type) -1F191 ; fully-qualified # 🆑 E0.6 CL button -1F192 ; fully-qualified # 🆒 E0.6 COOL button -1F193 ; fully-qualified # 🆓 E0.6 FREE button -2139 FE0F ; fully-qualified # ℹ️ E0.6 information -2139 ; unqualified # ℹ E0.6 information -1F194 ; fully-qualified # 🆔 E0.6 ID button -24C2 FE0F ; fully-qualified # Ⓜ️ E0.6 circled M -24C2 ; unqualified # Ⓜ E0.6 circled M -1F195 ; fully-qualified # 🆕 E0.6 NEW button -1F196 ; fully-qualified # 🆖 E0.6 NG button -1F17E FE0F ; fully-qualified # 🅾️ E0.6 O button (blood type) -1F17E ; unqualified # 🅾 E0.6 O button (blood type) -1F197 ; fully-qualified # 🆗 E0.6 OK button -1F17F FE0F ; fully-qualified # 🅿️ E0.6 P button -1F17F ; unqualified # 🅿 E0.6 P button -1F198 ; fully-qualified # 🆘 E0.6 SOS button -1F199 ; fully-qualified # 🆙 E0.6 UP! button -1F19A ; fully-qualified # 🆚 E0.6 VS button -1F201 ; fully-qualified # 🈁 E0.6 Japanese “here” button -1F202 FE0F ; fully-qualified # 🈂️ E0.6 Japanese “service charge” button -1F202 ; unqualified # 🈂 E0.6 Japanese “service charge” button -1F237 FE0F ; fully-qualified # 🈷️ E0.6 Japanese “monthly amount” button -1F237 ; unqualified # 🈷 E0.6 Japanese “monthly amount” button -1F236 ; fully-qualified # 🈶 E0.6 Japanese “not free of charge” button -1F22F ; fully-qualified # 🈯 E0.6 Japanese “reserved” button -1F250 ; fully-qualified # 🉐 E0.6 Japanese “bargain” button -1F239 ; fully-qualified # 🈹 E0.6 Japanese “discount” button -1F21A ; fully-qualified # 🈚 E0.6 Japanese “free of charge” button -1F232 ; fully-qualified # 🈲 E0.6 Japanese “prohibited” button -1F251 ; fully-qualified # 🉑 E0.6 Japanese “acceptable” button -1F238 ; fully-qualified # 🈸 E0.6 Japanese “application” button -1F234 ; fully-qualified # 🈴 E0.6 Japanese “passing grade” button -1F233 ; fully-qualified # 🈳 E0.6 Japanese “vacancy” button -3297 FE0F ; fully-qualified # ㊗️ E0.6 Japanese “congratulations” button -3297 ; unqualified # ㊗ E0.6 Japanese “congratulations” button -3299 FE0F ; fully-qualified # ㊙️ E0.6 Japanese “secret” button -3299 ; unqualified # ㊙ E0.6 Japanese “secret” button -1F23A ; fully-qualified # 🈺 E0.6 Japanese “open for business” button -1F235 ; fully-qualified # 🈵 E0.6 Japanese “no vacancy” button - -# subgroup: geometric -1F534 ; fully-qualified # 🔴 E0.6 red circle -1F7E0 ; fully-qualified # 🟠 E12.0 orange circle -1F7E1 ; fully-qualified # 🟡 E12.0 yellow circle -1F7E2 ; fully-qualified # 🟢 E12.0 green circle -1F535 ; fully-qualified # 🔵 E0.6 blue circle -1F7E3 ; fully-qualified # 🟣 E12.0 purple circle -1F7E4 ; fully-qualified # 🟤 E12.0 brown circle -26AB ; fully-qualified # ⚫ E0.6 black circle -26AA ; fully-qualified # ⚪ E0.6 white circle -1F7E5 ; fully-qualified # 🟥 E12.0 red square -1F7E7 ; fully-qualified # 🟧 E12.0 orange square -1F7E8 ; fully-qualified # 🟨 E12.0 yellow square -1F7E9 ; fully-qualified # 🟩 E12.0 green square -1F7E6 ; fully-qualified # 🟦 E12.0 blue square -1F7EA ; fully-qualified # 🟪 E12.0 purple square -1F7EB ; fully-qualified # 🟫 E12.0 brown square -2B1B ; fully-qualified # ⬛ E0.6 black large square -2B1C ; fully-qualified # ⬜ E0.6 white large square -25FC FE0F ; fully-qualified # ◼️ E0.6 black medium square -25FC ; unqualified # ◼ E0.6 black medium square -25FB FE0F ; fully-qualified # ◻️ E0.6 white medium square -25FB ; unqualified # ◻ E0.6 white medium square -25FE ; fully-qualified # ◾ E0.6 black medium-small square -25FD ; fully-qualified # ◽ E0.6 white medium-small square -25AA FE0F ; fully-qualified # ▪️ E0.6 black small square -25AA ; unqualified # ▪ E0.6 black small square -25AB FE0F ; fully-qualified # ▫️ E0.6 white small square -25AB ; unqualified # ▫ E0.6 white small square -1F536 ; fully-qualified # 🔶 E0.6 large orange diamond -1F537 ; fully-qualified # 🔷 E0.6 large blue diamond -1F538 ; fully-qualified # 🔸 E0.6 small orange diamond -1F539 ; fully-qualified # 🔹 E0.6 small blue diamond -1F53A ; fully-qualified # 🔺 E0.6 red triangle pointed up -1F53B ; fully-qualified # 🔻 E0.6 red triangle pointed down -1F4A0 ; fully-qualified # 💠 E0.6 diamond with a dot -1F518 ; fully-qualified # 🔘 E0.6 radio button -1F533 ; fully-qualified # 🔳 E0.6 white square button -1F532 ; fully-qualified # 🔲 E0.6 black square button - -# Symbols subtotal: 304 -# Symbols subtotal: 304 w/o modifiers - -# group: Flags - -# subgroup: flag -1F3C1 ; fully-qualified # 🏁 E0.6 chequered flag -1F6A9 ; fully-qualified # 🚩 E0.6 triangular flag -1F38C ; fully-qualified # 🎌 E0.6 crossed flags -1F3F4 ; fully-qualified # 🏴 E1.0 black flag -1F3F3 FE0F ; fully-qualified # 🏳️ E0.7 white flag -1F3F3 ; unqualified # 🏳 E0.7 white flag -1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 E4.0 rainbow flag -1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 E4.0 rainbow flag -1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️‍⚧️ E13.0 transgender flag -1F3F3 200D 26A7 FE0F ; unqualified # 🏳‍⚧️ E13.0 transgender flag -1F3F3 FE0F 200D 26A7 ; minimally-qualified # 🏳️‍⚧ E13.0 transgender flag -1F3F3 200D 26A7 ; unqualified # 🏳‍⚧ E13.0 transgender flag -1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ E11.0 pirate flag -1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ E11.0 pirate flag - -# subgroup: country-flag -1F1E6 1F1E8 ; fully-qualified # 🇦🇨 E2.0 flag: Ascension Island -1F1E6 1F1E9 ; fully-qualified # 🇦🇩 E2.0 flag: Andorra -1F1E6 1F1EA ; fully-qualified # 🇦🇪 E2.0 flag: United Arab Emirates -1F1E6 1F1EB ; fully-qualified # 🇦🇫 E2.0 flag: Afghanistan -1F1E6 1F1EC ; fully-qualified # 🇦🇬 E2.0 flag: Antigua & Barbuda -1F1E6 1F1EE ; fully-qualified # 🇦🇮 E2.0 flag: Anguilla -1F1E6 1F1F1 ; fully-qualified # 🇦🇱 E2.0 flag: Albania -1F1E6 1F1F2 ; fully-qualified # 🇦🇲 E2.0 flag: Armenia -1F1E6 1F1F4 ; fully-qualified # 🇦🇴 E2.0 flag: Angola -1F1E6 1F1F6 ; fully-qualified # 🇦🇶 E2.0 flag: Antarctica -1F1E6 1F1F7 ; fully-qualified # 🇦🇷 E2.0 flag: Argentina -1F1E6 1F1F8 ; fully-qualified # 🇦🇸 E2.0 flag: American Samoa -1F1E6 1F1F9 ; fully-qualified # 🇦🇹 E2.0 flag: Austria -1F1E6 1F1FA ; fully-qualified # 🇦🇺 E2.0 flag: Australia -1F1E6 1F1FC ; fully-qualified # 🇦🇼 E2.0 flag: Aruba -1F1E6 1F1FD ; fully-qualified # 🇦🇽 E2.0 flag: Åland Islands -1F1E6 1F1FF ; fully-qualified # 🇦🇿 E2.0 flag: Azerbaijan -1F1E7 1F1E6 ; fully-qualified # 🇧🇦 E2.0 flag: Bosnia & Herzegovina -1F1E7 1F1E7 ; fully-qualified # 🇧🇧 E2.0 flag: Barbados -1F1E7 1F1E9 ; fully-qualified # 🇧🇩 E2.0 flag: Bangladesh -1F1E7 1F1EA ; fully-qualified # 🇧🇪 E2.0 flag: Belgium -1F1E7 1F1EB ; fully-qualified # 🇧🇫 E2.0 flag: Burkina Faso -1F1E7 1F1EC ; fully-qualified # 🇧🇬 E2.0 flag: Bulgaria -1F1E7 1F1ED ; fully-qualified # 🇧🇭 E2.0 flag: Bahrain -1F1E7 1F1EE ; fully-qualified # 🇧🇮 E2.0 flag: Burundi -1F1E7 1F1EF ; fully-qualified # 🇧🇯 E2.0 flag: Benin -1F1E7 1F1F1 ; fully-qualified # 🇧🇱 E2.0 flag: St. Barthélemy -1F1E7 1F1F2 ; fully-qualified # 🇧🇲 E2.0 flag: Bermuda -1F1E7 1F1F3 ; fully-qualified # 🇧🇳 E2.0 flag: Brunei -1F1E7 1F1F4 ; fully-qualified # 🇧🇴 E2.0 flag: Bolivia -1F1E7 1F1F6 ; fully-qualified # 🇧🇶 E2.0 flag: Caribbean Netherlands -1F1E7 1F1F7 ; fully-qualified # 🇧🇷 E2.0 flag: Brazil -1F1E7 1F1F8 ; fully-qualified # 🇧🇸 E2.0 flag: Bahamas -1F1E7 1F1F9 ; fully-qualified # 🇧🇹 E2.0 flag: Bhutan -1F1E7 1F1FB ; fully-qualified # 🇧🇻 E2.0 flag: Bouvet Island -1F1E7 1F1FC ; fully-qualified # 🇧🇼 E2.0 flag: Botswana -1F1E7 1F1FE ; fully-qualified # 🇧🇾 E2.0 flag: Belarus -1F1E7 1F1FF ; fully-qualified # 🇧🇿 E2.0 flag: Belize -1F1E8 1F1E6 ; fully-qualified # 🇨🇦 E2.0 flag: Canada -1F1E8 1F1E8 ; fully-qualified # 🇨🇨 E2.0 flag: Cocos (Keeling) Islands -1F1E8 1F1E9 ; fully-qualified # 🇨🇩 E2.0 flag: Congo - Kinshasa -1F1E8 1F1EB ; fully-qualified # 🇨🇫 E2.0 flag: Central African Republic -1F1E8 1F1EC ; fully-qualified # 🇨🇬 E2.0 flag: Congo - Brazzaville -1F1E8 1F1ED ; fully-qualified # 🇨🇭 E2.0 flag: Switzerland -1F1E8 1F1EE ; fully-qualified # 🇨🇮 E2.0 flag: Côte d’Ivoire -1F1E8 1F1F0 ; fully-qualified # 🇨🇰 E2.0 flag: Cook Islands -1F1E8 1F1F1 ; fully-qualified # 🇨🇱 E2.0 flag: Chile -1F1E8 1F1F2 ; fully-qualified # 🇨🇲 E2.0 flag: Cameroon -1F1E8 1F1F3 ; fully-qualified # 🇨🇳 E0.6 flag: China -1F1E8 1F1F4 ; fully-qualified # 🇨🇴 E2.0 flag: Colombia -1F1E8 1F1F5 ; fully-qualified # 🇨🇵 E2.0 flag: Clipperton Island -1F1E8 1F1F7 ; fully-qualified # 🇨🇷 E2.0 flag: Costa Rica -1F1E8 1F1FA ; fully-qualified # 🇨🇺 E2.0 flag: Cuba -1F1E8 1F1FB ; fully-qualified # 🇨🇻 E2.0 flag: Cape Verde -1F1E8 1F1FC ; fully-qualified # 🇨🇼 E2.0 flag: Curaçao -1F1E8 1F1FD ; fully-qualified # 🇨🇽 E2.0 flag: Christmas Island -1F1E8 1F1FE ; fully-qualified # 🇨🇾 E2.0 flag: Cyprus -1F1E8 1F1FF ; fully-qualified # 🇨🇿 E2.0 flag: Czechia -1F1E9 1F1EA ; fully-qualified # 🇩🇪 E0.6 flag: Germany -1F1E9 1F1EC ; fully-qualified # 🇩🇬 E2.0 flag: Diego Garcia -1F1E9 1F1EF ; fully-qualified # 🇩🇯 E2.0 flag: Djibouti -1F1E9 1F1F0 ; fully-qualified # 🇩🇰 E2.0 flag: Denmark -1F1E9 1F1F2 ; fully-qualified # 🇩🇲 E2.0 flag: Dominica -1F1E9 1F1F4 ; fully-qualified # 🇩🇴 E2.0 flag: Dominican Republic -1F1E9 1F1FF ; fully-qualified # 🇩🇿 E2.0 flag: Algeria -1F1EA 1F1E6 ; fully-qualified # 🇪🇦 E2.0 flag: Ceuta & Melilla -1F1EA 1F1E8 ; fully-qualified # 🇪🇨 E2.0 flag: Ecuador -1F1EA 1F1EA ; fully-qualified # 🇪🇪 E2.0 flag: Estonia -1F1EA 1F1EC ; fully-qualified # 🇪🇬 E2.0 flag: Egypt -1F1EA 1F1ED ; fully-qualified # 🇪🇭 E2.0 flag: Western Sahara -1F1EA 1F1F7 ; fully-qualified # 🇪🇷 E2.0 flag: Eritrea -1F1EA 1F1F8 ; fully-qualified # 🇪🇸 E0.6 flag: Spain -1F1EA 1F1F9 ; fully-qualified # 🇪🇹 E2.0 flag: Ethiopia -1F1EA 1F1FA ; fully-qualified # 🇪🇺 E2.0 flag: European Union -1F1EB 1F1EE ; fully-qualified # 🇫🇮 E2.0 flag: Finland -1F1EB 1F1EF ; fully-qualified # 🇫🇯 E2.0 flag: Fiji -1F1EB 1F1F0 ; fully-qualified # 🇫🇰 E2.0 flag: Falkland Islands -1F1EB 1F1F2 ; fully-qualified # 🇫🇲 E2.0 flag: Micronesia -1F1EB 1F1F4 ; fully-qualified # 🇫🇴 E2.0 flag: Faroe Islands -1F1EB 1F1F7 ; fully-qualified # 🇫🇷 E0.6 flag: France -1F1EC 1F1E6 ; fully-qualified # 🇬🇦 E2.0 flag: Gabon -1F1EC 1F1E7 ; fully-qualified # 🇬🇧 E0.6 flag: United Kingdom -1F1EC 1F1E9 ; fully-qualified # 🇬🇩 E2.0 flag: Grenada -1F1EC 1F1EA ; fully-qualified # 🇬🇪 E2.0 flag: Georgia -1F1EC 1F1EB ; fully-qualified # 🇬🇫 E2.0 flag: French Guiana -1F1EC 1F1EC ; fully-qualified # 🇬🇬 E2.0 flag: Guernsey -1F1EC 1F1ED ; fully-qualified # 🇬🇭 E2.0 flag: Ghana -1F1EC 1F1EE ; fully-qualified # 🇬🇮 E2.0 flag: Gibraltar -1F1EC 1F1F1 ; fully-qualified # 🇬🇱 E2.0 flag: Greenland -1F1EC 1F1F2 ; fully-qualified # 🇬🇲 E2.0 flag: Gambia -1F1EC 1F1F3 ; fully-qualified # 🇬🇳 E2.0 flag: Guinea -1F1EC 1F1F5 ; fully-qualified # 🇬🇵 E2.0 flag: Guadeloupe -1F1EC 1F1F6 ; fully-qualified # 🇬🇶 E2.0 flag: Equatorial Guinea -1F1EC 1F1F7 ; fully-qualified # 🇬🇷 E2.0 flag: Greece -1F1EC 1F1F8 ; fully-qualified # 🇬🇸 E2.0 flag: South Georgia & South Sandwich Islands -1F1EC 1F1F9 ; fully-qualified # 🇬🇹 E2.0 flag: Guatemala -1F1EC 1F1FA ; fully-qualified # 🇬🇺 E2.0 flag: Guam -1F1EC 1F1FC ; fully-qualified # 🇬🇼 E2.0 flag: Guinea-Bissau -1F1EC 1F1FE ; fully-qualified # 🇬🇾 E2.0 flag: Guyana -1F1ED 1F1F0 ; fully-qualified # 🇭🇰 E2.0 flag: Hong Kong SAR China -1F1ED 1F1F2 ; fully-qualified # 🇭🇲 E2.0 flag: Heard & McDonald Islands -1F1ED 1F1F3 ; fully-qualified # 🇭🇳 E2.0 flag: Honduras -1F1ED 1F1F7 ; fully-qualified # 🇭🇷 E2.0 flag: Croatia -1F1ED 1F1F9 ; fully-qualified # 🇭🇹 E2.0 flag: Haiti -1F1ED 1F1FA ; fully-qualified # 🇭🇺 E2.0 flag: Hungary -1F1EE 1F1E8 ; fully-qualified # 🇮🇨 E2.0 flag: Canary Islands -1F1EE 1F1E9 ; fully-qualified # 🇮🇩 E2.0 flag: Indonesia -1F1EE 1F1EA ; fully-qualified # 🇮🇪 E2.0 flag: Ireland -1F1EE 1F1F1 ; fully-qualified # 🇮🇱 E2.0 flag: Israel -1F1EE 1F1F2 ; fully-qualified # 🇮🇲 E2.0 flag: Isle of Man -1F1EE 1F1F3 ; fully-qualified # 🇮🇳 E2.0 flag: India -1F1EE 1F1F4 ; fully-qualified # 🇮🇴 E2.0 flag: British Indian Ocean Territory -1F1EE 1F1F6 ; fully-qualified # 🇮🇶 E2.0 flag: Iraq -1F1EE 1F1F7 ; fully-qualified # 🇮🇷 E2.0 flag: Iran -1F1EE 1F1F8 ; fully-qualified # 🇮🇸 E2.0 flag: Iceland -1F1EE 1F1F9 ; fully-qualified # 🇮🇹 E0.6 flag: Italy -1F1EF 1F1EA ; fully-qualified # 🇯🇪 E2.0 flag: Jersey -1F1EF 1F1F2 ; fully-qualified # 🇯🇲 E2.0 flag: Jamaica -1F1EF 1F1F4 ; fully-qualified # 🇯🇴 E2.0 flag: Jordan -1F1EF 1F1F5 ; fully-qualified # 🇯🇵 E0.6 flag: Japan -1F1F0 1F1EA ; fully-qualified # 🇰🇪 E2.0 flag: Kenya -1F1F0 1F1EC ; fully-qualified # 🇰🇬 E2.0 flag: Kyrgyzstan -1F1F0 1F1ED ; fully-qualified # 🇰🇭 E2.0 flag: Cambodia -1F1F0 1F1EE ; fully-qualified # 🇰🇮 E2.0 flag: Kiribati -1F1F0 1F1F2 ; fully-qualified # 🇰🇲 E2.0 flag: Comoros -1F1F0 1F1F3 ; fully-qualified # 🇰🇳 E2.0 flag: St. Kitts & Nevis -1F1F0 1F1F5 ; fully-qualified # 🇰🇵 E2.0 flag: North Korea -1F1F0 1F1F7 ; fully-qualified # 🇰🇷 E0.6 flag: South Korea -1F1F0 1F1FC ; fully-qualified # 🇰🇼 E2.0 flag: Kuwait -1F1F0 1F1FE ; fully-qualified # 🇰🇾 E2.0 flag: Cayman Islands -1F1F0 1F1FF ; fully-qualified # 🇰🇿 E2.0 flag: Kazakhstan -1F1F1 1F1E6 ; fully-qualified # 🇱🇦 E2.0 flag: Laos -1F1F1 1F1E7 ; fully-qualified # 🇱🇧 E2.0 flag: Lebanon -1F1F1 1F1E8 ; fully-qualified # 🇱🇨 E2.0 flag: St. Lucia -1F1F1 1F1EE ; fully-qualified # 🇱🇮 E2.0 flag: Liechtenstein -1F1F1 1F1F0 ; fully-qualified # 🇱🇰 E2.0 flag: Sri Lanka -1F1F1 1F1F7 ; fully-qualified # 🇱🇷 E2.0 flag: Liberia -1F1F1 1F1F8 ; fully-qualified # 🇱🇸 E2.0 flag: Lesotho -1F1F1 1F1F9 ; fully-qualified # 🇱🇹 E2.0 flag: Lithuania -1F1F1 1F1FA ; fully-qualified # 🇱🇺 E2.0 flag: Luxembourg -1F1F1 1F1FB ; fully-qualified # 🇱🇻 E2.0 flag: Latvia -1F1F1 1F1FE ; fully-qualified # 🇱🇾 E2.0 flag: Libya -1F1F2 1F1E6 ; fully-qualified # 🇲🇦 E2.0 flag: Morocco -1F1F2 1F1E8 ; fully-qualified # 🇲🇨 E2.0 flag: Monaco -1F1F2 1F1E9 ; fully-qualified # 🇲🇩 E2.0 flag: Moldova -1F1F2 1F1EA ; fully-qualified # 🇲🇪 E2.0 flag: Montenegro -1F1F2 1F1EB ; fully-qualified # 🇲🇫 E2.0 flag: St. Martin -1F1F2 1F1EC ; fully-qualified # 🇲🇬 E2.0 flag: Madagascar -1F1F2 1F1ED ; fully-qualified # 🇲🇭 E2.0 flag: Marshall Islands -1F1F2 1F1F0 ; fully-qualified # 🇲🇰 E2.0 flag: North Macedonia -1F1F2 1F1F1 ; fully-qualified # 🇲🇱 E2.0 flag: Mali -1F1F2 1F1F2 ; fully-qualified # 🇲🇲 E2.0 flag: Myanmar (Burma) -1F1F2 1F1F3 ; fully-qualified # 🇲🇳 E2.0 flag: Mongolia -1F1F2 1F1F4 ; fully-qualified # 🇲🇴 E2.0 flag: Macao SAR China -1F1F2 1F1F5 ; fully-qualified # 🇲🇵 E2.0 flag: Northern Mariana Islands -1F1F2 1F1F6 ; fully-qualified # 🇲🇶 E2.0 flag: Martinique -1F1F2 1F1F7 ; fully-qualified # 🇲🇷 E2.0 flag: Mauritania -1F1F2 1F1F8 ; fully-qualified # 🇲🇸 E2.0 flag: Montserrat -1F1F2 1F1F9 ; fully-qualified # 🇲🇹 E2.0 flag: Malta -1F1F2 1F1FA ; fully-qualified # 🇲🇺 E2.0 flag: Mauritius -1F1F2 1F1FB ; fully-qualified # 🇲🇻 E2.0 flag: Maldives -1F1F2 1F1FC ; fully-qualified # 🇲🇼 E2.0 flag: Malawi -1F1F2 1F1FD ; fully-qualified # 🇲🇽 E2.0 flag: Mexico -1F1F2 1F1FE ; fully-qualified # 🇲🇾 E2.0 flag: Malaysia -1F1F2 1F1FF ; fully-qualified # 🇲🇿 E2.0 flag: Mozambique -1F1F3 1F1E6 ; fully-qualified # 🇳🇦 E2.0 flag: Namibia -1F1F3 1F1E8 ; fully-qualified # 🇳🇨 E2.0 flag: New Caledonia -1F1F3 1F1EA ; fully-qualified # 🇳🇪 E2.0 flag: Niger -1F1F3 1F1EB ; fully-qualified # 🇳🇫 E2.0 flag: Norfolk Island -1F1F3 1F1EC ; fully-qualified # 🇳🇬 E2.0 flag: Nigeria -1F1F3 1F1EE ; fully-qualified # 🇳🇮 E2.0 flag: Nicaragua -1F1F3 1F1F1 ; fully-qualified # 🇳🇱 E2.0 flag: Netherlands -1F1F3 1F1F4 ; fully-qualified # 🇳🇴 E2.0 flag: Norway -1F1F3 1F1F5 ; fully-qualified # 🇳🇵 E2.0 flag: Nepal -1F1F3 1F1F7 ; fully-qualified # 🇳🇷 E2.0 flag: Nauru -1F1F3 1F1FA ; fully-qualified # 🇳🇺 E2.0 flag: Niue -1F1F3 1F1FF ; fully-qualified # 🇳🇿 E2.0 flag: New Zealand -1F1F4 1F1F2 ; fully-qualified # 🇴🇲 E2.0 flag: Oman -1F1F5 1F1E6 ; fully-qualified # 🇵🇦 E2.0 flag: Panama -1F1F5 1F1EA ; fully-qualified # 🇵🇪 E2.0 flag: Peru -1F1F5 1F1EB ; fully-qualified # 🇵🇫 E2.0 flag: French Polynesia -1F1F5 1F1EC ; fully-qualified # 🇵🇬 E2.0 flag: Papua New Guinea -1F1F5 1F1ED ; fully-qualified # 🇵🇭 E2.0 flag: Philippines -1F1F5 1F1F0 ; fully-qualified # 🇵🇰 E2.0 flag: Pakistan -1F1F5 1F1F1 ; fully-qualified # 🇵🇱 E2.0 flag: Poland -1F1F5 1F1F2 ; fully-qualified # 🇵🇲 E2.0 flag: St. Pierre & Miquelon -1F1F5 1F1F3 ; fully-qualified # 🇵🇳 E2.0 flag: Pitcairn Islands -1F1F5 1F1F7 ; fully-qualified # 🇵🇷 E2.0 flag: Puerto Rico -1F1F5 1F1F8 ; fully-qualified # 🇵🇸 E2.0 flag: Palestinian Territories -1F1F5 1F1F9 ; fully-qualified # 🇵🇹 E2.0 flag: Portugal -1F1F5 1F1FC ; fully-qualified # 🇵🇼 E2.0 flag: Palau -1F1F5 1F1FE ; fully-qualified # 🇵🇾 E2.0 flag: Paraguay -1F1F6 1F1E6 ; fully-qualified # 🇶🇦 E2.0 flag: Qatar -1F1F7 1F1EA ; fully-qualified # 🇷🇪 E2.0 flag: Réunion -1F1F7 1F1F4 ; fully-qualified # 🇷🇴 E2.0 flag: Romania -1F1F7 1F1F8 ; fully-qualified # 🇷🇸 E2.0 flag: Serbia -1F1F7 1F1FA ; fully-qualified # 🇷🇺 E0.6 flag: Russia -1F1F7 1F1FC ; fully-qualified # 🇷🇼 E2.0 flag: Rwanda -1F1F8 1F1E6 ; fully-qualified # 🇸🇦 E2.0 flag: Saudi Arabia -1F1F8 1F1E7 ; fully-qualified # 🇸🇧 E2.0 flag: Solomon Islands -1F1F8 1F1E8 ; fully-qualified # 🇸🇨 E2.0 flag: Seychelles -1F1F8 1F1E9 ; fully-qualified # 🇸🇩 E2.0 flag: Sudan -1F1F8 1F1EA ; fully-qualified # 🇸🇪 E2.0 flag: Sweden -1F1F8 1F1EC ; fully-qualified # 🇸🇬 E2.0 flag: Singapore -1F1F8 1F1ED ; fully-qualified # 🇸🇭 E2.0 flag: St. Helena -1F1F8 1F1EE ; fully-qualified # 🇸🇮 E2.0 flag: Slovenia -1F1F8 1F1EF ; fully-qualified # 🇸🇯 E2.0 flag: Svalbard & Jan Mayen -1F1F8 1F1F0 ; fully-qualified # 🇸🇰 E2.0 flag: Slovakia -1F1F8 1F1F1 ; fully-qualified # 🇸🇱 E2.0 flag: Sierra Leone -1F1F8 1F1F2 ; fully-qualified # 🇸🇲 E2.0 flag: San Marino -1F1F8 1F1F3 ; fully-qualified # 🇸🇳 E2.0 flag: Senegal -1F1F8 1F1F4 ; fully-qualified # 🇸🇴 E2.0 flag: Somalia -1F1F8 1F1F7 ; fully-qualified # 🇸🇷 E2.0 flag: Suriname -1F1F8 1F1F8 ; fully-qualified # 🇸🇸 E2.0 flag: South Sudan -1F1F8 1F1F9 ; fully-qualified # 🇸🇹 E2.0 flag: São Tomé & Príncipe -1F1F8 1F1FB ; fully-qualified # 🇸🇻 E2.0 flag: El Salvador -1F1F8 1F1FD ; fully-qualified # 🇸🇽 E2.0 flag: Sint Maarten -1F1F8 1F1FE ; fully-qualified # 🇸🇾 E2.0 flag: Syria -1F1F8 1F1FF ; fully-qualified # 🇸🇿 E2.0 flag: Eswatini -1F1F9 1F1E6 ; fully-qualified # 🇹🇦 E2.0 flag: Tristan da Cunha -1F1F9 1F1E8 ; fully-qualified # 🇹🇨 E2.0 flag: Turks & Caicos Islands -1F1F9 1F1E9 ; fully-qualified # 🇹🇩 E2.0 flag: Chad -1F1F9 1F1EB ; fully-qualified # 🇹🇫 E2.0 flag: French Southern Territories -1F1F9 1F1EC ; fully-qualified # 🇹🇬 E2.0 flag: Togo -1F1F9 1F1ED ; fully-qualified # 🇹🇭 E2.0 flag: Thailand -1F1F9 1F1EF ; fully-qualified # 🇹🇯 E2.0 flag: Tajikistan -1F1F9 1F1F0 ; fully-qualified # 🇹🇰 E2.0 flag: Tokelau -1F1F9 1F1F1 ; fully-qualified # 🇹🇱 E2.0 flag: Timor-Leste -1F1F9 1F1F2 ; fully-qualified # 🇹🇲 E2.0 flag: Turkmenistan -1F1F9 1F1F3 ; fully-qualified # 🇹🇳 E2.0 flag: Tunisia -1F1F9 1F1F4 ; fully-qualified # 🇹🇴 E2.0 flag: Tonga -1F1F9 1F1F7 ; fully-qualified # 🇹🇷 E2.0 flag: Turkey -1F1F9 1F1F9 ; fully-qualified # 🇹🇹 E2.0 flag: Trinidad & Tobago -1F1F9 1F1FB ; fully-qualified # 🇹🇻 E2.0 flag: Tuvalu -1F1F9 1F1FC ; fully-qualified # 🇹🇼 E2.0 flag: Taiwan -1F1F9 1F1FF ; fully-qualified # 🇹🇿 E2.0 flag: Tanzania -1F1FA 1F1E6 ; fully-qualified # 🇺🇦 E2.0 flag: Ukraine -1F1FA 1F1EC ; fully-qualified # 🇺🇬 E2.0 flag: Uganda -1F1FA 1F1F2 ; fully-qualified # 🇺🇲 E2.0 flag: U.S. Outlying Islands -1F1FA 1F1F3 ; fully-qualified # 🇺🇳 E4.0 flag: United Nations -1F1FA 1F1F8 ; fully-qualified # 🇺🇸 E0.6 flag: United States -1F1FA 1F1FE ; fully-qualified # 🇺🇾 E2.0 flag: Uruguay -1F1FA 1F1FF ; fully-qualified # 🇺🇿 E2.0 flag: Uzbekistan -1F1FB 1F1E6 ; fully-qualified # 🇻🇦 E2.0 flag: Vatican City -1F1FB 1F1E8 ; fully-qualified # 🇻🇨 E2.0 flag: St. Vincent & Grenadines -1F1FB 1F1EA ; fully-qualified # 🇻🇪 E2.0 flag: Venezuela -1F1FB 1F1EC ; fully-qualified # 🇻🇬 E2.0 flag: British Virgin Islands -1F1FB 1F1EE ; fully-qualified # 🇻🇮 E2.0 flag: U.S. Virgin Islands -1F1FB 1F1F3 ; fully-qualified # 🇻🇳 E2.0 flag: Vietnam -1F1FB 1F1FA ; fully-qualified # 🇻🇺 E2.0 flag: Vanuatu -1F1FC 1F1EB ; fully-qualified # 🇼🇫 E2.0 flag: Wallis & Futuna -1F1FC 1F1F8 ; fully-qualified # 🇼🇸 E2.0 flag: Samoa -1F1FD 1F1F0 ; fully-qualified # 🇽🇰 E2.0 flag: Kosovo -1F1FE 1F1EA ; fully-qualified # 🇾🇪 E2.0 flag: Yemen -1F1FE 1F1F9 ; fully-qualified # 🇾🇹 E2.0 flag: Mayotte -1F1FF 1F1E6 ; fully-qualified # 🇿🇦 E2.0 flag: South Africa -1F1FF 1F1F2 ; fully-qualified # 🇿🇲 E2.0 flag: Zambia -1F1FF 1F1FC ; fully-qualified # 🇿🇼 E2.0 flag: Zimbabwe - -# subgroup: subdivision-flag -1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 E5.0 flag: England -1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 E5.0 flag: Scotland -1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 E5.0 flag: Wales - -# Flags subtotal: 275 -# Flags subtotal: 275 w/o modifiers - -# Status Counts -# fully-qualified : 3655 -# minimally-qualified : 827 -# unqualified : 242 -# component : 9 - -#EOF diff --git a/emoji/scrape_aliases b/emoji/scrape_aliases deleted file mode 100755 index 3290d80b..00000000 --- a/emoji/scrape_aliases +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python -import asyncio -import json -import re - -from requests_html import AsyncHTMLSession -from rich.progress import track - -LINE_RE = re.compile( - r""" - ^ - (?P .*\S) - \s*;\s* - (?P \S+) - \s*\#\s* - (?P \S+) - \s* - (?P E\d+.\d+) - \s* - (?P .+) - $ -""", - re.VERBOSE, -) - - -async def get_aliases(session, name): - emojipedia_name = name.replace(" ", "-") - url = f"https://emojipedia.org/{emojipedia_name}/" - r = await session.get(url) - if r.status_code != 200: - return (name, []) - - aliases = r.html.find(".aliases li") - res = [] - for alias in aliases: - alias = alias.text.split(maxsplit=1)[1] - alias = alias.replace("\N{NO-BREAK SPACE}", " ") # Replace nbsp - res.append(alias) - return (name, res) - - -async def main(): - session = AsyncHTMLSession() - aliases = {} - - aws = [] - with open("emoji-test.txt") as f: - for l in f: - if m := LINE_RE.match(l): - d = m.groupdict() - if d["status"] != "fully-qualified": - continue - - aws.append(get_aliases(session, d["name"])) - - for aw in track(asyncio.as_completed(aws), total=len(aws)): - name, res = await aw - if res: - aliases[name] = res - - print(name, res) - - with open("aliases.json", "w") as f: - json.dump(aliases, f) - - -asyncio.run(main()) From e7d3d186a32eb85299ce15c02d2154642b015d9f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:56:37 +0200 Subject: [PATCH 042/243] [goldendict] Interface v2.0 --- goldendict/__init__.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index c1bbd3db..6b1aba14 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,32 +1,25 @@ from albert import Action, Item, TriggerQuery, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error -md_iid = '1.0' -md_version = '1.2' +md_iid = '2.0' +md_version = '1.3' md_name = 'GoldenDict' md_description = 'Searches in GoldenDict' md_url = 'https://github.com/albertlauncher/python/' md_maintainers = '@stevenxxiu' md_bin_dependencies = ['goldendict'] -TRIGGER = 'gd' -ICON_PATH = '/usr/share/pixmaps/goldendict.png' +class Plugin(PluginInstance, TriggerQueryHandler): -class Plugin(TriggerQueryHandler): - def id(self) -> str: - return __name__ - - def name(self) -> str: - return md_name - - def description(self) -> str: - return md_description - - def defaultTrigger(self) -> str: - return f'{TRIGGER} ' - - def synopsis(self) -> str: - return 'query' + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='query', + defaultTrigger='gd ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = ["xdg:goldendict"] def handleTriggerQuery(self, query: TriggerQuery) -> None: query_str = query.string.strip() @@ -34,11 +27,11 @@ def handleTriggerQuery(self, query: TriggerQuery) -> None: return query.add( - Item( + StandardItem( id=md_name, text=md_name, subtext=f'Look up {query_str} using GoldenDict', - icon=[ICON_PATH], + iconUrls=self.iconUrls, actions=[Action(md_name, md_name, lambda: runDetachedProcess(['goldendict', query_str]))], ) ) From e11d5c47af7860b76c21c70f87a23bcfc93a7004 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:57:00 +0200 Subject: [PATCH 043/243] [googletrans] Interface v2.0 --- googletrans/__init__.py | 48 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/googletrans/__init__.py b/googletrans/__init__.py index 65e3873d..a97d4a81 100644 --- a/googletrans/__init__.py +++ b/googletrans/__init__.py @@ -4,14 +4,15 @@ Translator using py-googletrans """ -from albert import * -from googletrans import Translator, LANGUAGES from locale import getdefaultlocale +from pathlib import Path from time import sleep -import os -md_iid = '1.0' -md_version = "1.1" +from albert import * +from googletrans import Translator, LANGUAGES + +md_iid = '2.0' +md_version = "1.2" md_name = "Google Translate" md_description = "Translate sentences using googletrans" md_license = "BSD-3" @@ -19,32 +20,25 @@ md_lib_dependencies = "googletrans==3.1.0a0" md_maintainers = "@manuelschneid3r" -class Plugin(TriggerQueryHandler): - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "tr " - - def synopsis(self): - return "[[src] dest] text" +class Plugin(TriggerQueryHandler): - def initialize(self): - self.icon = [os.path.dirname(__file__)+"/google_translate.png"] + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis="[[src] dest] text", + defaultTrigger='tr ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/google_translate.png"] self.translator = Translator() self.lang = getdefaultlocale()[0][0:2] def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: - for number in range(50): + for _ in range(50): sleep(0.01) if not query.isValid: return @@ -64,11 +58,11 @@ def handleTriggerQuery(self, query): else: translation = self.translator.translate(text, dest=dest) - query.add(Item( + query.add(StandardItem( id=md_id, text=translation.text, subtext=f'From {LANGUAGES[translation.src]} to {LANGUAGES[translation.dest]}', - icon=self.icon, - actions = [Action("copy", "Copy result to clipboard", - lambda t=translation.text: setClipboardText(t))] + iconUrls=self.iconUrls, + actions=[Action("copy", "Copy result to clipboard", + lambda t=translation.text: setClipboardText(t))] )) From bce5ce82df98e06777390c258c758eca8bfcc39c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:57:27 +0200 Subject: [PATCH 044/243] [jetbrains] Interface v2.0 --- jetbrains_projects/__init__.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 10bb9aa4..9a5258c3 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -15,8 +15,8 @@ from xml.etree import ElementTree from albert import * -md_iid = '1.0' -md_version = "1.4" +md_iid = '2.0' +md_version = "1.5" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "GPL-3" @@ -82,22 +82,19 @@ def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: return [] -class Plugin(TriggerQueryHandler): - executables = [] - - def id(self): - return md_id +class Plugin(PluginInstance, TriggerQueryHandler): - def name(self): - return md_name - - def description(self): - return md_description + executables = [] - def defaultTrigger(self): - return "jb " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='project name', + defaultTrigger='jb ') + PluginInstance.__init__(self, extensions=[self]) - def initialize(self): plugin_dir = Path(__file__).parent editors = [ Editor( @@ -175,12 +172,12 @@ def handleTriggerQuery(self, query: TriggerQuery): query.add([self._make_item(editor, project, query) for editor, project in editor_project_pairs]) def _make_item(self, editor: Editor, project: Project, query: TriggerQuery) -> Item: - return Item( + return StandardItem( id="%s-%s-%s" % (editor.binary, project.path, project.last_opened), text=project.name, subtext=project.path, - completion=query.trigger + project.name, - icon=[str(editor.icon)], + inputActionText=query.trigger + project.name, + iconUrls=["file:" + str(editor.icon)], actions=[ Action( "Open", From 6c9c962d16f299adde2456f1bfb6f8c093a1fdab Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 01:57:43 +0200 Subject: [PATCH 045/243] [kill] Interface v2.0 --- kill/__init__.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/kill/__init__.py b/kill/__init__.py index 7aec13c8..0b259a23 100644 --- a/kill/__init__.py +++ b/kill/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = '1.0' -md_version = "1.2" +md_iid = '2.0' +md_version = "1.3" md_name = "Kill Process" md_description = "Kill processes" md_license = "BSD-3" @@ -15,23 +15,14 @@ md_credits = "Original idea by Benedict Dudel & Manuel Schneider" -class Plugin(TriggerQueryHandler): - icon_path = "xdg:process-stop" - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def initialize(self): - pass - - def defaultTrigger(self): - return "kill " +class Plugin(PluginInstance, TriggerQueryHandler): + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='kill ') + PluginInstance.__init__(self, extensions=[self]) def handleTriggerQuery(self, query): if not query.isValid: @@ -53,9 +44,9 @@ def handleTriggerQuery(self, query): .replace("\0", " ") ) results.append( - Item( + StandardItem( id="kill", - icon=[self.icon_path], + iconUrls=["xdg:process-stop"], text=proc_command, subtext=proc_cmdline, actions=[ From fb6cde26e1f02f6c7708dc090fa543fc7dcb4f05 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:02:16 +0200 Subject: [PATCH 046/243] [locate] Interface v2.0 --- locate/__init__.py | 48 +++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/locate/__init__.py b/locate/__init__.py index 40fef9bb..461de92b 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -7,14 +7,14 @@ """ -from albert import * -import os -import pathlib import shlex import subprocess +from pathlib import Path + +from albert import * -md_iid = '1.0' -md_version = "1.8" +md_iid = '2.0' +md_version = "1.9" md_name = "Locate" md_description = "Find and open files using locate" md_license = "BSD-3" @@ -24,28 +24,21 @@ class Plugin(TriggerQueryHandler): - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "'" - - def synopsis(self): - return "" + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger="'") + PluginInstance.__init__(self, extensions=[self]) - def initialize(self): - self.icons = [ + self.iconUrls = [ "xdg:preferences-system-search", "xdg:system-search", "xdg:search", "xdg:text-x-generic", - str(pathlib.Path(__file__).parent / "locate.svg") + f"file:{Path(__file__).parent}/locate.svg" ] def handleTriggerQuery(self, query): @@ -64,13 +57,12 @@ def handleTriggerQuery(self, query): return for path in lines: - basename = os.path.basename(path) query.add( - Item( + StandardItem( id=path, - text=basename, + text=Path(path).name, subtext=path, - icon=self.icons, + iconUrls=self.iconUrls, actions=[ Action("open", "Open", lambda p=path: openUrl("file://%s" % p)) ] @@ -78,11 +70,11 @@ def handleTriggerQuery(self, query): ) else: query.add( - Item( + StandardItem( id="updatedb", text="Update locate database", subtext="Type at least three chars for a search", - icon=self.icons, + iconUrls=self.iconUrls, actions=[ Action("update", "Update", lambda: runTerminal("sudo updatedb")) ] From 6afc16efe6368a7a6cb390a6199c964c9ecf7bc5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:02:33 +0200 Subject: [PATCH 047/243] [mathematica_eval] Interface v2.0 --- mathematica_eval/__init__.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/mathematica_eval/__init__.py b/mathematica_eval/__init__.py index e988fc67..6bb38c4d 100644 --- a/mathematica_eval/__init__.py +++ b/mathematica_eval/__init__.py @@ -7,8 +7,8 @@ from albert import (Action, Item, TriggerQuery, TriggerQueryHandler, setClipboardText) -md_iid = "1.0" -md_version = "1.0" +md_iid = "2.0" +md_version = "1.1" md_name = "Mathematica Eval" md_description = "Evaluate Mathemtica code" md_license = "GPL-3.0" @@ -17,21 +17,16 @@ md_bin_dependencies = ["wolframscript"] -class Plugin(TriggerQueryHandler): - def id(self) -> str: - return md_id +class Plugin(PluginInstance, TriggerQueryHandler): - def name(self) -> str: - return md_name - - def description(self) -> str: - return md_description - - def defaultTrigger(self) -> str: - return "mma " - - def synopsis(self) -> str: - return "" + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='mma ') + PluginInstance.__init__(self, extensions=[self]) def handleTriggerQuery(self, query: TriggerQuery) -> None: stripped = query.string.strip() @@ -61,11 +56,11 @@ def handleTriggerQuery(self, query: TriggerQuery) -> None: result_str = output.strip() query.add( - Item( + StandardItem( id=md_id, text=result_str, - completion=query.trigger + result_str, - icon=["xdg:wolfram-mathematica"], + inputActionText=query.trigger + result_str, + iconUrls=["xdg:wolfram-mathematica"], actions=[ Action( "copy", From aa454efb825a57c96220f17390998e4c0bf77aa4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:04:01 +0200 Subject: [PATCH 048/243] [pacman] Interface v2.0 --- pacman/__init__.py | 50 +++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/pacman/__init__.py b/pacman/__init__.py index 0b2ee192..11e9a841 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -11,8 +11,8 @@ from albert import Action, Item, TriggerQueryHandler, runTerminal, openUrl -md_iid = '1.0' -md_version = "1.7" +md_iid = '2.0' +md_version = "1.8" md_name = "PacMan" md_description = "Search, install and remove packages" md_license = "BSD-3" @@ -20,30 +20,22 @@ md_bin_dependencies = ["pacman", "expac"] -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): pkgs_url = "https://www.archlinux.org/packages/" - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def synopsis(self): - return "" - - def defaultTrigger(self): - return "pac " - - def initialize(self): - self.icons = [ + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='pac ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [ "xdg:archlinux-logo", "xdg:system-software-install", - str(pathlib.Path(__file__).parent / "arch.svg") + f"file:{pathlib.Path(__file__).parent}/arch.svg" ] def handleTriggerQuery(self, query): @@ -51,11 +43,11 @@ def handleTriggerQuery(self, query): # Update item on empty queries if not stripped: - query.add(Item( + query.add(StandardItem( id="%s-update" % md_id, text="Pacman package manager", subtext="Enter the package you are looking for or hit enter to update.", - icon=self.icons, + iconUrls=self.iconUrls, actions=[ Action("up-nc", "Update packages (no confirm)", lambda: runTerminal("sudo pacman -Syu --noconfirm")), @@ -82,7 +74,7 @@ def handleTriggerQuery(self, query): remote_pkgs = [tuple(line.split('\t')) for line in proc_s.stdout.read().split('\n')[:-1]] # newline at end for pkg_name, pkg_vers, pkg_repo, pkg_desc, pkg_purl, pkg_deps in remote_pkgs: - if stripped not in pkg_name : + if stripped not in pkg_name: continue pkg_installed = True if pkg_name in local_pkgs else False @@ -100,12 +92,12 @@ def handleTriggerQuery(self, query): if pkg_purl: actions.append(Action("proj_url", "Show project website", lambda u=pkg_purl: openUrl(u))) - item = Item( + item = StandardItem( id="%s_%s_%s" % (md_id, pkg_repo, pkg_name), - icon=self.icons, + iconUrls=self.iconUrls, text="%s %s [%s]" % (pkg_name, pkg_vers, pkg_repo), subtext=f"{pkg_desc} [Installed]" if pkg_installed else f"{pkg_desc}", - completion="%s%s" % (query.trigger, pkg_name), + inputActionText="%s%s" % (query.trigger, pkg_name), actions=actions ) items.append(item) @@ -113,10 +105,10 @@ def handleTriggerQuery(self, query): if items: query.add(items) else: - query.add(Item( + query.add(StandardItem( id="%s-empty" % md_id, text="Search on archlinux.org", subtext="No results found in the local database", - icon=self.icons, + iconUrls=self.iconUrls, actions=[Action("search", "Search on archlinux.org", lambda: openUrl(f"{self.pkgs_url}?q={stripped}"))] )) From c409e3feab2708740f03c0ba6dd6abbf4a691b74 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:05:07 +0200 Subject: [PATCH 049/243] [pass] Interface v2.0 --- pass/__init__.py | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/pass/__init__.py b/pass/__init__.py index 0a76a51f..97feb71e 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -4,8 +4,8 @@ import os from albert import * -md_iid = '1.0' -md_version = "1.3" +md_iid = '2.0' +md_version = "1.4" md_name = "Pass" md_description = "Manage passwords in pass" md_bin_dependencies = ["pass"] @@ -14,24 +14,19 @@ HOME_DIR = os.environ["HOME"] PASS_DIR = os.environ.get("PASSWORD_STORE_DIR", os.path.join(HOME_DIR, ".password-store/")) -ICON = ["xdg:dialog-password"] -class Plugin(TriggerQueryHandler): - def id(self): - return md_id +class Plugin(PluginInstance, TriggerQueryHandler): - def name(self): - return md_name - - def description(self): - return md_description - - def synopsis(self): - return "" - - def defaultTrigger(self): - return "pass " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='pass ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = ["xdg:dialog-password"] def handleTriggerQuery(self, query): if query.string.strip().startswith("generate"): @@ -42,19 +37,18 @@ def handleTriggerQuery(self, query): def generatePassword(self, query): location = query.string.strip()[9:] - query.add(Item( + query.add(StandardItem( id="generate_password", - icon=ICON, + iconUrls=self.iconUrls, text="Generate a new password", subtext="The new password will be located at %s" % location, - completion="pass %s" % query.string, + inputActionText="pass %s" % query.string, actions=[ Action("generate", "Generate", lambda: runDetachedProcess(["pass", "generate", "--clip", location, "20"])) ] )) def showPasswords(self, query): - passwords = [] if query.string.strip(): passwords = self.getPasswordsFromSearch(query) else: @@ -64,12 +58,12 @@ def showPasswords(self, query): for password in passwords: name = password.split("/")[-1] results.append( - Item( + StandardItem( id=password, - icon=ICON, text=name, subtext=password, - completion="pass %s" % password, + iconUrls=self.iconUrls, + inputActionText="pass %s" % password, actions=[ Action("copy", "Copy", lambda pwd=password: runDetachedProcess(["pass", "--clip", pwd])), Action("edit", "Edit", lambda pwd=password: runDetachedProcess(["pass", "edit", pwd])), From 33d6ad9eff545699e217f05d6bc178c503e79ea7 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:07:04 +0200 Subject: [PATCH 050/243] [pomodoro] Interface v2.0 --- pomodoro/__init__.py | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 53faba9b..a18b4455 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -5,14 +5,15 @@ https://en.wikipedia.org/wiki/Pomodoro_Technique """ -from albert import * import subprocess import threading import time -import os +from pathlib import Path + +from albert import * -md_iid = '1.0' -md_version = "1.2" +md_iid = '2.0' +md_version = "1.3" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "BSD-3" @@ -66,36 +67,28 @@ def isActive(self): return self.timer is not None -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): - icon = [os.path.dirname(__file__) + "/pomodoro.svg"] default_pomodoro_duration = 25 default_break_duration = 5 default_longbreak_duration = 15 default_pomodoro_count = 4 - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return "pomo " - - def initialize(self): + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='[duration [break duration [long break duration [count]]]]', + defaultTrigger='pomo ') + PluginInstance.__init__(self, extensions=[self]) self.pomodoro = PomodoroTimer() - - def synopsis(self): - return "[duration [break duration [long break duration [count]]]]" + self.iconUrls = [f"file:{Path(__file__).parent}/pomodoro.svg"] def handleTriggerQuery(self, query): - item = Item( + item = StandardItem( id=md_id, - icon=self.icon, + iconUrls=self.iconUrls, text=md_name ) From b13835e4cd8f238e0e460346588bc7cb60d79a5e Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:08:13 +0200 Subject: [PATCH 051/243] [python_eval] Interface v2.0 --- python_eval/__init__.py | 45 +++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/python_eval/__init__.py b/python_eval/__init__.py index 71a480ff..86f86d16 100644 --- a/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -1,39 +1,30 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2022-2023 Manuel Schneider -from albert import * from builtins import pow from math import * -import os +from pathlib import Path + +from albert import * -md_iid = '1.0' -md_version = "1.4" +md_iid = '2.0' +md_version = "1.5" md_name = "Python Eval" md_description = "Evaluate Python code" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/python_eval" -md_maintainers = "@manuelschneid3r" - - -class Plugin(TriggerQueryHandler): - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - def defaultTrigger(self): - return "py " - def synopsis(self): - return "" +class Plugin(PluginInstance, TriggerQueryHandler): - def initialize(self): - self.iconPath = os.path.dirname(__file__)+"/python.svg" + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='py ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/python.svg"] def handleTriggerQuery(self, query): stripped = query.string.strip() @@ -45,12 +36,12 @@ def handleTriggerQuery(self, query): result_str = str(result) - query.add(Item( + query.add(StandardItem( id=md_id, text=result_str, subtext=type(result).__name__, - completion=query.trigger + result_str, - icon=[self.iconPath], + inputActionText=query.trigger + result_str, + iconUrls=self.iconUrls, actions = [ Action("copy", "Copy result to clipboard", lambda r=result_str: setClipboardText(r)), Action("exec", "Execute python code", lambda r=result_str: exec(stripped)), From 94f5fe4ac49746348a77a0ce9694130aba625f0c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:09:28 +0200 Subject: [PATCH 052/243] [tex_to_unicode] Interface v2.0 --- tex_to_unicode/__init__.py | 39 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/tex_to_unicode/__init__.py b/tex_to_unicode/__init__.py index 6393ed43..f28fabdd 100644 --- a/tex_to_unicode/__init__.py +++ b/tex_to_unicode/__init__.py @@ -2,15 +2,15 @@ # Copyright (c) 2022 Manuel Schneider -import os import re import unicodedata +from pathlib import Path +from pylatexenc.latex2text import LatexNodes2Text from albert import * -from pylatexenc.latex2text import LatexNodes2Text -md_iid = '1.0' -md_version = "1.1" +md_iid = '2.0' +md_version = "1.2" md_name = "TeX to Unicode" md_description = "Convert TeX mathmode commands to unicode characters" md_license = "GPL-3.0" @@ -19,25 +19,18 @@ md_maintainers = "@DenverCoder1" -class Plugin(TriggerQueryHandler): - def id(self) -> str: - return md_id - - def name(self) -> str: - return md_name - - def description(self) -> str: - return md_description - - def defaultTrigger(self) -> str: - return "tex " - - def synopsis(self) -> str: - return "" +class Plugin(PluginInstance, TriggerQueryHandler): - def initialize(self) -> None: + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='tex ') + PluginInstance.__init__(self, extensions=[self]) self.COMBINING_LONG_SOLIDUS_OVERLAY = "\u0338" - self.icon = [os.path.dirname(__file__) + "/tex.png"] + self.iconUrls = [f"file:{Path(__file__).parent}/tex.png"] def _create_item(self, text: str, subtext: str, can_copy: bool) -> Item: actions = [] @@ -49,11 +42,11 @@ def _create_item(self, text: str, subtext: str, can_copy: bool) -> Item: lambda t=text: setClipboardText(t), ) ) - return Item( + return StandardItem( id=md_id, - icon=self.icon, text=text, subtext=subtext, + iconUrls=self.iconUrls, actions=actions, ) From 32d1701664dfd37194631239fba6870a5c210deb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:10:28 +0200 Subject: [PATCH 053/243] [timer] Interface v2.0 --- timer/__init__.py | 59 +++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/timer/__init__.py b/timer/__init__.py index 5fc96bbb..a85e6ee1 100644 --- a/timer/__init__.py +++ b/timer/__init__.py @@ -10,16 +10,17 @@ - `120:` starts a 2 hours timer """ -from albert import * -from time import strftime, time, localtime -from datetime import timedelta +import subprocess import threading +from datetime import timedelta +from pathlib import Path from sys import platform -import os -import subprocess +from time import strftime, time, localtime -md_iid = '1.0' -md_version = "1.5" +from albert import * + +md_iid = '2.0' +md_version = "1.6" md_name = "Timer" md_description = "Set up timers" md_license = "BSD-2" @@ -37,11 +38,18 @@ def __init__(self, interval, name, callback): self.start() -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): - def initialize(self): - self.icons = [os.path.dirname(__file__)+"/time.svg"] - self.soundPath = os.path.dirname(__file__)+"/bing.wav" + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='[[hrs:]mins:]secs [name]', + defaultTrigger='timer ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/time.svg"] + self.soundPath = Path(__file__).parent / "bing.wav" self.timers = [] def finalize(self): @@ -68,21 +76,6 @@ def onTimerTimeout(self, timer): self.deleteTimer(timer) - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - - def defaultTrigger(self): - return 'timer ' - - def synopsis(self): - return '[[hrs:]mins:]secs [name]' - def handleTriggerQuery(self, query): if not query.isValid: return @@ -92,11 +85,11 @@ def handleTriggerQuery(self, query): fields = args[0].split(":") name = args[1] if 1 < len(args) else '' if not all(field.isdigit() or field == '' for field in fields): - return Item( + return StandardItem( id=self.name(), text="Invalid input", subtext="Enter a query in the form of '%s[[hours:]minutes:]seconds [name]'" % self.defaultTrigger(), - icon=self.icons + iconUrls=self.iconUrls, ) seconds = 0 @@ -104,11 +97,11 @@ def handleTriggerQuery(self, query): for i in range(len(fields)): seconds += int(fields[i] if fields[i] else 0)*(60**i) - query.add(Item( + query.add(StandardItem( id=self.name(), text=str(timedelta(seconds=seconds)), subtext='Set a timer with name "%s"' % name if name else 'Set a timer', - icon=self.icons, + iconUrls=self.iconUrls, actions=[Action("set-timer", "Set timer", lambda sec=seconds: self.startTimer(sec, name))] )) return @@ -121,13 +114,13 @@ def handleTriggerQuery(self, query): identifier = "%d:%02d:%02d" % (h, m, s) timer_name_with_quotes = '"%s"' % timer.name if timer.name else '' - items.append(Item( + items.append(StandardItem( id=self.name(), text='Delete timer %s [%s]' % (timer_name_with_quotes, identifier), subtext="Times out %s" % strftime("%X", localtime(timer.end)), - icon=self.icons, + iconUrls=self.iconUrls, actions=[Action("delete-timer", "Delete timer", lambda timer=timer: self.deleteTimer(timer))] )) if items: - query.add(items) \ No newline at end of file + query.add(items) From 52b30f7ef78716f4cacdfa6c0c027babf989c33f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:11:04 +0200 Subject: [PATCH 054/243] [unit_converter] Interface v2.0 --- unit_converter/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unit_converter/__init__.py b/unit_converter/__init__.py index d25f547f..462dad4f 100644 --- a/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -29,8 +29,8 @@ import pint -md_iid = '1.0' -md_version = "1.3" +md_iid = '2.0' +md_version = "1.4" md_name = "Unit Converter" md_description = "Convert between units" md_license = "MIT" @@ -356,7 +356,7 @@ def initialize(self): self.currency_converter = CurrencyConverter() def id(self) -> str: - return __name__ + return md_id def name(self) -> str: return md_name @@ -405,9 +405,9 @@ def _create_item(self, text: str, subtext: str, icon: str = "") -> albert.Item: if not icon or not icon_path.exists(): albert.warning(f"Icon {icon} does not exist") icon_path = Path(__file__).parent / "icons" / "unit_converter.svg" - return albert.Item( + return albert.StandardItem( id=str(icon_path), - icon=[str(icon_path)], + iconUrls=["file:" + str(icon_path)], text=text, subtext=subtext, actions=[ From 4bebd79cc38878c31f4b5d61aafba477c96f3b38 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:13:28 +0200 Subject: [PATCH 055/243] [vbox] Interface v2.0 --- virtualbox/__init__.py | 51 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/virtualbox/__init__.py b/virtualbox/__init__.py index f084bcfe..94b21015 100644 --- a/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = '1.0' -md_version = "1.4" +md_iid = '2.0' +md_version = "1.5" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" md_license = "BSD-3" @@ -23,47 +23,48 @@ def startVm(vm): except Exception as e: warning(str(e)) + def acpiPowerVm(vm): with vm.create_session(LockType.shared) as session: session.console.power_button() + def stopVm(vm): with vm.create_session(LockType.shared) as session: session.console.power_down() + def saveVm(vm): with vm.create_session(LockType.shared) as session: session.machine.save_state() + def discardSavedVm(vm): with vm.create_session(LockType.shared) as session: - session.machine.discard_save_state(True); + session.machine.discard_save_state(True) + def resumeVm(vm): with vm.create_session(LockType.shared) as session: session.console.resume() + def pauseVm(vm): with vm.create_session(LockType.shared) as session: session.console.pause() -class Plugin(TriggerQueryHandler): - iconUrls = ["xdg:virtualbox", ":unknown"] - - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description - def synopsis(self): - return "" +class Plugin(PluginInstance, TriggerQueryHandler): - def defaultTrigger(self): - return "vbox " + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='', + defaultTrigger='vbox ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = ["xdg:virtualbox", ":unknown"] def handleTriggerQuery(self, query): items = [] @@ -71,26 +72,26 @@ def handleTriggerQuery(self, query): try: for vm in filter(lambda vm: pattern in vm.name.lower(), virtualbox.VirtualBox().machines): actions = [] - if vm.state == MachineState.powered_off or vm.state == MachineState.aborted: #1 #4 + if vm.state == MachineState.powered_off or vm.state == MachineState.aborted: # 1 # 4 actions.append(Action("startvm", "Start virtual machine", lambda vm=vm: startVm(vm))) - if vm.state == MachineState.saved: #2 + if vm.state == MachineState.saved: # 2 actions.append(Action("restorevm", "Start saved virtual machine", lambda vm=vm: startVm(vm))) actions.append(Action("discardvm", "Discard saved state", lambda vm=vm: discardSavedVm(vm))) - if vm.state == MachineState.running: #5 + if vm.state == MachineState.running: # 5 actions.append(Action("savevm", "Save virtual machine", lambda vm=vm: saveVm(vm))) actions.append(Action("poweroffvm", "Power off via ACPI event (Power button)", lambda vm=vm: acpiPowerVm(vm))) actions.append(Action("stopvm", "Turn off virtual machine", lambda vm=vm: stopVm(vm))) actions.append(Action("pausevm", "Pause virtual machine", lambda vm=vm: pauseVm(vm))) - if vm.state == MachineState.paused: #6 + if vm.state == MachineState.paused: # 6 actions.append(Action("resumevm", "Resume virtual machine", lambda vm=vm: resumeVm(vm))) items.append( - Item( + StandardItem( id=vm.__uuid__, text=vm.name, subtext="{vm.state}".format(vm=vm), - completion=vm.name, - icon=self.iconUrls, + inputActionText=vm.name, + iconUrls=self.iconUrls, actions=actions ) ) From c8725d89951a0a805198d320e735cab30f46105e Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:15:56 +0200 Subject: [PATCH 056/243] [vpn] Interface v2.0 --- vpn/__init__.py | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/vpn/__init__.py b/vpn/__init__.py index 3d6fae20..7c7bc02d 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -1,9 +1,10 @@ -from albert import * -from collections import namedtuple import subprocess +from collections import namedtuple + +from albert import * -md_iid = '1.0' -md_version = "1.3" +md_iid = '2.0' +md_version = "1.4" md_id = "vpn" md_name = "VPN" md_description = "Manage NetworkManager VPN connections" @@ -14,20 +15,17 @@ md_bin_dependencies = ["nmcli"] -class Plugin(TriggerQueryHandler): - - iconPath = ['xdg:network-wired'] +class Plugin(PluginInstance, TriggerQueryHandler): VPNConnection = namedtuple('VPNConnection', ['name', 'connected']) - def id(self): - return md_id - - def name(self): - return md_name - - def description(self): - return md_description + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='vpn ') + PluginInstance.__init__(self, extensions=[self]) def getVPNConnections(self): consStr = subprocess.check_output( @@ -40,25 +38,24 @@ def getVPNConnections(self): if con[2] in ['vpn', 'wireguard']: yield self.VPNConnection(name=con[0], connected=con[3] != '') - - def buildItem(self,con): + @staticmethod + def buildItem(con): name = con.name command = 'down' if con.connected else 'up' text = f'Connect to {name}' if command == 'up' else f'Disconnect from {name}' commandline = ['nmcli', 'connection', command, 'id', name] - return Item( + return StandardItem( id=f'vpn-{command}-{name}', text=name, subtext=text, - icon=self.iconPath, - completion=name, - actions=[ Action("run",text=text, callable=lambda: runDetachedProcess(commandline)) ] + iconUrls=['xdg:network-wired'], + inputActionText=name, + actions=[Action("run", text=text, callable=lambda: runDetachedProcess(commandline))] ) - - def handleTriggerQuery(self,query): + def handleTriggerQuery(self, query): if query.isValid: connections = self.getVPNConnections() if query.string: - connections = [ con for con in connections if query.string.lower() in con.name.lower() ] - query.add([ self.buildItem(con) for con in connections ]) + connections = [con for con in connections if query.string.lower() in con.name.lower()] + query.add([self.buildItem(con) for con in connections]) From 8b1d7480afd76458cc96f3d57f68980bc39e09c4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:25:31 +0200 Subject: [PATCH 057/243] [youtube] Interface v2.0 --- youtube/__init__.py | 54 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/youtube/__init__.py b/youtube/__init__.py index 17f5bc9e..ee6fb6a7 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -8,17 +8,16 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen -from albert import Action, Item, TriggerQuery, TriggerQueryHandler, critical, info, openUrl # pylint: disable=import-error +from albert import Action, StandardItem, TriggerQuery, PluginInstance, TriggerQueryHandler, critical, info, openUrl # pylint: disable=import-error -md_iid = '1.0' -md_version = '1.4' +md_iid = '2.0' +md_version = '1.5' md_name = 'YouTube' md_description = 'Query and open YouTube videos and channels' md_url = 'https://github.com/albertlauncher/python/' md_maintainers = '@stevenxxiu' -ICON_PATH = str(Path(__file__).parent / 'youtube.svg') DATA_REGEX = re.compile(r'\b(var\s|window\[")ytInitialData("\])?\s*=\s*(.*)\s*;', re.MULTILINE) HEADERS = { @@ -52,17 +51,17 @@ def text_from(val: dict[str, Any]) -> str: return text.strip() -def download_item_icon(item: Item, temp_dir: Path) -> None: +def download_item_icon(item: StandardItem, temp_dir: Path) -> None: url = item.icon[0] video_id = url.split('/')[-2] path = temp_dir / f'{video_id}.png' with urlopen_with_headers(url) as response, path.open('wb') as sr: sr.write(response.read()) - item.icon = [str(path)] + item.icon = ["file:" + str(path)] -def entry_to_item(type_, data) -> Item | None: - icon = ICON_PATH +def entry_to_item(type_, data) -> StandardItem | None: + icon = Plugin.iconUrls[0] match type_: case 'videoRenderer': subtext = ['Video'] @@ -87,16 +86,16 @@ def entry_to_item(type_, data) -> Item | None: case _: return None - return Item( + return StandardItem( id=f'{md_name}/{url_path}', text=text_from(data['title']), subtext=' | '.join(subtext), - icon=[icon], + iconUrls=[icon], actions=[Action(f'{md_name}/{url_path}', action, lambda: openUrl(f'https://www.youtube.com/{url_path}'))], ) -def results_to_items(results: dict) -> list[Item]: +def results_to_items(results: dict) -> list[StandardItem]: items: list[Item] = [] for result in results: for type_, data in result.items(): @@ -111,19 +110,18 @@ def results_to_items(results: dict) -> list[Item]: return items -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): temp_dir = None - - def id(self) -> str: - return __name__ - - def name(self) -> str: - return md_name - - def description(self) -> str: - return md_description - - def initialize(self) -> None: + iconUrls = [f"file:{Path(__file__).parent}/youtube.svg"] + + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis='query', + defaultTrigger='yt ') + PluginInstance.__init__(self, extensions=[self]) self.temp_dir = Path(tempfile.mkdtemp(prefix='albert_yt_')) def finalize(self) -> None: @@ -131,12 +129,6 @@ def finalize(self) -> None: child.unlink() self.temp_dir.rmdir() - def defaultTrigger(self) -> str: - return 'yt ' - - def synopsis(self) -> str: - return 'query' - def handleTriggerQuery(self, query: TriggerQuery) -> None: query_str = query.string.strip() if not query_str: @@ -182,10 +174,10 @@ def handleTriggerQuery(self, query: TriggerQuery) -> None: query.add(item) # Add a link to the *YouTube* page, in case there's more results, including results we didn't include - item = Item( + item = StandardItem( id=f'{md_name}/show_more', text='Show more in browser', - icon=[ICON_PATH], + iconUrls=self.iconUrls, actions=[ Action( f'{md_name}/show_more', From 8efaf89e354a4dd99e04291992b240b47b4ce486 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:26:09 +0200 Subject: [PATCH 058/243] [color] Add extension --- color/__init__.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 color/__init__.py diff --git a/color/__init__.py b/color/__init__.py new file mode 100644 index 00000000..10bfa2f9 --- /dev/null +++ b/color/__init__.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +""" +Displays a color parsed from name, which may be in one of these formats: + +* #RGB (each of R, G, and B is a single hex digit) +* #RRGGBB +* #AARRGGBB +* #RRRGGGBBB +* #RRRRGGGGBBBB +* A name from the list of colors defined in the list of SVG color keyword +names provided by the World Wide Web Consortium; for example, "steelblue" +or "gainsboro". + +Note: This extension started as a prototype to test the internal color pixmap +generator. However it may serve as a starting point for people having a real +need for color workflows. PR's welcome. +""" + +from albert import * +from urllib.parse import quote_plus +from string import hexdigits + +md_iid = '2.0' +md_version = '1.0' +md_name = 'Color' +md_description = 'Display color for color codes' +md_license = 'MIT' +md_url = 'https://github.com/albertlauncher/python/color' + + +class Plugin(PluginInstance, GlobalQueryHandler): + + def __init__(self): + GlobalQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='#') + PluginInstance.__init__(self, extensions=[self]) + + def handleGlobalQuery(self, query): + rank_items = [] + s = query.string.strip() + if s: + if s.startswith('#'): # remove hash + s = s[1:] + + # check length and hex + if any([len(s) == l for l in [3, 6, 8, 9, 12]]) and all(c in hexdigits for c in s): + rank_items.append( + RankItem( + StandardItem( + id=md_id, + text=s, + subtext="The color for this code.", + iconUrls=[f"gen:?background=%23{s}"], + ), + 1 + ) + ) + + return rank_items From 09ad279054ba0ddb4f0913fced09e523b6160684 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 02:26:27 +0200 Subject: [PATCH 059/243] [duckduckgo] Add extension --- duckduckgo/__init__.py | 52 +++++++++++++++++ duckduckgo/duckduckgo.svg | 118 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 duckduckgo/__init__.py create mode 100644 duckduckgo/duckduckgo.svg diff --git a/duckduckgo/__init__.py b/duckduckgo/__init__.py new file mode 100644 index 00000000..2f9174e9 --- /dev/null +++ b/duckduckgo/__init__.py @@ -0,0 +1,52 @@ +""" +Inline DuckDuckGo web search using the 'duckduckgo-search' library. +""" + +from albert import * +from pathlib import Path +from duckduckgo_search import DDGS +from itertools import islice +from time import sleep + +md_iid = '2.0' +md_version = '1.0' +md_name = 'DuckDuckGo' +md_description = 'Inline DuckDuckGo web search' +md_url = 'https://github.com/albertlauncher/python/duckduckgo' +md_lib_dependencies = "duckduckgo-search" + + +class Plugin(PluginInstance, TriggerQueryHandler): + + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis="", + defaultTrigger='ddg ') + PluginInstance.__init__(self, extensions=[self]) + self.ddg = DDGS() + self.iconUrls = [f"file:{Path(__file__).parent}/duckduckgo.svg"] + + def handleTriggerQuery(self, query): + + stripped = query.string.strip() + if stripped: + + # dont flood + for number in range(25): + sleep(0.01) + if not query.isValid: + return + + for r in islice(self.ddg.text(stripped, safesearch='off'), 10): + query.add( + StandardItem( + id=md_id, + text=r['title'], + subtext=r['body'], + iconUrls=self.iconUrls, + actions=[Action("open", "Open link", lambda u=r['href']: openUrl(u))] + ) + ) diff --git a/duckduckgo/duckduckgo.svg b/duckduckgo/duckduckgo.svg new file mode 100644 index 00000000..87f27951 --- /dev/null +++ b/duckduckgo/duckduckgo.svg @@ -0,0 +1,118 @@ + + From 2cf4cc1917c2b8907d044fe69265bffe71c6dff1 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 3 Aug 2023 20:48:14 +0200 Subject: [PATCH 060/243] [wiki] Don't fail on lacking language code. Use en. --- wikipedia/__init__.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index b44aac5c..24809292 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path md_iid = '2.0' -md_version = "1.9" +md_version = "1.10" md_name = "Wikipedia" md_description = "Search Wikipedia articles" md_license = "BSD-3" @@ -47,7 +47,6 @@ def __init__(self): self.wiki_fb = WikiFallbackHandler() PluginInstance.__init__(self, extensions=[self, self.wiki_fb]) - params = { 'action': 'query', 'meta': 'siteinfo', @@ -56,7 +55,12 @@ def __init__(self): 'format': 'json' } - Plugin.local_lang_code = getdefaultlocale()[0][0:2] + Plugin.local_lang_code = getdefaultlocale()[0] + if Plugin.local_lang_code: + Plugin.local_lang_code = Plugin.local_lang_code[0:2] + else: + Plugin.local_lang_code = 'en' + warning("Failed getting language code. Using 'en'.") get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) req = request.Request(get_url, headers={'User-Agent': self.user_agent}) @@ -67,15 +71,15 @@ def __init__(self): if self.local_lang_code in languages: self.baseurl = self.baseurl.replace("en", self.local_lang_code) except timeout: - critical('Error getting languages - socket timed out. Defaulting to EN.') + warning('Error getting languages - socket timed out. Defaulting to EN.') except Exception as error: - critical('Error getting languages (%s). Defaulting to EN.' % error) + warning('Error getting languages (%s). Defaulting to EN.' % error) def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: # avoid rate limiting - for number in range(50): + for _ in range(50): sleep(0.01) if not query.isValid: return @@ -99,17 +103,22 @@ def handleTriggerQuery(self, query): title = data[1][i] summary = data[2][i] url = data[3][i] + results.append( + StandardItem( + id=md_id, + text=title, + subtext=summary if summary else url, + iconUrls=self.iconUrls, + actions=[ + Action("open", "Open article on Wikipedia", lambda u=url: openUrl(u)), + Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) + ] + ) + ) - results.append(StandardItem(id=md_id, - text=title, - subtext=summary if summary else url, - iconUrls=self.iconUrls, - actions=[ - Action("open", "Open article on Wikipedia", lambda u=url: openUrl(u)), - Action("copy", "Copy URL to clipboard", lambda u=url: setClipboardText(u)) - ])) if not results: results.append(Plugin.createFallbackItem(stripped)) + query.add(results) else: query.add( From 970f98b3f44a3838510526f5c94dbda1ee8b242e Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 5 Aug 2023 10:02:11 +0200 Subject: [PATCH 061/243] [emoji] New generic and platform agnostic emoji implementation --- emoji/__init__.py | 182 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 emoji/__init__.py diff --git a/emoji/__init__.py b/emoji/__init__.py new file mode 100644 index 00000000..df6965ec --- /dev/null +++ b/emoji/__init__.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +import json +import re +import threading +import urllib.request +from itertools import product +from locale import getdefaultlocale +from pathlib import Path + +from albert import * + +md_iid = '2.0' +md_version = "2.0" +md_name = "Emoji" +md_description = "Find and copy emojis by name" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/master/emoji" + + +class Plugin(PluginInstance, IndexQueryHandler): + + def __init__(self): + IndexQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger=':', + synopsis='') + PluginInstance.__init__(self, extensions=[self]) + self.thread = None + + def finalize(self): + if self.thread.is_alive(): + self.thread.join() + + def updateIndexItems(self): + if self.thread and self.thread.is_alive(): + self.thread.join() + self.thread = threading.Thread(target=self.update_index_items_task) + self.thread.start() + + def update_index_items_task(self): + + def download_file(url: str, path: str) -> bool: + debug(f"Downloading {url}.") + headers = {'User-Agent': 'Mozilla/5.0'} # otherwise github returns html + request = urllib.request.Request(url, headers=headers) + with urllib.request.urlopen(request, timeout=3) as response: + if response.getcode() == 200: + debug(f"Success. Storing to {path}.") + with open(path, 'wb') as file: + file.write(response.read()) + else: + raise RuntimeError(f"Failed to download {url}. Status code: {response.getcode()}") + + def get_fully_qualified_emojis(cache_path: str) -> list: + """Returns fully qualified emoji strings""" + + def convert_to_unicode_char(hex_code: str): + return chr(int(hex_code, 16)) + + def convert_to_unicode_str(hex_codes: str): + hex_list = hex_codes.split() + return ''.join([convert_to_unicode_char(hex_code) for hex_code in hex_list]) + + path = cache_path / 'emoji_list.txt' + if not path.is_file(): + info("Fetching emoji list.") + url = 'https://unicode.org/Public/emoji/latest/emoji-test.txt' + download_file(url, path) + + # components = set() + fully_qualified = [] + + with path.open("r") as f: + + emoji_list_re_str = r""" + ^ + (?P .*\S) + \s*;\s* + (?P \S+) + \s*\#\s* + (?P \S+) + \s* + (?P E\d+.\d+) + \s* + (?P [^:]+) + (?: : \s* (?P .+))? + \n + $ + """ + + line_re = re.compile(emoji_list_re_str, re.VERBOSE) + for line in f: + if match := line_re.match(line): + if match.group("status") == "fully-qualified": + fully_qualified.append(convert_to_unicode_str(match.group("codepoints"))) + + return fully_qualified + + def get_annotations(cache_path: str) -> dict: + + # determine locale + + if lang := getdefaultlocale()[0]: + lang = lang[0:2] + else: + warning("Failed getting locale. There will be no localized emoji aliases.") + lang = 'en' + + # fetch localized cldr annotations 'full' + + path_full = cache_path / 'emoji_annotations_full.json' + if not path_full.is_file(): + url = 'https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/' \ + 'cldr-annotations-full/annotations/%s/annotations.json' % lang + download_file(url, path_full) + + # fetch localized cldr annotations 'derived' + + path_derived = cache_path / 'emoji_annotations_derived.json' + if not path_derived.is_file(): + url = 'https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/' \ + 'cldr-annotations-derived-full/annotationsDerived/%s/annotations.json' % lang + download_file(url, path_derived) + + # open, read, parse, merge, return + + with path_full.open("r", encoding='utf-8') as file_full, \ + path_derived.open("r", encoding='utf-8') as file_derived: + json_full = json.load(file_full)['annotations']['annotations'] + json_derived = json.load(file_derived)['annotationsDerived']['annotations'] + return json_full | json_derived + emojis = get_fully_qualified_emojis(self.cacheLocation) + annotations = get_annotations(self.cacheLocation) + + def remove_redundancy(sentences): + sets_of_words = [set(sentence.lower().split()) for sentence in sentences] + unique = [] + for sow, sentence in zip(sets_of_words, sentences): + for other_sow in sets_of_words: + if sow != other_sow: + if all([any([oword.startswith(word) for oword in other_sow]) for word in sow]): + break + else: + unique.append(sentence) + return unique + + index_items = [] + for emoji in emojis: + try: + ann = annotations[emoji] + except KeyError: + try: + non_rgi_emoji = emoji.replace('\uFE0F', '') + ann = annotations[non_rgi_emoji] + except KeyError as e: + debug(f"Found no translation for {e}. Emoji will not be available.") + continue + + title = ann['tts'][0] + aliases = remove_redundancy([title.replace(':', '').replace(',', ''), *ann['default']]) + + item = StandardItem( + id=emoji, + text=title.capitalize(), + subtext=", ".join([a.capitalize() for a in aliases]), + iconUrls=[f"gen:?text={emoji}"], + actions=[ + Action( + "copy", + "Copy to clipboard", + lambda emj=emoji: setClipboardText(emj), + ), + ] + ) + + for alias in aliases: + index_items.append(IndexItem(item=item, string=alias)) + + self.setIndexItems(index_items) From f49f2172da0eb95df70b2975f9ecdb33b85252d4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 5 Aug 2023 16:36:19 +0200 Subject: [PATCH 062/243] [stub] v2.0 Albert.setClipboardTextAndPaste --- albert.pyi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/albert.pyi b/albert.pyi index f9b824bf..969a40aa 100644 --- a/albert.pyi +++ b/albert.pyi @@ -317,6 +317,14 @@ def setClipboardText(text: str=''): """ +def setClipboardTextAndPaste(text: str=''): + """ + Set the system clipboard text and paste to the front-most window + Args: + text: The text used to set the clipboard + """ + + def openUrl(url: str = ''): """ Open an URL using QDesktopServices::openUrl. From 6a561e016e47abf6884edbb69fa869cee49a2084 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 5 Aug 2023 16:38:19 +0200 Subject: [PATCH 063/243] [emoji] Use paste action --- emoji/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index df6965ec..c8190ea9 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -169,9 +169,12 @@ def remove_redundancy(sentences): iconUrls=[f"gen:?text={emoji}"], actions=[ Action( - "copy", - "Copy to clipboard", - lambda emj=emoji: setClipboardText(emj), + "paste", "Copy and paste to front-most window", + lambda emj=emoji: setClipboardTextAndPaste(emj) + ), + Action( + "copy", "Copy to clipboard", + lambda emj=emoji: setClipboardText(emj) ), ] ) From 30e6d6c3e37cdb1962e9169e7e94f3a5e001440a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20K=C3=A4stner?= Date: Sat, 5 Aug 2023 19:16:05 +0200 Subject: [PATCH 064/243] [pass]: follow symlinks --- pass/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pass/__init__.py b/pass/__init__.py index 97feb71e..9a868c3a 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -76,7 +76,7 @@ def showPasswords(self, query): def getPasswords(self): passwords = [] - for root, dirnames, filenames in os.walk(PASS_DIR): + for root, dirnames, filenames in os.walk(PASS_DIR, followlinks=True): for filename in fnmatch.filter(filenames, "*.gpg"): passwords.append( os.path.join(root, filename.replace(".gpg", "")).replace(PASS_DIR, "") From 75e69801f2d69398f920a71e1f7ec74880e27647 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 8 Aug 2023 16:54:24 +0200 Subject: [PATCH 065/243] [stub] Add missing GQH ctor --- albert.pyi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/albert.pyi b/albert.pyi index 969a40aa..bff1dadb 100644 --- a/albert.pyi +++ b/albert.pyi @@ -268,6 +268,16 @@ class GlobalQuery(ABC): class GlobalQueryHandler(TriggerQueryHandler): """https://albertlauncher.github.io/reference/classalbert_1_1_global_query_handler.html""" + def __init__(self, + id: str, + name: str, + description: str, + synopsis: str = '', + defaultTrigger: str = f'{id} ', + allowTriggerRemap: str = true, + supportsFuzzyMatching: bool = False): + ... + @abstractmethod def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: ... From 99d4d5130a29d00d1565996ff72deda3829a7176 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 8 Aug 2023 16:54:55 +0200 Subject: [PATCH 066/243] [stub] 0.2 add notification --- albert.pyi | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/albert.pyi b/albert.pyi index bff1dadb..ec5130b9 100644 --- a/albert.pyi +++ b/albert.pyi @@ -313,6 +313,12 @@ class IndexQueryHandler(GlobalQueryHandler): ... +class Notification: + + def __init__(self, title: str, subtitle: str = '', text: str = ''): + ... + + def debug(arg: Any):... def info(arg: Any):... def warning(arg: Any):... @@ -361,13 +367,3 @@ def runTerminal(script: str = '', workdir: str = '', close_on_exit: bool = False close_on_exit: Close the terminal on exit. Otherwise exec $SHELL. """ - -def sendTrayNotification(title: str = '', msg: str = '', ms: int = 10000): - """ - Send a tray notification. - Args: - title: The notification title - msg: The notification body - ms: The display time (if supported by the system) - """ - From 675cdec8f9d6e57874808de9510df8b66d1489f6 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 8 Aug 2023 16:58:29 +0200 Subject: [PATCH 067/243] [timer:1.7] use notification, fixes --- timer/__init__.py | 27 +++++++++++---------------- timer/bing.wav | Bin 254670 -> 0 bytes 2 files changed, 11 insertions(+), 16 deletions(-) delete mode 100644 timer/bing.wav diff --git a/timer/__init__.py b/timer/__init__.py index a85e6ee1..02760cd1 100644 --- a/timer/__init__.py +++ b/timer/__init__.py @@ -10,23 +10,22 @@ - `120:` starts a 2 hours timer """ -import subprocess import threading from datetime import timedelta from pathlib import Path -from sys import platform from time import strftime, time, localtime from albert import * md_iid = '2.0' -md_version = "1.6" +md_version = "1.7" md_name = "Timer" md_description = "Set up timers" md_license = "BSD-2" md_url = "https://github.com/albertlauncher/python/tree/master/timer" md_maintainers = ["@manuelschneid3r", "@googol42", "@uztnus"] + class Timer(threading.Timer): def __init__(self, interval, name, callback): @@ -51,6 +50,7 @@ def __init__(self): self.iconUrls = [f"file:{Path(__file__).parent}/time.svg"] self.soundPath = Path(__file__).parent / "bing.wav" self.timers = [] + self.notification = None def finalize(self): for timer in self.timers: @@ -65,15 +65,10 @@ def deleteTimer(self, timer): timer.cancel() def onTimerTimeout(self, timer): - title = 'Timer "%s"' % timer.name if timer.name else 'Timer' - text = "Timed out at %s" % strftime("%X", localtime(timer.end)) - sendTrayNotification(title, text) - - if platform == "linux": - subprocess.Popen(["aplay", self.soundPath]) - elif platform == "darwin": - subprocess.Popen(["afplay", self.soundPath]) - + self.notification = Notification( + title=f"Timer '{timer.name if timer.name else 'Timer'}'", + subtitle=f"Timed out at {strftime('%X', localtime(timer.end))}" + ) self.deleteTimer(timer) def handleTriggerQuery(self, query): @@ -86,7 +81,7 @@ def handleTriggerQuery(self, query): name = args[1] if 1 < len(args) else '' if not all(field.isdigit() or field == '' for field in fields): return StandardItem( - id=self.name(), + id=self.name, text="Invalid input", subtext="Enter a query in the form of '%s[[hours:]minutes:]seconds [name]'" % self.defaultTrigger(), iconUrls=self.iconUrls, @@ -98,7 +93,7 @@ def handleTriggerQuery(self, query): seconds += int(fields[i] if fields[i] else 0)*(60**i) query.add(StandardItem( - id=self.name(), + id=self.name, text=str(timedelta(seconds=seconds)), subtext='Set a timer with name "%s"' % name if name else 'Set a timer', iconUrls=self.iconUrls, @@ -115,11 +110,11 @@ def handleTriggerQuery(self, query): timer_name_with_quotes = '"%s"' % timer.name if timer.name else '' items.append(StandardItem( - id=self.name(), + id=self.name, text='Delete timer %s [%s]' % (timer_name_with_quotes, identifier), subtext="Times out %s" % strftime("%X", localtime(timer.end)), iconUrls=self.iconUrls, - actions=[Action("delete-timer", "Delete timer", lambda timer=timer: self.deleteTimer(timer))] + actions=[Action("delete-timer", "Delete timer", lambda t=timer: self.deleteTimer(t))] )) if items: diff --git a/timer/bing.wav b/timer/bing.wav deleted file mode 100644 index 1e9ef1037d7c1e4fa83e1cc326e41eb71cd916cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254670 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%0A7n7-QP%*c(q` zV}uQohuI%s%*tDa1-1m0%5i>h+m^W4QT)HteD(kH5m2#1qh|jhb zmF=&zs#7&a>sWWd?;|&t_RGB9cx+|{KbL7o(ZlorPt&(_QyIIe);ut|3BQNkRXSc` z%v@10SB_<+CNCHp>EpU%wF~QC)PK`14F!nBqT+l?cD1yt%*UNTEx>c##JbZJOG?Jp z_Hy?0Plp~-#xVvd@r|};yOe4+HE}n$DKhy| z@nUQ{_Z@BXAFL{k{)7Im{Y6j}r*I;f{M>)?Iw{98M5M;a6wg@u$l4uWr&OkDS9^Fw zJpClQr($#J%q(Hf-+U%+QJ_fEU%eiDbH~&z)%|H14;ApsQvS-DoY_lsS@?jJ#8oD} z=3PIgl^w1mX@~pzMQbr#SpE6N^c7A1$$$mh$kL$Ft+$Ko&sCRIdMZEI^HCOhiDFVp zs<vYGz;~ip0L1EtJj875r7RVV{e2>OXo@Yh0#arwE0dH}nT8W~ zDTB~%6a+?bT_xlvf6sGCiO?t>Pq#D zz)0$N{v;(UZH?rX@~jV*7P?QVk4 zI>zlu%Y`ln9(#t>KP~Z;xvC3n6{y){7w5cmfHW;DQm{?FkIsylbOv=N?G)7}O}$2> zdmO@1yGxY?YjZZr4@wWS50F}6S2-uv2+HFnb+yA>r2!k1L+iwzm$smBJbO^eS;p{i zp|_V~zqXgUq;mg{xdvZw22VLP{YwX{zRxUDZGG;rEhb_qm z+{@Xoa$4uyS2U(CC9F+MbMtH|HKEU+DxVm(`!AFJW;Ew86ynr}nMZQY@TusnUa^{7 zZ_%gMCF&j-&f3Lnd*$uS+#&2MXwFh&Br&C_`p52aZ1r5-?ZA`Re)MiuUy(Iq zaO0Ngw|Ei4cVDG5>Da7}Rll#gRMo|?81tBEP>fLGBvR$p6q0ZNt0r2h-CNtFvVYA# zrcCQJcX_giI87odEY1<6J{MIG{iw&W!8WFLU}dXszt-Jy&57TjPv;C|4@fD_N(E23 zZrb(GF#DJK6Lmw4UDfKkeCu$(m|UcU)AbqA)aCq44w?Qn(Zst?)2L)=1w&hGDT;J~ z{=%gQ{K_raioyr-_2e^f)Ge`fwY^jwDUZ~+G}nSY`doohFQwJh_3Vu2jXn@7(hjt#UnMQTr!p0BxP zP4nzaai0t7RmAv0jo^YA7gXys8L#?%Jwnpo)K?*^(WTiBj(nGpMdP|}p zH;!amx>e4tp03sEhC2TB$0O%xRkB6t<+-@jaqJM~Hgq$X<1NtK`?0EOV*NU2Tg)TU zEanH%N=b3rw(OyjKD65Ccl|5vZNo72Cv6J@%Z!cArSL>mStB#fNigEcta-#s=yT5g zs!i2rDhZmY?zh2j&@oDDW~&rs!I_M2l8LmMh{tDke$;=^eyu%UdDe71EF}FQHOO{} zMko@pu_6=Ki(4u z%`#64mXKD{H!?}mfhh;m24_6xu0h4UceQ*?j*+REsJ&*!I%Q}Bf2Dj@#+tMaqRsr~ z3>3y14VxyGi>nUStu-zW?1`;I(^&^a2h*7a{8Sr9hF|0pyYxkq;8si3U`Dte@?+ zWU&!~NxKOFr+Tnem!Q@f>w zl;;Fd_Fk$c#&xOHDL+0{E!C~H4Ty?RVcY@1{SHF!b1bx2jvs8SQ&r3%fWlJ6^%yq@XhX z$gWQLg|mXP7G4*4=boW{_kC+snP!ixBYFf`&uS{^DLa|AKXaH!N!b;>XyofQ>0j4< zQU7ZgX!FI-)8>gjW{}eNOIC}u>{`ND_^X|&x>}8|PS<{Q_XwSXUQq{eWXkHilj)1a z=cx9G!l!WV(JfK8RJNj)WOt2> z^}-zFQ8SxmFjDQ}chp+k)x-*S&|IsUTsEaH+sX`aNhW%KR!~}_tWkDNnap|)efG`L zf6??a#?-Gh7+apN3R}vzNL!?iP<|A?;B}^lAy)X6sbA&N8oOqf`ElS;yd0xqwG?Yo zujSws70f<3W#E-_xMPuar|NF~S?wxsDON?_E8Z#JBkm`s$U3s^#NDBlhB8fYePeaG zd6&J~dj>Uu+Cuzy*5kDI5-jfm;Ujb~mSZp1xN8?xVf0tL>k?zAhuLr0E}1y}SA|DZ zL}rD4IJX;bYE1fX+S~fEZdPnzVs;=sk=(@BV3Z9Pu(ql%F&_w#%B7SS^hFv)w60(8W(y#B~8rtJR`rSyiRscyo1js z6o)mA*Xk?kD&u*B*&6mmqK&EFC3=NF^@*&4wT`+5GbOC`jx=nq3aIAksefzxH8KTT!8t2jBX})O z$Zra|vJOB?U9BwhwL*>Ac-Az}@mu6D%FPNWS19+&apLauT%rpq@=dUrG%cz=sHfR3 z`hyrIWjXz-cvi|~MKigbc^B0+yx3M?yl=XwxuPkxZuH(oJ2=~fwbG69nZnb29BVkH zZzRQ5q>|MCqZ@9X?w=iX!E^>mv{E6;uu5}Ta^mI4Ft6V=S%0<8tVuUq@cuv#BNuY_ ziSLPYvcIK%)=1Jnkx^!}@fQPKH`x4-?Wso&eIhp(Tu2+0vQ%W?b|!U0&q%a(p4LrP z*Q!PsZ+p41EMf`cJZq11YU*U^T7GYmH1gcr$5y9LH6GH53}Ze2#a@v%h=>xhxK5JA zpUs+1p~pMA?-*ICUYdKBfMZAa6toN1k?WKj6{3_|{CC7LsAs{LV~eduyT49q`}@!frEYyz1ln1+0}krpQCSYoMugi3b6%@lagza zRRXl=E2lp-g3AwHFb~&nQs340a-w~X*g@P|@?pVCWfS>4(J|&>v^1^=q}Ym0cwMDB z*S04x6w=XCIDNRkOO{G>f~K6U*oVQvPKDu)9%Wf#4mm~#@5Dz?uZVw0Z_7@I?l4x7 zXP~!+7JEvK{py6eu9jBb9Z)CmA2~bmJ4#5hTJX0r2H`?H>dd!owLQ}F^mi@AuJuV8 z?IY)f_`Jl=Z_S^<-Ce!g^)N2RTUaCceja!ll5XSX(dH(HxQN#IhE@7ymMUwWdv9zE7G|*&3*~&t zcYZ%=61NLJ>SbB~)QzpnFl=zv1v_9fDMRREL~Rsq=^D`v+BCRp=#itRxq~^R`=IaS z$O^Q^_TYXKoDp4+^bk~Wj?w0#%0n}4C+cr%7^b$id4Y~`4EhYCk7%cCe##P&m);&f zFEY?uXqgarj+siC$RUZ;LWg99*viPnv%_BV3gbN^$MC??7CiM0K@Fy6 z3UZa>?TAFo@ixka7GYS84yD9F-FG`EK zsl*sOB>2#=);eCNtKVW$xv25kl+JrFxc`Z!+3MzOo3N zSjBBBp{C4~KIBx>YBBqx!vZOmgY{Y3Tw}VE6Z?WTk#o2a!D!hOg_^gY(i~!V2Lb($ zGeq@IO~2XKN2>8bCSDShWC%|S99^rnb&tbyoRbCTxT-?U?XUN}aT= zU=;l}lpWpfUu?Z&_*FMVr?DRnRiKwLS8^-3D9J>LlJ8>-M(_3Su=g|kt2dduTl>0Q z;VQT_eX8V=v`#WYn8#>IyaKNXEO0e97SzAix3f?84@9L9$+R?qSJF?qNPM4h6H^iy z?|f{{vOO@&FyQQ2z6a1F`V8KGVzNZS_j3sJQp}4O-?c-xKzH6yXL;o77@dj=Q%7-e zlB0^wqN~gS#6av!z~FJ2(7Mg~`zD!hAoPM5V{PN_;WrY~MC)1Qq%*M`hr(hqRvX${ zgN}LrP;vpGAIB(LES)5zaWlxBFqQFB?&;>XdKb7K|L_cq7U5@5$1qlj9?4gTYq;A< z-QoxRbjKHSk!6MPtEt*;h!hcmT(0n~@V&4tlN+x#u$5_{BIQoYcfpbSF8{UU| z!itDyNCmRrc!S9A(D%Y?JVNIc!x2rrai(KXq#xlgnu-03aI&zI=r(^WHHi8nP-iYS z?llcCt+f|>uweuC2V=9KOtxAwpI66tk87Vi6?WL@x+%K82D@DpJ_z3??qLoBd)gEE zH^F*FTMQHsx*+bfLVS8l^Ydpqyu z7z3*yH{*Ym-;!Px?qU{W*Cw6>_Bkw;?s~1JwY8alJN%eh%xcHIEeeQc2+G;p@cPIP z*9v1dLw9qEWveq0C`x=HZ{nX2Tck@wzq2M&o}(K@wt1Cit(u~jT1q@)lKZhs$!)lO zM4hFBq|-RZh?2wv_h!4pw$q3+_Ok|E3*-AqGWHfxsd$NCGj9>2FChXC^UgPx>DwDd znSXPa2j{_kNhZc@5hKMV-p1ij81Uz?&bQEd%CJIz&d|>liT1-?VxHpO2Z%zPzY4u0+U!Lw1%;k&~F{c*d;&_SazhMiqP)o2ef zn^wuMlH^Oa2|v-Qp=hYyxzak;Dl=7?IypxM_M-3j$u4vt@cku9VWeGb>Z{*Z-k!& z&l%kaN24noH!K~@apQYys_S~7C)AX5m~%r4f#0PH*t#oaMdgFH0BihS)&@n>$ocCU`0w#+||tl6oXs z`4U#DezT#c)#en26>tE%l(kiSUxJayxn-o^(Y|o8XNvQ^@n5aow9$Dm(w)$O_Al#^ z;FGY2Fv8tOfza)Oam#&^+I-ct)%Moo49jt+88!Tq(gos8+;fc0`0dHZ;b-t;EMOmmEG(>vRi;1N_R)z4ogatktqt2j%k znYi9zspE)gpYDyJt9`lWkLZ3(FY;xcKz=}K5eis?aZ{7oq4$m&^DzAmy`>#zlv&q$mq*Ld?HR3vqh)Uu(?lN_X@niI{l28L#&Amg zSvS$r-M<*sl>CZK6p_V8B!fi)<{KBjo!WXT;nUf*g&=5ef&f_ zl7n|ZbWg;WWC+rjCkW1{z%4W5w6AoXEMuG>LT8{-LUZmBStEIgWD~cVG#oV}WOAOf zLi#%OWRt}ej`kvsW~}Bu6W^0O5`Ey_Ast9Q@O>~pH|#KSjkm3nyyhsAIEAxL^tWu0 z1jox`RN#ihJ^l@rKANrCp+*@{_af9)l7$@>9F;zib>K&+Z&ADa^PFg#Q!mw9Ob*AQ zNK2fL-a=>+?-T`utvOrBT=W}%isd)MC*3c`&-Pz^N8&T_*J$0qFDVXiugqo4$NU|e z>lfMR#@RYpH^aU(n2)+kTgK7yR)9P16h6doqg}xRPL|25?`V2%zU^!t&P3tqg`(?{ zgOW``4--x1p%w+dIN8ST>LvQIwrjpW;A*^-cANiBGEdrFT*2IdTN(S)t+mF@$Mu~J zTH7HX5Bfko!a<3OByWY!c(>^@u)V-v6JF~%dc1MDWx3~A_!~r{j^z%JWGZmt9ClyQ zfJCEUcaOnTqMfc^Z*J!;PP8KY#(K!73I|DEh>o(p5sTutU4L2?rVa*?`B(d6-{z!| z(2et6+Ch3s_z$-mbq#iDTIZPb)l6a6T!gtWyV-2B+ zp80mR=?~*~(*b)p*an5Bj^XbYUK11ut2pPW$ezce;bdSN0a(P;?*$MOJao zN!5}G!h1{~P6jIFIC<8|hMxK%wr{>s$phpf=2uQ15k{=$2bek>E4I&LFt;_C%>69Q zoHql95`40O+g5~=v=Y8x&85sn`y!8hk1W@OHmb*MuI-#{##h4lNVH`cTKm>`HoFfzzw95a;vDRE< z3R=3_CEi4|JH|zi3ww*N2^qYKgx&f1_2wUgMH_MSIOyhW7H z?@7CXULCQ!me}4}rzAGA>g-)Yi=A;Xb2y1z**_$Z}^w)^NdBWf~46zvP;MbN| zCHfTgD`$#mq%^>PNZ*0~5z7w@@JzKZ4Tnt4?Ck?$XbSNea}|%ypD1(#3nT7MbcQR# zHo(%-+|9PZB@Y%tQqntig*YO5z?;vSPilhxGgjpJVwq|jY9QI}`TmX_!HuD^ncD=j zB=ZHASxn-J_<%sf>9Ay4{xVB#M|=z7%Ltd)?|6Lv6hRN}b@~!QJYMYEVf)qKH#%)I z+#AAQVI%HO<~ku!Ocv2N0g?(eJbc-6#7QvU);G20d!|Rf;zm<9vVQO%3gY~xoSUR{ z$Pl<}TWQ&AJ!dJi7x)fGr{aS2AH3J%UBXN3TePjX^NEw8^Nu{@C}XL)&{Z664y_{w z7@IkFgrCIixlO58(49h8JW}TuGs--|8ggpG7))2{ue@f03%qx{i>yUt66QweoFi#o zVk|a4bN2IZi*Lt{C8uyFNEV1X@b=N?VTFlUpr>=YwTtP4k?LF9ZN%$|1Kw#4x8=I|tnGvIlmA2FFWh$q3Ov#2cxTx~6dLwMe2H(6V~BaY;heRT z_s_^-Oa@s*hj}hhs^A3s326%aCiKR2z;?;j-ZI&`#QQQ@fU9A6I5A$FKbb>fTq4Gj z=AZ=pE$oHKYrF3n8CsiU;||a%Ji72#(R5B3`7cZ~vfVepz236K_|@9lT@Z0$rjvIw zoA3tnEWBQvgJc@o7&_`mvE|x^TQSZVK6B(8#zq;I_?=} z8o3*0Q)Ig5mhBhQL(5awOn-WOH%3Ez!h!|O1+TeV=$&w*l6Qmc++~ja7OwfeGe7t` znN32|^H|q-D|jyUd3q+UE_Tg#+V-dQZ+qN6!ov)YNsb|=veJ3~2#Rtmd?vxaRQEz-Rkt>vc;D`+o2Ea2!5H=tF(NX(KA;O=aeiOQ4C-AO5(bwT)u^ zW?kx4#<-a6)I-eetW~^TJTWt!JQKPS-0qg!+E`aQhPm?naO?~^oBEa8lYfU-%dVtc z$6ZKH4>t7-1AAI?8_lyXREWwU9Hh`WNBD8xLC$%~67=IJ&HI}(>L|AUZX4>k5?X~S zp%|E#xei_x`w~M-x{u0>xji#2<1AlnyPbmqq1X>}Uy6zOl=m7u6N+g=@vq`ff`vYu zeY|RFGegYb;R{6G&U$=uAc)p69*G137F;&K?@cnZO6KAJ@$m7}!b zPu}M4p4RcE(f0e^j_J!c>#Nj@Gk!gdpdO; z#uXO@7rJiQt(H@k7H&uADpW;o&nRSd<;~(nulR=+D_?TGWU+21Rdunw#e)W_G z4<vv79=Tvz>pJ`xc58$wu|hO zTr9sNx*xrV)QQ=Of0(y{HJr8$&w-@Tr=ARZmGu{!&y^O6Bs$^sl&$mxr=I_g{egNP zyDN4ju*iMMj&>Y$aKLXVn&ff9DP|9jiQSv?8*?xD8)jr|v;V5|f`w)yx^w-37BPZUTD4xW&LQq>*(Z5iA_Qk5wFmfvzBr< z^ZGNhNoSyp&?&FXoo^4>2(FF3{;}y89_1hQI_@I&8rEmpFNE>X;*iyK%I>tDv48e- z3$04d!%Ze%VF?8s-X`WdvJbr^P7fws2OVc^b8Wjlvm+x=oyl+L+n5!c8jgptk<3S5 ziO_u&_VG56)9h0F-bU}DekWgKak*=_QuZIzfANpu*5Pd5Ovf;b*Ouj37dj6Q#;qi~ znIAZ8?oD=MY7irjzwoJCjaX7V!WMWq-a;G4?arOX%BPPa=A!q<`uIP&_SkLKzOJ<3o_Hth zGSVw5hrOJuWQ*xH@Vw;B&~{HJXQ}h3v%hw&&TI?HUtb}@frUI!1F=O9gZ zzxTManSH3Uw@)5knQV>yNIb`A!XpEnTR3vozH8jZnX zKW6o!A1B>F?~1MScXOU~L|iGJPkwb2je1CI!=SK}>^ID}lrH$$@cW1^u+YV^U30YX zE)1PcKE!q)tLW>PDE5D>0Hqk)F_9lQ=lR#&&iT+u@O2Kig(u?AQ(deX>{|>A%|IG} z@y1663SIjhPR9YaKQJx!|2Vn|hNq6E8~5v$1b26L_fp&`4#nMyYoWNiySqCScXtXc z&y!r2xaZsV`vbYTduL~7&YW}hfPSU*lCv@Pn!JVWbltI}GkxP!xUt$zo+`uYKjBj8 zVWyz71hN7-iuut`&b*dAnViU5t&hw~v(^5=3bC<3bt5<@j?1 zVR^IAUnvmIpUgF6to58jfX-nk))^jbi|V$KCvoz zO-_g&sh9Q%?9y5m)!~AkV-?5|XkX`R)1p+(NGolKd{-W-I0K_%R93aHaBB?3R$)na zvfW^mk}tyNm3l%k>9{r^JSe$G|HN_`x{og*2IHTg+m^@KcCjVF>)ILVo-jmh8TpiQ zm{~_@q&XhNCZlzr8kXxBEc#io$$mK|6M>h}bb5igiR(A)5cv`>0PnYB##5;$QJ~Zm zriu6D1;I$-fWDz^H1r&a;vx}(FWQEIs(EJct~y@oAXQYChDIhY8|pYpVjJ+n*j=E;=>(8apaSIt|2EO1tkfS+9}=DP;hgivA&VL1gaxm6HBo&uHBY@vMu8KLaXGF zd_WwoJ&f$lK<1GS4H=4;CQ9LTT~|$Y)1Gib-J>MMKcq5&C(&8i&DK>=e!MtQ5R>6z zj`l__wKud!8YNDZIHhs8QBr4EVe>$P@r%@WJTH{j`gdk`tV8IV++FN0A!?z>ymZX; z!8H(ljK=UrAS=!>&rh!k|Dl$Vo{KA$^MP12AHc+?KxVQbsbW0rw$C?gPQDH;SNDm_ z_)Bs@5dR~L1MEGZN%$PHGrk5s0_OQGVvDs>a&dX6)LJ<*&j^@u0jd&^s}~u+PC_c5(#K8?Oi3Z0~fl zVwbc{;$PgqLP2e7B%JWO+*n>VZoX&Gf~qIu{x@CB6q6D;N1)+*q-WRwRxae0By~PmU*> zk-f004x`~qY?K-kApSW2Q63WdH963D(4k-znKLv+IN{lrYniT5Mm;86;fnJ$l>?E} znf2DAP;cx8b)P31ZJc&(vK{YZCpp5*2CaF^sQ;g9JLma|YAze=4X z|HKQyin&etT*xAI=9Bymv8EP{F3!HOUWF@?YndHXEi|vAmSIVvKxncA)O7qmq8cci zOd8^jt7seI3w4-EA#-i7vb!P|)$d{texraZEko^6XG}qtht#N^WIucoWVSTSB*Rq| zjdyVSg;AOmWwW)ceo(JgV3t#3u>4@2^DxmQXpjqvOZYzpLqLr;H570zM*9*AsLJGL zGp&PDrJE)DhvCu~?7@d=S zYgqzu_%*UD)spDu`eN*xd>JSqPZ9(Ce(||BIHu}4*wc0v|WN9c>>DU`LBGdL2@1GVHv{7yC~J`9?Z+l^Zt z50PDDeR?oi4sC1ene86EtmFzGh2sJu-wEwX^*5h`LU@W&DFn}P5tg&*<`IiBgX_&r z6w_)rx-r|%dJP^=bfRkkAe`G5%FSgNM zMc*`*tLzr?@Qe8+@`}*sWHHld=U99fBheplA2iL%e#zO!9#xBx{>cnN~q3NpTOpriVF8T}LDK zr@H6S?rM2{Hyh^T((Pa>S=tnFImrxItLEfWsE_4zdRh3PQkXC9ugX7E{*8pQPpn(u zb)-b!pgCfyYmIS3Vu7|&e8m^ztU?9ta_pY|sN(?kfPms&+vG;mRu_dMOnpxtY5@GwGB15ByhrKE&u1fCM&iP0&`WvIJLGF>18pV+*HTl% z<@d0}fpzis#X0ht}mi7i%YsTtcO*ygS_Az>2OjBPAKQ<0a?|6(l9c8E!CD*p{(Xy8+#jcL8>55mQ~rzcYdv6Z$y*#;56 zbcValUgcb}5NetJ)nbA#kp(?pnEb?eXrg&v>S}0&^n(rf6)vT;iyhZ3w~s_ykmnhv z`yJliInMAY)?fQ3+-CpaYKpxzDQ-1>ab6;DCQMJEcA`F8LtVA#VWl?T(chZeDE||p z)AP;8TwjR}?q^JQ(gF_v&&U({Dt+R|uyMAu(kL3ujQswJW;Ev9sC_VL3N}`zfe_Ey+%%X3!ZNrJT$tYCPh%MluB=M&&y9 zH~0mIQaF4qZLo}it5JQKm2_$1pzDFjlA=TFMIF5_>^{{Yy2Vcl`t4YU*0oZd$7CyKZh8mlJ8XtMB~ zJ;(kc-qK3PVM94bK|H~faC@okXgm8YU6oiDwXi_4vOghC49!YiG-q8l+{tWrGhiD$ zwfvD*K~!w#HgcsvAO9?TJM+~#934t6Wa=~Ri2q!>Orw(Hf`z~|=BwW!TC^T9vwpt4 z9oCy(@4nBV_-lJNebJbp4iamyo&4v8$$>;-kZFdiBTWk?w z7eR*IcYq2a123>_P9F@#q!s*2f7Xx6y&`1x58FfJ9a+%5g1JMigJzq|$x#7XXvwzX zN(q8O#q#T$J5tzZT5?|mc$~qpO#dpjR=daF^p#=vinRb|Y_933s~>TNuH}A6d(dvy zDw(698uE7jAOCBXlcFI#*y#(AV)PjIOEsXaqHw`5#XO^8;^~qhcWA!x#SpE3FE%_2^AGevp)YM zVTd*;{@&0R%+IQM`gq<`5p;sRyRK@~q&(ql{-XY|;;7KeRA);IFqs=S*$UBvy2IP3byXiQAg=JK)Jb3RI7RV-f35O5SD z`nf-Pin*)e2keV=_ab?eCH!4qSKmP4d*I*XJ+mI(OFni#ahIcyqg$=t)5Ak1kz#N9 zzp%}u;^A2tv#kialy2+&xNCBw;Xj;(c#?VXmCIKlV^x&p8sWOBeEdcVEB@ zIS~E0=pm&#pV$B0zfJh56-hoZ?S|@8%iKSiyVOQhwl>W~LVcxQ*fPG2>{NM8cu;1K zwKL+S8L!3jCpj8wXo@F_1-c0N*|)ygY%`@|Y?8h#s4M=aM|+028xs4Rgke}rr~bly z^UYva34OF*5|4~&*Lw1>d$?yCJq9Sd|H%m9Jc@m^5<3l$%k%&gSr8XMhXGZu$W3a!oPlWy@As#xn8KC%j^(Wx%RPb%!{hBxN-Z;^94FAHM@VuZI z?3JxrHXS}B{bUQUOId?hGjt=J&#FTU(j`5Zr#^KKPMYhc)(0;Grff;y9qzq+GSXA` z%>Eu=h6&F}Pa*P|Qvw}CPnGA7`<~{)d{EsJpKrY2no0iSZtVHUlqNFv|FYd9`=u6u zZ8*a(3YCJxQcWxmVS;|?E#kdL9YkJRzND@PKM4`vy4T)f_3$;~Dk+O%TMhkOL2{5MuXmSw1Cez+*L{y{l|_H;oKS8x!4c@0 z>}ReE7o`957R}>wPsWOJhaL;V4b2HLJ|pc3rq|HXHReXJCZONO4VT4XOzNAI7^9Q?3jrS4GVtF+KR zELZYP6MTWfsUDUw$Ze{h_e`G3%w{BM`JVbCNQonSOLFx7Rbu1tv}{*mO z=HYONw0nQgbCeO#p!HH34*d}7_=o$N_=gE&L%z&5+emB=)7`t@Gn#4(OQvUuzcrQ9 zRiMYrjPJC6__-FfqT^sFPUJBI1&Mz6|sIB)KnoDJM$?McFHZU`TtSWh`` zhS`Hxv+u|p50RqV|0LJuKOkHPZcY!j3P>$xLY}JLe$)>*ZH7`W1OM`6eFJkd{^4@n z=xg0w$1vQ$Z1T?Yb|?2c`-5C@RvE;Rz9qhYxDx7txN2MtZJ=g&c6d%R6Y(YX*4d}w z7hsY&GUukRr#LovGPS@`7TH06^v>|^q*o#xEsIiL16zgN{-U`}d>*M$1UL-ZbFnVW zuioXJLexj7lF5|Vrk3Vxz6-wJxst$TtCQiCb1z}@sNNp#nfNA$q}vodqAX{h=k)MZ z6uJavCJTc%%}ZT!SN9fmTd=FZAhkgVk?yb+eD(b2#J!<6>50}EXeDN>$KpOuPKR5Y zM<;s+N(vd@0N+e*GpGvY>uWd$U{9Ib-n{Nk_)o_beXp2PRXNQ6&4=>qwQOR8=`GZX z+{bkA%wqCkvu&lb*Td7KpZ-1|Z+;MO1_P-Lmgz`KdaOrv*QWNsx#otcGeKFLMEiMxyqT&IY-?)IL`baQNv zeXXuT^sqdDo#Ly+_5uu~{wbTK6MTrQ>Z$52LBq% zJ!~8Or+bHc7WE8jX?mY{q5s|COrDEWCv?tDhcg2^7R< ze2aZ?epo=4>}k5;Dn}G^U-m4choCiVM>6xnH>D@+EdNZlk61r+JxyBgAZMvA?l$hj znt8kNy#yB2SET&o;8+#Fv^ce%(MhUt;|LZ>Iq5MjzQ)=;lUGsXc_EfIod(8X2;uIr9(b z6Y&@QjowC682s@efj^|*ITLFZ-vx#xN*b3t z^Wx3v+Dt2|KRVI2G=drHbS88zJ>SwF#1Ya`$l^PS0#Or>i z|1ro{Vg%BSvcEtFQ=sB?*CWO`zZiatx6lp>s(+;a8$Vq85`Su7ov(>C%q6BSy$$OC zYGN_cNx8?h^gm&TN-aabq>EaXLmNnor=HtF#vsmo98?W?rAgd=e?G2HN=NNKDi&@G$#^9U;WkmGL)*;m#@eQ~En#SO0=Owq4A&h)z*w@B{s= zxJL5VP~EiIk^@DF8q8kiG--p6m|vz0q0>@Zei*xhYos)ee#=g<(dc|?I#ZJFkAHDC zG>%H-3(OWKvp?DQLO*R<`~j$mTVZqQyKaQ8j~YSUeKOigoxrd3cLtH!GWb22G@XQw z6B}v0yDj}1p{;E*XT!hB1Gt<1}4O-sMjCD*J|Vi`2vMcE<6p-sCmURBs994i>U!vvA~qr26OOPV@~I zrUmb()?2C~1E_bNx851F7nx|ekeU%p2@U)sd~4X<(y+)zo!-$3Kgq1|ym!wdXS+B< z_4r)%Cif+`t#2J~)po^af?9GuIny)V`-9Q2KK8$}q3}a#7rQZ6%l%DQ6MUJvVd;w{wi*4NPY-hV=D8y=j^YyTaa%^dSy^~A{A&}!3%1QVFSzs>zO zcO17|iAMM82RjQBr=$z%_zCG^sC?$J)q(y;@A2;Ol%s0GdCk3(`2zR&lfEUndiI%oKH5fK z(WxW;W7c~A^u&ps&bRuUs7D#XcFrx}`@}s_`zIEg%EK;tpf~2NTLB;xhe zo!rUX&AA-Msk`D=jh|c(2*RV!Q^3svbxktcE}}`x*p%HjFl z)1BUmth2b&WkXfPFz6YpawX;W;T73J;QYWDtGAtJ3K?+OOmz|`v~j|7-=W;4Ty?d1 z?5du2p2w#$n)@U$XW!rm=`KW@DziA!-_wr^-2(lS$4uiO33x3n^faJvq6=;JGn>Nu zr2L@zdFsydBfQ4^HTgYY5Lfw^_>9~h8Hujb86Cgj{lGIC zxnVNl9BwEOKdt`DmG`~({oqS!PvgHBAr}j3u;U(<9)oqVU(V)_ER!#BdwdT6A@M?} zd-}PhA<~_~Jw?GYR>BX=`BTpW)r3+0vVJ31Mvel-xM077EoGeE*6zH-E$3ebEdEk` z#)-M#eTXniYnX@{ivTyw9&W{Ba*x3N0{zGTcDL1Y3w(sRK* zm#T&&&Cb;Oz;FTf7xA5C=gXfWJ9H21L$MsjlgHvY08rev#=qj%)ik%%*UdM9>!A*e zTZ{`_+lUKHz_ZHT65nNCqH7YpBJW`*_(Fc2crTDlUNc{TuTV8TRXjy$1z_Stx^}3B zwAx=cw=~;QejE;EX4>wfBxCZH_LwNpjhm(?I%)U$nf{Kx&Fm`WdJHynaQ;Rdb@%sn z0B_pS`B0yU63Q%=&o%fz@H;dqVKQsbGjfgRv$rWT1pR4sWOj!VBIQ4mV*sq=KSJd) z9RT8Sn(5=6>CMNKMGBgWBzI_e`B%BubH;%_=Tl^>u7{&Ham_O-PX!R0yz8N%b*!I~ z!xqfdO~TtnufJRF5Z?{HMsPN${^y`InK9l~ z-Z(uT`3Nq+I33u^PtSGcPGKv`10sub>m3mB&RsFjd(RTGrE9%mXzZx6h#Qpq2I&0? z0TlOxaUhf;?|3eH3%N((>+IdK%fgg&)erd${=UMl;QX}BdJ?%!AMz63Yk+H*-%=sv z4|s&CzWKRBfP>MB$Z%a3M@xJ(v&@^5eTK5}HtIVL_vw9axjEpAwJ|<%og=-T zD;|mY7Z2O7WXDBD$*tMxxdC6U;0_i_JuxqVUr=7}e;%B^jTE!CPCpKg5?w6ho8sRg zwT|@2KCn^PV_Nl$^IReuLFG-O5`DDR{CVGJ-zu)IQXy8^(8K8@TDdVV=01*JcGS~< zi{4Ypac2P|c%RTSKqsG?K0rUopYGS57R&+kxV3ubd8od$hArqT=l>`=!s+xNYZdew zz0GsWU5q*c_cr%R4hhr~-2O$rid;w@8!e-+=(q$rj2+(J+$ZqS&WVPLv1005ZZG&3 zWBEDS&IE0$0M#MOx$k@YOna=Wty0z)c_3A08~6nOIB`udl6nX_j572>u*XhP4UtBc z!2qw=Ca(2=&HcbSl`D~V*^2f87{s>9t=xTVxF+u+_ z`dVT5jIW#jf{+^+m^^Rl3H=ASh;KYS=yS*!>%vTj@NwxPTg1PH#Uy?B&&*yci=;qD zx5AB4AE14vaY-d`MyTTd=p*>Opu_m7EAD_mO+3!?jCq4sa#{_O05WrpH~E+OY{G=V zsl;dFM^~5_^?n%49k)uJs)-p9H zXcIg8H~2XL2V`t9~Vuv%wuqFPlI-7q5I;DdAd9@>`v1I24qO2SD z)MDD;Z|#AsFH&CV#vSo-{y)Si!9J~zBDadB~!|J38_dE zo)+$dWC_@A{x!Kfa7{Sn_xi7Kot1^rlKM)HSy&w=3?@3g@S4s+hH&h#+KET~UQ7Sf z&0r~M0{hsvnVSKs;@qren~e^qn|LlTpYhkuO2#B$Hn$Tdv7CPb-&8A`U=3ZJtMRFXg%F3+|0|I5u& zE=HOI6+~(D5cQsE$gCr3fD?OiVp5>4D6^BX{HTymSJ{_r`AKGP*!MV`kI>}P;R=ZQozm#w=Ig6hn8 zm>a}4*Lq_((JZ)IT)<9YuL%XTtFcr1wvKbyVcO&->9!c@=&G*)R`aMZg{{xt7URJi z$=0T0uIfZxMrQ0(bEK1XNaksHu593MvmED;_l5t?w6)%buaOc{ov8q(c-u{fk}HGv z#SQFlYzJYBS~6zSf3fGqE>S$A(x~&wcG6IC90vjAJd&! z>H2E?JJCIGOBfE`G{bLFzr+d|4mj3hSLrA2X|xslYNz!@Vr$iH{34()xCE3I3zJ7o z4MET6V7zWWn6SOKl+NrAca&Ficm2PDIrnbR#Uo(izl<`v5#}T@1v&;4Q=Z^+@esQS zbP$Ktb}`^FXveWNbj;1s_pnEfJO(NDSuG(<1g^@9g+YNb$(XUHs}NBfbl541L=Cn^ z+4Yep@<{Hbe-5`x8VmfuM_T@ZkB}mh!*n8FLCeg4q$-4NN_wsjz^mPA=crK^vdu*w zQ&}cN-^EusZy4UhFKRx)%xz%%3Y<0|p&5!ghvJ2p5Yvn5f$HrAbiDw-x-;Jae6f{M z*ASnQ&DWsW#6+f%dl-2fN}KzozlQAcMy?Axho1qa*jKVEZROEAR3qjN?I3P~uCRMz zU7)tOg+0Q$#JAe&cxS_1hXcP)Ujt~wezb{wpl(;RliFOE$$nycN^?TXQ`OCNq1Hgd zbA{po$HL5MCIU8aXEX7Eha9| zAaHrv7RNlyN9|*3(A`qCR~ys7WL>~=>dC!hI|1%L6~Cn~>_~zhdnGfL zE`Uw6*VbQ)scJ{zKD&T(OVxvulcP*mTuq60bY12HITYz??UBhJX{!|FB|p!Zl)GVb z=C)-5JdxbOWa%bE#&yH=Jh?bHSc-6`*`@qa^-2uUcekHMPg6UXGV}^;DbNG9kGBCC z>kd1CTPSu7+)8{mvd$*>dD`u6PYpxT)=8j`f2K^~G4=%at1N{Yq)S>#!nwq4IuFx< zJOQER1*x&2r_vp66}yi&t1F`x-4a^^^a{0(q39C$4yS5}#ES)5h#GsIHHkF>OyZRx z>sW#fqAR=CQ9|j9%ml>4BeX94AjNFY#YJ#Tz(~=@HdEmR4y5=lm2Ml3jIpN=ow57u^4)8 zZj}BU(#ux90{5Mts=(2=*_yUB$Vajq(~fS6FLjnR9!xO7M`DVtz-=n@EuBHZ|pKUXB>F8zkxnSekax0_(fGPFJbl-&#+vtxpPOgB@SVHMN;T!U7z8-g< z@1V?$xUwUxyMcOO5SYf7#&evx#;(a_;G~<#)#Osb9IagZhQ5SDK#Nlq7?ggER<~R9 zM`9(ls{+Mc;6_S^g6EUH{x?4+me7xA;AsF*kqVhqxU2F9Kb-Bw4^#?A`eb63Kj7Zv zIOY=l8K39sX*!ntEBK2falqzJkkw4ApMJmn0lJ!+%*3c#nA@>Q9|vr#k-~d+3#XG3 zfg(xT^owg3zKPz${3Hzk+kB825Sgd+<;${W{;PZ}Y|5Omtb~V>!^h<9 z;3(-Fx1Y`7*Q-5aZ**1cXVDb3i+M)P!frVzLt&sEJ0rYg!|Vc434LZwaC;8KuWKd5b8KZcEbI&nOk@n>oL%tebPkyK4?!>3&SxQj zSS;cXf?Zfs>JTcBer(Ero#IR~~5oRsLU=8hsbWft2R2-bxlen#tJybCz zo4&h-6MdOZOo$i*f3S$@4&lG#e*8X`2bkZ>h>+=Rg^|HzBfw~RfM=XZ%)Bj^W#^sH+q+YLw8cmneEhf^dewMp76!OPLjP`qo zn_2*#aRP1c=wmn*rvszJF@PV0N{*nE7-n4L{DIA&KG3D9-;goZ8QDqELTY89C%20~ zD;ExTPV-=oEheVXUYaH*18&c$)IXsUa(|#eo-X`QuSfsMHnH_Ywv!lhk?M;HjyHz( ziR}SSMEM(BYjH$iLE?;HTU(tgwnc3#4CYSrZRD<@&8ZINkn0WJ zfj&d8Aa+6y%i8qTa1+2Ey~~ys2CB)3BYV_Z5NSY8p*3nUe%jg8XijzwwwDU?>p7kH zQQIHCtG{Odj_#%I0xY>Iy4-HokBn8;qQdW-fqyLJ2?df9!D^Pl^U!(07gM0?mid|L zkzq;>-TcRDBSXonw27X8^>EBLv`Xv? zz|vE04)>oZ1o|f~8GblcU|Xp%^n6l57TMlr&qc4Q`-N%TIKGekIh32KV!rEA@fGw0 z8X;yuc`XOi3&KC-v;0`D4AAJ*iw?_Pw({_2VkO;}PGLozV~o|4y@IVJnY+U+5Fcv4 z#B=nR;|E%Unn3>~{{S3~i@N=>qncBsIRx;BN{23k{9n#B2`^0lMZYEbz;7%AG9@Cj zm5xF`t|w5HoQ*8bY`3%qtGR=A(BpyjYc80S1cE0enm@|v#jLtKR$t%V4xtED2~-|? z(5OA3UlMl&+Kaz%Pq{wQuR$ug1@K4h_+{!keUmJWbO(wk7+lrZOlZRW&i|Cpg-@kD z76JM|?5FdA_^$|+HxEqx0XQ+GcrQ0oc%qWAX}Z(4{YaP`#k`CF!-3a{%Esc(^VkW>PshmF$am}Q?2%}FHN%(ZPVqbBS0N9G|5Z>^Vmfe=X+_kA z#sdD3J$yy(z`tdC39r@j(TCZlwk^m(@&bLBx`?G6n+=LN%w#Kho25dTizBb^N(i3$eT@fK}NRi_gK(CFA^ z=!(bMX$QpO{Cs|++%Wui>bR+@%Y^?)MX7gqQOIU7Wjv9M%4T5&PlWp`#4w4GE!D6^+ zMXD6o>3<95gg#QqKq7WkH`I0q7H}QaoP3Tr?LYNP5>tbe++R2;T$GbRI2kbvcJxAJ z;y6%abb?1(=VU9!Y=Jrw&zBY3t9c?dfdYYa?Zu7*#q4UVtE;VfX}WBrnYu@8CL9qb zY29P{vNr28XczvK%ujYfdpJrM=Ox>P&dYa&(*h|AfmB=pyvtvK7Mr5l5;x&Nwtscq z<8K3pr7-_VSgn}DwiIjZ;EbX)d4b%3w}o)a@=U{MQhhIag!EwSgCA{G&viMN!QVQ1Q7rd{>1Rb*c32T*8sFuzXsiF8owiz1&8cWZ&@gluzb zVfY%pj?|ME(4mgqhVseLp;z)6VVqD+9vJMB2!ow|9f=Yj$w&BFxUFrY?p^HHzzwON z@Kji%91CAbbu#_oLrB(hX3DJ6w@Qu9DOhUgkt z&p`R`f@F7MDl)((%&OVwSnXlI!f=fwy}QMt(E~$E4(;akjORIBui+nl(3)@-UU-4$arE(+jrkGR~ho`0P8do_QqbG^mgb5qxa#`kPxgJ!Urb*0PdiLf^{+t}T46nTp`BVeqz>#6xr=6$qmV7mN5JT3jusMwLrY_rYv z1|^8i_y%N#Sy?t`rv=+F=b-#bv&&*Y3%{6!g>&2u$xdF+fM!Z#P6XiN<-<4 zv{RiJ7L!)v7W)JEKkOx592@NV$6POaIhGTM$%CX`a!8vJHD_c~H)k1i6z;;8Aa5Nd zO@{Q9$Yrgg+)iq%oDGhRyLJBoU80Hx@Kcx%I&G_AsGN+3CaQVm|0JjC2%k=NFzm7A zgI8i}@g#cOwb$a*O^VM33Y#<11^KA9Inoa35;r*7AYOa~{sCbfS52nO>S&L^Nx8jL zQt21`JJvW`%~HcP4(*BG#qPk}?Mn>rlaIqmwWa(<`l4(NDT(>|u=O&~L4(YR7J$}S zuj~Gc8$)-MM^YQPw$>r?ICaLDu)l#}{5@V4E$!@SevmQ8<^+DoE2Z!9!a%{8lDTVs z?W~P9#_f0)4$?y7YfG#ERGH^8<+dRs+(>%@>yW%Y`* zOM0a?igZau3_I=LU?+YKpMt800hw7`>q(jmX^%eMH=L{q4 zkKrF!KYT4Z+f~!z$!?7m4VF=sNGoM?U``Zp_Do3!i=4qL;8vusqk*Y%`cP!Owm?1& zRB-EqH{-wQqL#<56X+cL4Hkmjw&(h$$yBJh8VB0>qiV(Q+T_>QwPWs<&!EE?v-qASZrGjfvCeLp!r-oEtPa@fz}R43nZUh zIpB^CNS8Hrcf{a9*d^>VatpjiFs($l25Kp_<-^LNV4?W@tO2Ob3!{&)f6+LU$G*kz zJedk_*51k;d@fY7?wR`?&chijKWM92^6uK;g8gYNWnl6rHA~t z+A(|*K%}FsH(X87*;sY-rmLs*lWt)m9THWuQdZfdZHWX^`Hi`@7=&YOF%-d_i_Dhn zt=QaPM7b|>O4(qK*x^hO^Bu<{xC+)8s|iE);l_q(CfX@5O1USWQ%{7-Cl2UhmS(Om zNJ*>>S_Qgct*ZAVPlOw4-{extYpq$tlUiU{W!nioMH^thA`6{H%hGJ)xHU9KeJ2l7 zJ_c&W3T6748adX$ZmcCX7rtReS~$V~0JY*2P+hr`X1etkt-Rj3a-1f$U+uKt$tx2nbJB;t_ z8(;{_i|LS0jt{278E1?PURL(VnldXGjg85kGnaHOK^|i)x&unu9LC>M10rX&q?s!U;^%N>H;$Esn?-~;wm#;>VUK&@R*86k_xfY9Q^WnGSC znX4Q60BC9}Ll3P>^hnYkUZMUahvlx??J$~>4RPB&h{mkAf~<5V%}cUa{7mo$h|NmM zv%np2&*LQ!|J9MHAZ}_PjU5k+ebSwydmqbvxDu{GId7N@HcYQUReWnk34%kVb&Ej&V-E`OGm zs1w3ya-7~{`x8o{h4H=U6IU7QpE@;OF~q1{z5k}YVt<$R00$Bfuf=)7%yIVXU#@-k2$|EFA#FDu^z&13&$3YfP!Q1BiWtRg(qKF%0U zB_mkCq#TrQs(XMd$x&T#>rhuNG6Oq`CS1d;z4UalI@n`(<(KkiZCd1))K0^{HXeF} zHpW&VXPpntXf_sm7tAV?%*r;1 zlc5r72YI?;2!4;U>Ay@<9W2})tA(9|Jr2~gCOtGdEwDt{1frsN2uqaGHMLk=HgplT z2b~2Cw4sK-l4rtowSVQf+)CRPKAaq27;38toj~7X9P+nwnx&#{Tf9K1jXFo}s;mqA z6+M_PV=Co12_sk=tO}^iE1JS-ee4=ILAJ?^`ZhQyemjd;PB|Zewc3xKhQ8Pqg6wXJ z9MdxL9+}X#gc~PM=#y46G#R~)0aFCmbIWEOoai5#r9PKiD~>?n=-l)p<3{^$upRpq zs}G|4l~oAw)2?XwKxgHeyj|TFs-D=W`);Y{`WLB& zbwq1JpRK>@^CwS+t7x~ung2km5V57^8drzrQoT z)YPfxJUBgD%@vn+g;8u4;Joi*F7BDP*<6~uI?+hz9Q~*+u!>NXh5qtn*y+TWdZ7Wh z9`nk{ZLCW&5@He;Cmqn^oEvlsj6&&Rio_Zb85~qIs;IpJwDM;{hr}_-^UW^aJa&oL zAbdOKP0a1^5$Qfx+AmF7s&?YbgmTgAYK+x4*veZ{;YgR*RWbcSZ*hd3;*K|xlX@hk zOq7!<>y%TI9)Q*WHY>)~i5aCl6h2WO>=->hY9!1~ER=l4xZ&1gONehmJ7OZSxgxpc zF8mGuEP1KIX(f=vQ|byb)erNpBq5R|_G(N_xSu$Ll|8|1n7lcWB+QK7)h^n%;T@u- z6h5XwY<5tP-DoHs>vYz$C4~}uCw@t)Ycz7B%qu)3G%scd^l=XP3_mp}WPMVLMzS3VvOV zfb&QLef%a|UCIF`mSkQ9RBTBY9*t;Kt+PQR;hH=z(kk|N%$(3@+>!YRUU6emkgz%- zd(v~gpR*cn9jO?a8Z#jFXQV9L%2S0(um);Fqh}HtC#Fv>U@rGQvP;FD;hNymK8H6* zAGwykLOv!>N}Qe`L^r8l;C+c-`6+V8$ltMLW1Y|;e2bakb}*JEeFY`F70so8u%apR)flI<6*>cYriU)0Cz$_haY91j-Rq zoxW;k(2qr1CEQE6l+?^9;eKFBi&?`=?Dp8_kp=P&zDDqe3{)Q{wn)g6cqzFN84P#P z+>!c6*2h+l-5X9Ny=60boS7?GNNkr-BAP>cVa=m{3B{C>F)32Kipd+wfkmc?Q&oQ( zU74^iA%D_KJ;52k9KugSpMXuO#R!VapAME=FVvff^%CkQj)Ii(+MB@*hg)FA#SV#0 z3J;SyajU$c=IZ1MiJ23u#4H;8COGIW#3~}}BO0?av_qUg<~2iJHSD`unL2_>6TC~u~afL+j}{=h%_zSIQDRCfyhiLo~!TwHXkSV zPb`{nFY&6XSk;18{4x1bWK66XQ#ee+eaR)=0mkB_M+qAe&Ol0ubw<+5k*n;C*&Z8? znWi*Ex2bIQO6^E=Qv&>9WKy#6&F#!i5(@xVtc$G^IV6ogqjU%PxC47;;NZTSj^V;Fha#CmSFym} zavPWh)q&9qiT9G;=!y1y>XNWcmLk1lGDQx`noyZ8<2={>r1H@r(JjeYOxL}_?~9Yp|rdu{3#+t$|*z9MEZiW-I$ZyEjlu~IQgQ{)=kHZL2*i}$j3CP6H=4530Fcua`V7x;8pY z%|qIH2D1czQkq6~MAnD)iA%XheqL*s)+1?5bV1T+t(nz0(72o8>Cml6`N$ll34X_f zy^Cfy^+I%7w0|5K5zH;p0%v1JWV> zMv&W{sV5}mj%H0NrHvv_0h@)z+MyAV9+6t1>f&2Aoxhrdv?fWzVW(O24)*h)IX_pb z5B zW-s+ev`=(G@1a}{cFR2HHe=?Zt#XR+ws3{eVeuq4G^lHz(~l$%N=la;)>m6={b%e~yh|AnE*y?k z>fn;>4sSa-rxi_}ofKBP7=km1+Qh43#n74X!O#I|g|Lv$=GHY&sPB{fBuBk$-f`11 z3d%3<3AG5vhSrPcxx>L(I|tzAR?^DkQhFn+oqvp-fm0}2C^7U~E`jT@iCzk;lD;hY zR8k#vyP?^BP@3N%-dFO6+k}ov2ZgP4H}||5r*%uNmn>_K%zJJ;Jy|#^wGSnQ)+^&g zigyCro?#4DD<>x=H`U8oW&N>i2*cfVp4`CqK4K1@BBd`+Ef?6da=Gr0r!q1-6+S=l397yi)s zyn7^_o{;=4d5?C-%x4Ie$%M{H z*AzK4P%euLA@A&ArUbNujYcNO_`+m8aq&T-D&T zZ5ahLJNdOb#3*k+^B1z$QAYWbvPhXGl|#=N-5X+k)vv2%)UMiK^PvAE+)+8|(9k5%ZTz&F z`gz#t=(aRlS)hEBW+IvG;WKtU!&d964YkVV2*|683)U_rFg1*U#YFcO4Wrqv<7c&?4dVPyQ>xSCS;;ppZ>t_ z7Befil|wQ-TIOm{OPp!uX|16;Q~jb}vM7HRlLD=frYk2DM!td;vakH=_5>pY-=d6m z#kgay4f1hmadr8J(o;z!LZPe6qsj=EVW2h|#=h%NyM1G+R zg%e8>_An2hr8cm}0jUUD&*b%4yMd9Qj#sa1%gi-SergxD6Hk^G zD7WRQ;x`^<%kBg+OTVnHP-o~KN#(7jvj~SoBLA;erEyE{ZBWsvZEn}LsHL^eMlSoF zU!ARiR!M!8hRQf88#==j@@=a%^w@HhXxq#QP9f?!w;$J&Cn%;oS=`0fqEEQf$vFLr zx=~%Fr?;|uOX=*wQ;||eDi!6wVU5*6A!m@ePn)F{)rJ|x>_>hxwjtUntx!5Dt)<*Z zXNvjft;$9kZJv5oTWGF!3Q(81Be<2kLdmbJ6j$<1=`ZeFQcK^io>V96mkIBkp#Kui ziWvH%m%JU1<+j5<7MZ)XTB@Y|V_>_#pO@`}_DYkK!OC)}4NAqf@n2gFjeOb&wU^ex zT<1_!FK!_oFCSELDAz=fUkK>lLpsBG>`@!&UC9-93vKfwMPGietd@7<6I^;~q%#L< z6{mhus~hL7^!|6I1==L_QC2A_}4>o+Yh8m~En#G(0K?!ako(8z8prit| z{XuiyU9wtVrq)z7ttjc^7Nk$}1H_1uI`mPF;>`SND&AQJ`)H=_RgdT`tf;q|`6-N& zCPR<4mRUTRjrz;%wZ;!MuUZN^Xr)~*$iT(n%JOYxs?r%0Bo2H<$hr(`NKnlPN$u{W z`tiL)MoCs^rL#DQKSiF*MeCH?7mU?q%@W*Vup(gHrOYPRKWkV zHpOUSGrJ+V^ImPKisoX7mLbjB0$_?=cUjooQoMh0;s{Pg8`WEuhJx#Y2 zT(P9`ud)|TtPd9-lyHigqqPd^LA8ug*q-U9V&|Y_X`ix2iI>KrShl2p%c^dCRV%5( zwMu4NK=%o59$qX{q4LThF-BNVKXxaQIDNc2RxP2AAy3@-bV}i#m|l6Nyq5pP6FEe! z0d$WAzcWpV?iHo93Cn^QR0-J zVhiC7oyogF#^{HkkN;>@$OX3wy@a12%E}GpoScAPaV@AD&Q9}))>HkUI{FaH@SZcN zQGKbqa$UjlY2ejwemDCB=y7Saxt76vWw!`oxhlA;$Mtc7B?&K33hVp3o~=%Rr^}2_dP9a$hMRe?!070l^LX z6P#EdwY;{?_}k799AYn{s5DDy1)P^4v|^rnM=i;?qz+a;Xcx>&PG3so6YvswoAO5< zDemWU(SzM)q@BK69j#8%!&W|TFI_?S3<|PGX(9K46T1PL-_~5GO;*!uH4M={IPsj7q#1HZ;@GhUBnyINh&R6q`HbgblY{phAwZE5nE;N)nDE*Yf(tflCs@d5- zZw%1ptL-#ovQDqy3yZK0tXNvH#X3S3X1`b4dZDZ8S+#(kl9Y7+P)+zGya?7(srm-v=_!*TL^lx`_MC~ zk&;r$Dcu&PGyi#;tg1$C?Uai3$L2d{2^HlQ;|}s_WuKf@oXr=d7XrHv((kB_`a&12 zIPW!GRyZx*lUplsayjg>dxG9hKl6}wKy9q;Fk0F>`~qxG(Bm>ndZjHW$Y&;>uUp%V z#@b@_vv%2>?a&mw83X5+|5lF4wZ+H$Ec&Z^p6t}IW~oy_r80Yy>0`iHXXJWHX}LSF zyB{2Mj+h^`d+GpfhLL2o^*J^jDj;!6L8Tk8dn`NEFK*v9hH7g86=%#2&XJ%c*BHxk zCFPX7Qly2sbOvzu4|G}6)xr8klHk^(NAtDCD{@06yF3@4=gLyYoEK(h{TbA%v+>l* z;a_7C0h_$iMOg&ir46h4L+yvgRPBO#O>1YSaTW!sIE4SePM^r{#9G1yx|cT$9F(p8 zRLAH&$Y7UHt@#ws;Ct=E?alJ(#$g9hxk%1rECJXxtT2m zjFrVauW9O4VE0${px`1ns2x%<<+^-T94KUGj(9t)8b(PCYqRuv-~>-oMfrbmaXGIt zOn!k~?lRR462xSvW=1WivBp~I5xTu_U%VhAQ1ticDO(}%?UCkN?X7xPzAEiA zPcAKW(m|x99@h41Z;hOGJ^vo_TM(pOa=hFW{Lw3BqrccrHV$YhG)~`Yj&n{10>|NZ z5>m#<{l&CGJYCYOY{ePPG)~*5A0W$Ik#5hw!j0teptf!BZEh|lxj9K6J-0SOYhs+R z!hUaNxbPf&TvT2pjYmz{NRVU~H4kd8nh4DM+O8JdV^5&jfX#UMyx2)7$sG08TH}nq z+6TxvUuA2e4vW=&^ZvcCD2bF$P-?AqnLaPKxj4l*&5kI(p7_ z3$W9{{G_SsaV-KT#s@3e1*o?aQDPKL93l*6V*GPfC!@9YPA#XWCJDfbpSZoanp{#D zFIN;t@b9T5?j=~`g_>IX1t-?fdq}4ccEB3dl^;?LT!lLp6m!O#zIIbB3(VWYUg^th z9n?q4tQ1!2N{PZxW{jW4UTE~uK7mV{X^wI-QiHkNSb`2(FL!`^(2J(LuysdIQn7YR zzd>%gUFbV}V{xKfOerdN#l5(c)F}skZKZ#MHKrK3?KpoklPpw}G})CqN-a@ocAeiF zeDzB0x%yQbWR`cXg7bI@j8#|JE_W71(6Q!TaZ5C+X=${@z`Tdu9P}7Iqu5n0thA9A z;RRd=DxE8neELtwYpvkK61{(zQ-UWZ$T^fX(jK%F`uK|7!aS!PS1)UA%!Ou0 zx>zf`5==c|oVZIaplp@4qetw(Ad@rGG_`Ng$3@Lu_BVe3I|B8Rp2$rPSysMFweWNzbF|3@ zW%uy+FiV6)V64Y-Ua0^o#y;`4*cr?R+Fx2$eUn+w*&d|h-lNIVM|q>%ReZ$Xr)PLQ ztO~|Jt*CZMzeLu!7S)*l0{H(dCrPvLHLekr)g4FX>KV0#+EU|MEzUDp($LtbgU%tB7bU_ZMRjg(%?+vEje79l6|%G(L}pQ_c=*6UqJQ}+oa z@Y8^?99faqV~5*9b#-@=U3zM5q&Cu6Wo`3vGOdJF;$-=?yg=FtQQ<@jLiQVLn@^$GgddzMOrZ{^{q{nJ!G~bwOUk0r#hpI@ApcCUl>cZkKhC`x$I=1rh>=%Ev1Eo9TvCpgJ|7NAiwmMS}pCN&RSKyh4gm5 zqBvHz4my?%P_b9LWfTSGO~Hl{mY&F_Tu35<#mw`Y*uRX8+AB2%P%+85 zAGF}g;7`Dcv*cmoFMc*%z$**PTTIKWZH5!mAcGI%^N0)OOo}D-$A`IU)Lq9n8|glH zmvM$_74uIpk0HB0mvh1Nrx5s2)1L#crP&Hx@mlL)=5W>qVeTi2moh3RffXwX59qPp zEGv_dLL-n&`;&g|FRBiojN3z(mX=RqieC6+czCX<>7meFE}vQ`B7gCEt?;>4mVLDGq*g zwJ`&{3!}d^lbq7jKyXl*cBm6k5&= zTFFqeor`8zf1(c5b{nbec77rgLHVV(&>@4R;^-T*$RBM#Hb!ah)t}m8VE0EsAz*i1 z%BF0T=ZNWq6ObS>TWO7gpfPjw!{mUQi=NDf#gVW^A$bIT#7(3U9TvEP(OPJA;KXwK z^O*(0FGy_Zlnv5c)SZnDe%b{fr8{bZ*2L88Zoyaf3YsdJ@-_La*g(j{?DbY#L%
H>W|>Ev#s((<=)PFa`J$$K%)uchLED>mwdwZU3_W1r=ERhYuUR`I<2pWI!V4}DxN z_-(g@6U(9f(CV1K?3%$Tb}?!Vd~{1bDwYC0{^K3CIv68>v1aN$Nf~!7<#1~O6$x@~ z(BoA6Dyj`k^mc$(?5edi=2(Zkj7$Y#y|_jGDo>IQqnm8);6J;S8Njz(tEDtw*x{fX zAa9rym2b;W#9=~D;EK~$d(cXx)z(>JIrFH;Txam3MU*bGg*)>ewa&dlj_WD3Mp|AY z&T8zvfr-W_ag6NA&m{-x?7E<%vl-Z3($;H+vBkdbr)DdnR?>6%jO+tf>}Ik7Dy|qS zG!wWY*}Uh}p_V}(yHa^&w46cQ2VC*ReNJ8gAC=VZ=-)^LeDx7X(Ea3CMV70;gegqD zbrQ@xfdBQ{5hJ7B%)iUL19m?qe~@c{chA7C@h98G%{yA6nqI#NzIr{(d4HhY(hqr% z+({IK+jJ|hIXI{ZT2*jRN6Ei#h|a`6hS{YdtH2c}xbajWw=QI*hT0aGFx|5}Z#<;* zNx-~+{%zeF{3CzD5dE3J>#)L3ur^bnIr=p&AYZ!tw$j9#*?8KN z@KIbV50(##hlI1>d0W}MX@dq$)zgsMPMB)P1uzNTCs&tk+@J4Dm+*>Ng+ODbY3Yp! z=y4o9h<|{Iv|3&vWx;*92xU9d$PIm@hV^u29XPRt%wS=LSW&JbpAugSK6A?7VfQjS z>zlOxx@VqrE(DL+WhhP>BqNyqtmTh^9?!967~i#fz^g8K=~kfQ_%ZljX}`QdN`)_0kft@}CIUogAOAbU~`vAWQaN$pp&o0|3Y zlUm5QLz=kVKr4OlSk2_wQazZvf1nP!CaGxr(jxkHK;~h$M+gNRkGr(x8 zjPX_@Zx+3ne~9l$ZR9r66%@<;3hp=!$#?yr#^@!@_4Z(YHZx6_FBX%l$%n;<0>_^9 z57-0EdirK|nm*TBb zFL{}ij0$swseriVkdy9pa8UX1AgQ)IQ%a3zaBHbm?hBIE zc&6pke;YrnH(nlw7C7;bR8#&g<^=`G9TauGn^W~=+8F(znZd~rbYSzMPhwHo1ytmK z**@GazUxVjPM-tVe+BAKVx!2z1KZ2CrQ+zEYO4GyE8f6X3jw$z_#uk}U* zejglo>~>U8DlH$8&fxj{G`hOC-uhx>(VqZU^t58U!gLXSAYLmqkc&wB5c~oIp4Utx zis5Rl^h{<)cv7-}sRGrEk+aLk#Ww#F;h zx(bYyQ~D^Sl3t)bTs!I?x1cq`kac+0XV$bO|1ahxFxD#Ro-{%HD%59-2i2VrNz}vo zF5NWyJ7t5s>{nrf_*pt2{lTC3T%cps?Xu>0y_nwG_(T@D$Eo?;Rg_Cg2kt+Lc5wTt zO>S0etdUJ0q1QAE+oqSB@%SHjy`)L!!~`KfTQ=zI1ZKR>>sNJ=^ml3mRasj&Abyfo zNH`stH(&a|v2-oMae zTk$y9=|wSu`m#%dt4;%A=*9J4x^8B1LO}uciqIKqbyDI*S*QT}7-}yxSHS44vGJ4C zbt?n%;^90@sj<`mr{S~G>AlI;4WpypNyTXZH5nI9Z6G0NXNctLG z^t<|A^N>B>Uko~S7Y~uPNfEJ<@PN_$d`=32jh=c-V*$D7EDnaTuZ3n}R|!jLD>lL7_@{JAe}%oD?I#vE$h zhUx!MZaSJIwvcX$n^9lx4#j&Lty1O({k=ZJY-u0xh5`OF;TK{Bse0;_Sj^=7diJZ)P*r>f?;tB$t~U+=dBKUvY|*UrL8R zb1mpO-Uq9oc~8HuS290Yt-NCNNA4B6DAtggh+%YzjRswSH!b5M{15F(I%lC@gW1T} z!FR;F;$Up?JE58*oSo#IQQr7tq_@hr8-x7pB;h^&E-n%uAf1~>|L_Lb=gbyHC8M|b z&?@T%)C_JcS|C<|H86U`<^%lqw9=Z$SZoX>rJRHQREF@A@f-1-*cGqjGcsfSSm5T> zMj@l1IorDAR-#U^U!a;_#L{AGG?e>Hwe(8aH_h?JG9%IKWN-FL&~v%As0Mt?3wV?; zj!jE#b~joR;S&#xnWTm@(^r^&yoOqfcL2g0qfFiks(*RpuhAjk_ec#7yHS zp$s?=%EY%}G6uxyVNEel8AHv%u+Moxge@Qp#FNFM;w*HN>q`^whFy%@G7cNJ%q(_u z?;z!{7=^{4J&Zk2MJ7Cp$qOWW)SD1yId2f<$Jppi8rnr0WjVsFD>1jtBzvCu3}HzP^ins z2hH4Z)@Jj*QPgCuk51lTF>{1BQAx1`pel)bM=$d0I;Tkm^M~=u{AGP`J5tBlvBDJG zUc7-_p$K=08sts2WwOJ#Zge?JPtgZCP52vM!CTQ-elyb{XyksdQjluqM{~DT z(5)0qVtVj1(ONtSmqfR@lk^7vxKq)(VU96tl9qN|uMO3PEhJ>e*Ku(?NqEhkpeDgi z7m)7e5c3hq?PTx=&{MeoggKZOf1#5+#XbqD^I z691Au<==PmSYyp%W|-*KT{jigoGmR>#7LZtUkjx>O4D37>pZ>pRj-maQ{(ry(xB8LYq49z-L?aYS0U~vgj&KC6>emKtE0f!`;)? zRrvQ|W?JhyxQc;H2mUlt#6&zD<>u2cNBucY3TwKlm~TzP`p+#u4PZ+M>9B`);7>v( z&ZX{nH|?>cp4rTtLmt@Ay_|Ge?uIZLf5QUoqXPRoc;M!@Gm+fpX0xt!#d+>$XR`6V z&^|b^#i$ZrmNEUOPGjq`S;~BAzOgR5uLFEZJzU z^=vA{%@?}Ed1S-cgh6a+aHARQFiC9=HYsZiFy4PO!*@p~a1{SQbNDVy?m%&yS_@6l zJYdeU+Ph1Gr_57c#GH5%S43yIiNNidoTsF%naZp|Cfhx{Bh(Lew$K&Fiq&ytVLIEC z^4%PO|ElIt^9kwT%=K^3x41a88DGS^QEynId{Ek*YMnI8nqSTJR&{quaEn>RZ$`&4 zysZ%J;x^Fj{k~3iYl~Ue>_{fqMZIR!e71_f;R84a9w=OAS5t$%ZT2+M%N%ANCK;SU zes#JacV1|RH{+w|44=SU2)4MKU5(5!HFLVfyJ>^^OjWK$ogtl<^0D0$}w$>YS zoY|68w12uGssQ_d-;MspF?gns#NMTDds&}!w#QqHK zxVh~%WR!Wy>}#!$PGPtDMLWi%bmqZvxcXJx+lj)A@;LI4*#X3oW>=bX|YAQ_JGXRdXvjYtQpW0#~T~0ThRKp(x*( z9Ysy^9@-DcOH(23t>w;Ge>L5U+alz_V{tFklwZY+4Mw__Rf0@1(~(P7QMY)|jj6=9 zKwWV&oDQAhPSE@PXU-@KlSAeR($_BJ<)?D6kNKUbC4P-+3J=()RJ@nNDGHx3&4N}7 zC#|1{wxA17p`5rVisMf)kAmOsX!`;Y$w(5l+PV#ba?CUCqwoj4KnH|D+)R3#Z#rA8 zqU4u(gY2_gc=f1q?0J3&{0|2ZElgq8P%Ayn4nZycn6<1GPD{TrAn$MCJt_?SQH@{9 zEC}|v4}~sV5A5E>>E_R; zCv&5-f%i~;l_tnq2tcfo4+nZ=Sq z=4@ar!wLM-OkI8hdVtU2fe7CE$z1b~I#sMgW-GG{$zmtFwWv-kAP&WYf=m`n_BD0D z`_FzvE}4%_+A8Pd^y|>oxYdG&8sUG?Fn$MfDR}Evv!{^9W_9uws#!9q#JF4_#9$WR z6}E6U>3#km=Z008+%b=n^?-^JR3Y{#XiPX8v$5p-)rdJHl-427TM7+-tDLLs(?9EAo%Xq1peOu z`q0n%?fmdlFh4mFwZh|Y3G|iA!YBdbPPd4;#B59Q+K*iaJlKAI1?rC_yjV!d*_7=y zaFBJ{Ja2v>4tSS$)JwLW@DSC(8_>UeS+)d~%bN%2zG410N5C4V{Nwa0?y-;_SH=ZV zHoiC02prT$s}{Ltb|t*M&AkzvW$N>tVU0}qkkFkQLHGAJI8Cj<{9=9}&+R$ha%u@n z3yYwdw@|=$Vkb}wy`MH?!QXb0QPyejF5T#=+!Wzo6ap__bE|#}Ll$ z3;3_bya4Bs8vjOng@wSpccI4)TFnRn?p|)!^a@ky+1-3MlmphtEo@_-P+x#~!Z^22{%o&-^MmVFS|q#gNc6?emd$eib%0&WcSRyf1`p!P7N zVPc?~`|Qso6&VDmh;_gCkLYRKI^ig~hHeUbxvzBAV1%2_UQUXTmt=u0d(VRHOkv&? zZlbX$t?-4-OPBJuIH#@Qq#UVlRdFtO8>t@bCcY3lha#v4AAvp|?YVYwt1DSTE?9}q z8GkH|xiZ2qv%nqeadX1ey))c7373+z!7<%juD+ztjJm9=+l`XRydEW;5Huz5$}wh-x@%Uk_z@pw_M=U zYq`0?GPE4^6Pj}u=>)%&E7>PW50cG#WY_nKQ3*^OkI^&K34P^vuy3ibKi`=J-*N<* z4c;ZjSE-Y1BHs+fqkO0%e~*!<`rcDJv(<=9Ckc>ZK7i*P1lZh$4x^pIaW2H95BdN% z?-w$V+vcES$y9=#HNat)a#!3wvty`OmIF4ElI;f@RXftsqq z4|EcZ5UO&6=^6ebXPwoSq$eN9TYG~ykSfkz=7%E&k3moPHta-dnP=Fkt(>GP`N!Jt z4Dm7LCK|&d>7rnsO0Not`-FYq3p6vCZYOp)_66i7N zf-dvT*}2p{&||oDlQbo(tazs@FfZb2!kPbzTp>TdoLLv#c8A-yNoKMb_^6ND3D`ZJ zdn*uh9gP)QbL;3`{wL>v)dgzxn>@BhdT`@7dx>v^Y_t@;%C80E$_QD6Qg z^E6Q1CH4)XngfYum3E~7F(GN59#*OrO{RK&rVH%E{8KJ0jE zuU8V*s6;wJAMbPi_8ZV9TT$2qoo)$*LG#ZBuicUMTd37o5?I~bLO~|x5_bqR<}2DG zOoW_I{Oph`2g8ZoBeU&PUYLqPb}fYjyc3lNJ^n+5{K>%XIRXE*;lvWX&(v8qg)jpp zp_*t4uQMsBLf#SEhc#N02jDjUgOoCl+b-NipV2+xDwmnb6SM@*n@uEg0Q&fu`yx2S z6y&p^boe}KBBX`1)d;k5h?NhzkhgSutG9`o%_j3>Q3NkU5Bc(JPim_7-2Mf~%SA?8 z7o3UyaJn%!L%59s;O4yicxDiAMJ;h4c>?hegM0QI_f#L2j8Ix>0!-xAUV+$xB1zjGSOeEQyXW*bduuX(ls3Bg2;`w}R3>D)| zx7U;7CL{#wsq+f*@h0w@P!5;FX%XVfG1-Cw?kTGPIcPQ}>Fi5xJmlkPd=JzFXNE+V znk!3}^cy?rtq+jb9+7YMF7E~v&z2M(p$>Qt=vZC0AXU`c1sb%+q{uMqjI-T8PcH;M z%7RPb!YCI^<7x!W-A`6iasfOqwvV`*gLO(P0*S%L?(m;{Uk?;sC8xE1Zbu#aNk3i7(?gAz;tNhlvKgp-A{ z+z&uSvUBwRtGSSjw?z+8so8`4G*lnIM6CpswWvQ{X(xlFn+c{zayZCm>8ET{@a{G7 zM05arbt)=@H_F}$PB1rF1`g_(pGcqL?g(LA5l7H_t_V{!r~*!~A&EDqkm~jYV8wW* zH(wv+frLFxD8LniS=u6}r6rIgQzgmvA#XP%AxYQ|9W)X8xFOq)YU^FH6NwASbc}Te zrY3Xfj@)eFC3K;Pe69~OI+*2VfsE~d#$>h^x^shZ3=jT@!q-5Lka7GK+v%p;43urKh1B&PNr6aj%5eNDoQ3>7oA?fiaEJXp!uJXBV^=fGE0K9ZVzzx zsmV%`(H;YuUyZrO-535t2f;zL=C;x&LB}3|j#VHWIH;~(6)GJ&gU^m0p<(D1-xua} zzr0#b3veF2$b4&~Q_0Uxe`C`KaFY^B1U=pf%=nsBjmj{>Dp3v4EyRDa&f*WLc7QRDM^n#xWi7wXv+nH=lB2&mhYZ`cWg+2?o zX$Wil5%Tg^n8YBz_rlI(H6iCoKD(hy1)u2g+;Cwsnt^5rQ@AH|BpB=#16R<8VC$@1 z#`_cO1m;Z;HUfA5;!m?Yo!?*YoUoRI6P#iVcRcSEwSYa&mqw?NDwN^BGx?}CUet#7 z^N~vg*+t!7{u#O+*Gw3Krl3W_N-j#52{yV-?8o3}Q(1d#Te0MhcLqU&A3lq^=@a}K9wlGyc18K`39YG1Z z+t=Ok-~``r`-Kf?CMqk4kmz>$QRfM)(E~J}cjkG+DR^4L=K#Gf1@{~fSj|aKK=&M{yq}Z)!Tu!-M-NdZ zR0UGw+aR5{64;~^Bq7mm>t+a8<`8#4I0xTiCGgQZ`m2Br8tot{=JQj1ixnmeidWa==h2>(C_m4uxCmI+a1VcPZy{Lx65%%pTb z1G_JP3~~`1)Ok3u5R)!w?4|?L*Vv+llNHIs)I(RXwJ^%1gjRp_q%WoM2R2j4S|71-OnCDai14L<;>s4F_l z*JJxq!@Xa2HsFd1WTo}WneUIM>u`g?2_~Xsf#wIm^k<`6)!q$WaRbQ-X=`CHn1L&N zgamX9ynA(S6ur=Y@9cnD<$)jkeXvJ(y{IPaNgnzOje`^G$S$D{d8wUZRxuI>8RU@D z)~`jU=IRKm&{IU9j~6ma!Ml%vOqdojUmAM|q{hO3f_iQm`y6RZegP`VdN!O`HJ(9dP!DADXIMlR z^p`jrtbfR0^0(E=alGHu8b}Z|AvKB!{$7H~3G8m#B_LxQB)*jm`uI5Al4}cGu@LPR z4se*M3w_+d{s4Srk|j2A4+SG&0CH7Wf?A@8kj&Pi`}psisC9@81rJsOa{eN!Ham$A zqiu)>f0U0M0x6{w;AS~FMV?xJoK1drT4TdPU8wf1|KsQy!z4|bV6kUyW7f89+n!n5 z_RgBOwr$(yt!>-Zbn%Ptp8L7)O}$l_85t2--HoiMh|~OIRzQP~hhFQWc9`}!&8_ei zJBKt0$pE%FmL71~O>VH_5x|O!dblk@6ss&B1O{S_MY0tX-8Va4Zw~GOt{z7nw{uD9 z3Vsc4Lzj@%fj?q8d*E}}C#o7S?H3sB2$RA8pdH~hR1awpQY=tF9H%jTchf+T;G)PH z!Jn$U_5L7>F1H0*g~SLsDW~&$B)7Y&9|Vs^9*sO4oUcRO1JawXmeE4mgzOK5%JVD{ zX=&r@h~T!!$B_k9YxB*mr+Gzaph!r=kUD|Z;ukH4+#IBm1vf^P44wdYf9S`uD{wSg zha?QSD);c%w1xYr{{+wDtTqID=ofYksfE0G7HArB6X^b!6@v$r7jDBg_!UW2adXo3 zp)o}4K(>%-A+3QG$yfvQ%B3oOa6x3I;2QPGZ1K(5Mlm?hE+loxeR+qMr(@vgg&?N8 zBgX}6>VNG>k_Cu$GtfNbeW0HF%UXc*)`k7C9wV*%vcbNt;?+2_X!Fu~g z=A@^(PpP_TmEk$WP$!Asb0TR~S=m3MHO?*rXr zWPjZOA4B4-4OhkOW31FyJGG6J!<`Wtx{o$wCO{W%%Lr^v^FVj&X)AH`~>J-!tN z{O1w0ZEJ9swEh%LDxw5ZqR%CPUvY(Ifoncnl?*-5iHykNy(2UXr|ftR+6)>Em>Fehn8-dIKiP6aXqCWvC}vhM~ScQsf%I1SzQo+<8+qGKPG>jP^7 z>*RAjlTyFQJW`8-xq~l)-Sk+?$PhM7BnTW2v<;+}dEtrIaIwKndIZ}Ci>kly>&}rx zJedp+Yz?FcEE3&V2w7o=Xcep*oD)2zRvGFi(aU^@9Dy}<%b)xxE#qIA5Pc|EKKK;f zHVh1^EbAb?%jJPKfy!`-zmrj}jv1#$1$zVws7E@Rn@!%ZpXebQ0;vQ0#RgUyGttdD zm+BUr8r-I4m`AP|UCYB|ufV3jIf+|qbf`~eGw3_PHo<4Xe7cz3=0jNtaaS%3bP6<= zBhZhxx>06>nilMfeO$(jqC2_4?u#se<$;`mOX4_d3C{bkuBrwFM+KLwG3L0-MaS?u zvO{2D;F8QK0&IrQXG`ig!A`+P!4x{RUF<1KfYY2G=n!ZuC-MCBoSOnxydpR#m{%Rs z?`%VIf}IgD1G57~0=Kb74-y^Sw5}Q*oEcoH2AEyo)%|!y**q{Ma8Z^OiP&sk&bGj5 zc0oL{>NIwm|4UiYJ=U1XtI~&V7IO1sa8fX*I;DTx24n|2h&84M3Iy(nvup@S zg)goMQ!|5`gF94rvjh9+$@9v3fvJHDvV=&=mibz?r;e`r20sUj=?r$1|4ieH?Q%e% zG0ti_FH4`g<>t1!9Go4@ug++Lsp=B+n3#bHfi&nrx7cWs)G6ItEe-An?p9sRW|xJw zz#2P!Q`=p~RTF~0G37{w?0yDEZ>4M>s2gZ6=kYT1y*pxFtBb(}!P4p^ zDyS5h&6bEDvNw6)rg+7skx)1Rlhpd)D)8>EW`PT#HF!K(I4}w^FDbIK3%-vXtW&9l z!SBJuI>dJOXE95eB0FM@4stzjP6Pg?`KxXQ=LL(YE&7m6Nk+2K;-MTE$P2Xj%XX4# zE|XcTwg;C6=c<~fx4T0!aw@X~`ULLCPNE)r>lfHHI=`A5d2BT!`v?p4 zlUMj+TEzb_N%aq`kz9=juD-+kxCyeNcc47bCIx>@Cb{}%lR6w+9UP<*n4)eS`N?je z#`*_7$XQ|>OG2*Ld%C)s61)l|SZ}_%26P;+EGq@V1GA;#*Jxj#!IstW@D@Q#Ia=E_ zU^k&+4f3W~AcL&Ulhea)jJd8Z1(yfI)B{AU7um&TiQjT?K+8?y5NivcOTs~29lRL) zR}Dl~B%<|D$EgBMP^Fd2pI1=x07!Ru2>ol$MiuBl^{yF$wke zAlRU~sBd6?HjK3s*JLNGaag=&D@Y}m$E;L0f_IR4EzE593cB($(7Q%pzbq;+9rj!7 zCS48L8(Zbm0bAZLqx*RknLUszP);u718J!LZOZFJY7h2tUhlLP9#mPeRu&6>EhOs z^K7ZOC=&<%!9Hq%yF9Re^m^rj=~OArZE_z@dvhkgVwGHS0bfYl`ocKP#>xkWsDpZ` zec|J?U%b7Hg*rba0{kx7;I^5VI=PCY?jiGPx~b#a_Glm+EJz6V)R&(72Bln(Y*YxPKb%73S~cwHGga6_IKsiA()qsBsY z4i#M;RJlxU*OAO)oyBY!64)S{iSDd6$>g$Nji@S}O0G|v+sKN%{EoOKwX7iT^ZRr< zYHY9WsXiheGj$U?(Vw9Qcri(ER?9eD{0~7dOsGDp-lmT`K<={% zVy8?P7%J0<$}A<&J;ZFrTU1pwG_~n{Q`(w4@l<}6`G6HOX;&X^r|4FQ`fznZFSKV+ zV;^}fspMsOP9))so^ppw0qiscSy9atblu2&)=DgpA~03f6C+q7oMvHjK_yp3Rcd_} zUP4luf?vmJ{*smCUH$+yHp3p#BNe{oTFuif>^Pj}A)a1-l5gZ^oMtH+74w1mz=CLM zr%Gy^%}rXe;$pP)ajJi>pNRaADc{N;GM4Pk`=B4Evh8&}>~xNLrqAG3L2edZbi+RO%MIc^J4B|s zxyGr)Dv^4RIYj8EbRLWc4 zS|$s;lP^VH=!5fai;03Yz6BSlG$z06P8PAD;(*K;I3oLt39K=COd_bM&%szKy54Q> zyF9ci|0MoOeC9)b;BV+c-yW>Ej=CPKtS0JkJI6o88tr6gfCOU6MwkP>cIQnzoj|<` zPE=7%QrCctWxas^dC;Y%iS4W}$?Vdb?dm;n^_N-(UXh3v<}ZNFg#tr>?vx$$L+nDV zaXVO8btbH0_$oFsZ-9w#X9>Q$SR9f(&>eN0gT41B?N>bmSW!W>&Nlu6^Nkj^Zh=qw+| z2-!-^WSvM=*U3a;jan*?K54GIKVUa2#AKPS5&Pz@wciw;HULfV=cV&!5f} z@c)c_1k9^U)A%^H8+uHBbzUVge{E(`o<$S&SY(dV~QfxNlZeihW$ zfq5y}YhFgakPGAl@tB<<3*APO7W}^=x>ST2;#QG^teY4r@5zobiO3DiGd7vIsPd|o zs-sR|Q~GM)qqoH_c}|9anJ=K>;MJS-C>5%fLB(veSN%^Yp`19YWpXjH;x*ap4w`I0 z-a_h`3Nztu1ewJeiou9!FPR&>q994&GMlS7?S`tR{)C7XqD4?++wnvS;ObsF#J98i zutowkUHz-4*q#0f?oZ^Dx8-JeTqNXw$Ypog6x6j4)9b3DsqDIv2xP@b`BDy%g_?lNK^n32 zqBh=Qmh2#wqZ77(J`SM9N~?@|A3EV9@|n#L3*~iLM1JJ~+~GT7joyQpj|O+CX1hR{ z_U6BkdHdxP5yr!5ZlB4H!cNPo<6!2$;aB8lL==|i<#gFk>|xu174u9+ph**zMK3Wk z+(~i<+I0wWw<{ z{R1tP%;%%2`C{PaMEOSau@oa(HuDW?zHWBvkNias_@wv>IuTlS4ybKlJv zy;u!Une`~sz;z;RnGl(AmT6HzHCPo=!u2s(^dz-b4F@x)J_>ztArypOF2mnkWU_eI}Ojd-T44YV(;ydZ~J<-sw@cmye*8`CQRV zwvv-YB3=&MrLn!EpP|CC>Tna^B_=W1A?&mxct?IQhs`G|+)Fb^?@|X;Pkr0$1b3Ok zGK;jbi~J%E^APwI(Oh#=80_V`x}w|Jvc5b`&WDI9vMH*x2=79>_~G`u{;F1}1Uip- zWk33dbUeQz+RNOs0<_dwa@7UEJdUaD=yO}lAUBY7V^rXNv=rcZX<1%Uzzs1K^lEhi zx-*}R<73g!&`L#RBe_eo<@4!ezuG1;3fOK`471dfj()$wjxPE}VN~Upp!lfsP*hT(Rbd{MAu|aGK znE=k)24}TH_0x~d5qFtvVcA7)IRNZ`9e+<>`=7S5NuZ~zyXv;?WW#(*nhz1HB^$_P zA~U>%ioP}U*aI~K46A~X&cM;w&EFw!a?5;TCEG?;y4z-m-l7(x+nzG>fV`7gVv!Mf z^HUt-QE^)=BP9hsz8y0o=GQq`TXWteCGh zs}ZQNTka{j!s>`ZvbTIM=JE6N5_)AiBlQIJuX+f_ub&@CYw?|;iwu)fL<+cA>3tb{ z82cE3h*dN(TnhZ$>QQv;u`)ln%Vai({DVH$1ZOp0HPZjW+uTbwu~H%*I6*)j=3nS9 zuWbp#^>B4W9nkgBj~mm1d@0uGEjNn_yfrj<8+%{>t9q)RD!X}Ozj%B$oBx4stS0M- z{p=Xo?QWU&K!ULK_8D|Swt~8MJAPRP-AJ}b2T(^^&lYDJe?M(Sdb><<4_+H z}0Zrm<^9!dN`) z1E1rOQAHzGkCbr(O-`_|nQEw(_NR+Z|AH%amWw35-HIQl`~5x}#k>NpURN*S{;c$y z=wSXxOaX2#Lq86siF{tW8rdCzHCmfot|aQ%K;15sRnhaufV&TH>wy0=(9t{TFXoE7 z3$@i!G?Fu<7Q6XJ`q;k&Dta{ry7Q9mZ3h7rtMI)d0@=GvUTjevrl`5!7~v$1_b`jd4;OdH8Pq6r^Qr}>po>HkN? zALz$tpy>YJV@?3E3d{Q9Ci{&VO9qa0UadlnO*M^NIH|;*!g=Y1%zMKtv4&77i%o5K zGbhyoaQ7eX7ku8@K&+|?loZ_c>Gh*&0(9&20|OZaR0-8)+kh_y|oe+{8lzvJS8gRU;DjXu z54tO2aZf;+{n(JO=6pg-i$bq#f#e5JFX zJX^|8nOQ7iyU9`a*UZuvvBofc%dB!sp^p=a=-9_cv4NfDT$8#O5H<0F5`>P zn7pqj4hL$oD1hl$Yd_WsW7ImhDwWI+_;vT;kDf+_q`*E_v*SSbNUU)MEUtq-0pD>F z>56_VWPSNg9E8G7hRo}0O6VQnE?0C7o6F~>4tlHr;xS7UGToa0T{1x|kepZ&0chkV2cB+eNm(FXW`{?u~tB0N+CU=S!aHChDgCsN2 zfHyI8VsqFYfX;8jcZ(+A-QiHbe~5Lt?GEf?7Zk=^R8SpK0lXpz<`F~w;#F8P(#owc zEfJ3sYPAjp=6xsE(LoBxaCurp@B?&@|JUX+vGp2-kBym?b}4jz4OCG0e--qXB?9IZ zfr7Y$h!p^?7It|_CU%v75Up{VLWIE+8RB-D=6b6-tR`t?uDKggqw7zpa46}-+-lMkjdc2 z%_R%mCDTps#TtY4BeT~XBP&@3@QUv86|(y`D#+VrCOug38}$U-r2$;5tb7WxyN+Ba za>5g-@B0GtK4TwAbWIb@1u-dEhbrv^wpl=|hf}uCy)#qvDYaYmfa5pcEhi&bERj@p zMSnQL;~?|!%T~~1n~-^Dk$FXZ0UG7M?5-u(AuHO`K7JM|=m)YQzRqu+*c<*|IvSd< zE;v{*bi$kDqNC^_=h4wyBJ&2gUT|1{AS?b~L05TtmXj27BTXf}A5QWPWJL-ek3L`x zL@rrgt`@cV7&-&aOal0$n^ioW-kbog{y!_0in7>8d2yP31g@sB`}GrKcbHydnz_29 z6uZYiipuhnc*#q!iX;r&y&bUPB{X3MEBtqI9L{7)Fsv1#HT1z&f6nHIf;g^X>QJ-S z&hyh~1@z+r$nNUmHVe?C;1yT(A9S(idItQ8(j*ly@1`h$H9qjVV6;Qrbu&`m25Ror z$?RKq54f6MNaXG|(Vrg!u70&mO$~h)@kniU+6mD4xp{=h24AKz_)#|8*6L&d3QD$&de9HLjOb;+pA;RYfyA^SPbzUb$UdMgZlmH6S@JW znSQQP>9}T&?c{6IxV()>EEA!EV&XLBAcmJ8}Mi^ZOLbda(7%NJH(XR7XY6oQ(ygSllyt@d6vtLhqVkv1p-_d@h(cmy|h z_*Ri!{t(}JYc_!_b}!5#tnoyxgj04HU1}WT;wPTiF8+b5{@#b6gVfNM6@Eop54CN5 zMM@B{P$;E#B06r})c3<+zb=0ud8VK8?d5`&qIH)`Z%k!a+t^pMmq@To*Z294yH9> zhPbExB<8NGP=x`RQmlZ^f96uy)7oM}F$JE;R5yw=WViVfQ3T9nCpt(@QWG`S4-t#4 z-|I%uAc^QhWCcSnoq#UYpU(4VY!URaH}Lc7n$PGUYcLa9AEYVwhGgGzrRmSK%jtUFof;@WFEn=YtdV_NyjZG)q688Lkz zT49ZC^rcS$Cb&Rn)J=3{bi(ew41L8CiF4v#5gUGXIyf`SZ7E<{PQ4r|zL<+a&H=HS ziyLCD*bjw0nVfZb>CN zY3W(U#BOm&BoI$n3Gj;Jwhc5!F}+5wGdbOFzXhjROO*`QGE=2b*!sEJWz3vfU-rE#$$EiCHyuw@$Z#^1kIs-?~^g?1|oJ#lo!eP z1bWCDtT9?=(0x!rckMzjS_QY^oY*HU-^jj_%+UEMQRx{q`XT(&jN~)2qBmkbPpm?A zcR|m$=C;m`_xz4Y$_IG+z1c17n5tV2Y|f_7VZJ=y z^(2|uTs}v@Hw4eKv;gVs9-4hP&6+xy>1VTgO*XI~Sn&f<9on|be_7GTMBs^`=!Dkp z_amVX%88renE1gLvJWJo?`gAuvF6oh^hYxsenk{E7#;g5)|kPQL9fkr1dd)M-B8Cc zEp1$XoeT!^SdOSy5uto6J?G=VgPo5G8V#lN1QWQX_;Id;Vz1aPKJk^nqr83^xMDXv zaTs&L<}M|9NNe+k;)v*vZkvI1gu|K!j$S?8Ob1LQ`^{~_X>R3X#ZFNi9@SF%!sl@N z&3@F!DBZ^VYx{WIm&S6z_(GzTvtHj18N5ii6> z!V|1)W5TiMq(e+Gd&^A*@=oKOP#?wNneU;5RB?CAUA)5>bo4_u9O`#7{L#r`q4>*> zv%IvQzh?0b70Arf`Ut$8pME(_#Ixcr3=nI0ZA{?4y4E&5aI=RNCXYP^tVqv>^A=(~ z;!%O0r6~}xkLHbTugB{q@Q-SENmsJR;Iu156yQ-+I>~>uvkZQ*68qS1vb&dlHtIN| z*dm6*YiJ8bD|~mj4P$jT9n|UUF5DT2$NKRmSfjqE%kR=0P`}oE1R71!wag+g+DI}D z%wx1zj5S^%^XB+Jb~*4rTpxkgmegJL!>C4XE&|#d%2I+bzBUg$Kb$iC*F)AI6@N{9tL}h}b~v zgWC~cn|Emmo>@#1HGl-3hU1n;P9X1SJrSyMpbhc+No_V85t|^Q3(iNwjmrs@a$673 zyY*!A39fn>dK6r7By#r>pT;z)0pGowS+9ra5BdkDl_|+t+|fuYri(Ce;v{S!IqxdL z(VK`ha+|jH6a3RMY#a~66DdVnzJ`AEHQWdDTKCu6^=Na)4u-e639jK#fjZ<{SsL2Q z-?9^Nnxph%_yA2@Br-2HkAWI(Bu4WhY#I6Pdf6&wrCtn2yqdl4#*l<8oEH_tz(zCj zgET%F2=_ASAg0a44Zf4*rZds=_nS{Z?%zOys_s4%-6M9N*A}@% zPgKxW64lSPgUn@pP?s~c?P)OD_cS{X5%o|XvG{(PmdwCu<}^q3Js{d?Tgg9x1|Q1P zi<06f-^2>hwLYzTY@(P$I;!~q-FX6vZXest^Mko`=jAcw%k1~th2||PthA|Ow}QKz zrLp)AUP%ObI%IcwD7t#Knz^Q*0?{_xy#6>T#Jcd*q5}5uFKZ1AQo;Q(39yFM*Wjuw z_QmLOwuM&)?k+^;{UJ^L1G~~Z(OZC;acv)TkX2we4%nMbbmU2Kd+xm(2<3TH?*(h< zX1}^|U{H;C2GK@%9-VK2C(_r&w`t7|eFZmW?%HOa&|z#5ctb030TC+%?G_s zJN*(}>bW08UqHu)18s&PVrxhU^1lPt*sY708uqMfL;lb*Jg#UgIHt$DX(lq+rMG#2 zdRO&Ka}D=IK9i1YBF?HjnBppymHy*7ZhZa0Ux;n~g8l#Sqv%~=b1m?W#ykO-`42Y+ zxp_lh1fmVH-`zwKi&f@PMLF@Dzha}IrCPgK$jw9gquyhb9RWr=iLK_vL=kvcLzy9A z{;}O?Ug>Q*rHO~LNEtGgZG~<-7EJIFFNu!d zn8#<+iE)$cVDk+7sA*atyW5giG&5Jo?>9UOR7@VS2IyW5x%o=ZGP}_U&p~a~=dndz zF!$Z8A-YsW7XsdJM<+FpO%k`#XF$Zp;Vh`=0M_u3H21&kEtA@O*9}cYI};x5So$wp z&ILGHbpDX`M2#)8D@~Ax;bEi zjaUo*nm^+Wcscfx^zyMW=O|-hn;xbKFfS$<2R^!xYkrMK<#%XrqTF)3%A_>O%rV1(5qDhY_Z| zsf9I?lKJ!wThF6`xkSSnoya>k!_G8OO&qftoH!xY;4Ce_$1R`1H?T1J$ai-oZ41Lp zMH8^Oa8_|?7z^Wf_+?(2XJ=PPIGmYJrm9JA#+e>=w+qES4kM<|_zM1%ZKoB<6SoL? zliL(BSIlEu(C@%LV)4CLr4t{8`Z(h|x-zzp$zVE|47Qrv1lG5-}YdtN~^eJrUDB$nPd5)K& zp&x*oU-(jfnvJCiF{5Z}N1ISn3TPARBD^6DP-7qQUtM04eI^}z6x=(=hs?rfi0mqt z4sUs$ZA0!}M0VexO~^wx1?(o4Nouy3<6sTzfky@SQ#`STuV>YP-C-`3t!)%Kr8D`E z-5Kc+)(7?R1#6UMH%LwY+djuh#xa9UYbd1*WD&iH3i`E+X^Fk|A^oE5`fsB4F;lz`;JdJ2skK zgTGMHW_Qi7)0=cJtI9ib&hxN%^qODj=Hk3pnp>v7jc^B{^W(D5tQl|2AF_A!02zX} zXl&1!Md+0K?PV8*)Tix`y?v1r%h*bAmpDMIM0SChV`AGN+=xD8C*8xE@Ugrg?*%UD z{9(7m-ZNv(DpS-}bkqG~5{Gd`g5zD2mEqfv zH8HuPn@J@fX@8j(z>=+IF{a=Z$r>o32!0SdS;zXqYuoOI+NGwf>1?9elx{fUk%1-S zn^9qD!8=})zTjY~ZGTh8tbxke;zCFhbdaiiKJS4F+e};GEuUI$r<;W)iKVWHA5ON> zHLM`sQL!Q{GZf!xch07^kBm1nZ9g}`??ZO?WAB+|OV~5|k=*s?+$Y=8hT1arqFvzT z`z>TGZNV?zpO+Rwl|;63!BQ11s{FJ#_~NpoDT*+df>;pM)n_^ z<`7fdwsogarR`Zcet<9Ij7Mh&NqJA;Hn%d>%^owwUc&5aDj3ud;MFvKi*2Bl$y>L> zUNjAG;;%6$$?xYAe9Hpc1Y~W+$FaKT`Ey-Y^q886SUw={QqO2fmJTuB%rgRcUy=#F zl*^B|2t)Rc1o8?JMhD}(*YMu_0y{$6llcCNbtb}0G$HmI?%(x9=AB^S$h<txj2W>)o8%Qw^{rZ@HOMU`x3iDk2 zAbUX%lhM8%c-(U!Vsv}j-f?kBNm`aMUY}>;b8%L^p-hvy!gjCOVN%;jo7C4LgXkcZ ziihDOy0dVapHTl6IkEk}I>xsp_#tEkozAl3Jp;Td%kf`k9kt$UFc-1Y#txt2B@gI+ zyhUaHj3t0R*o~MjuwQ|yugyZc#GUcKiDZ{p5uOK_caiQO1AQwu0(o=MB*1$<0P{#q zaTlKzMU@U?3u$*!+!uB2?0fUll(%tQYG0DHMsEILnfWhP4&5|9nBGg98I}6ijI#Za z-|NAfm*KRM@HOlvJw(O>|EJkVpiLJ06uwIqQk@oOk*p-wEQ~dwiO5TL%f_@v%tO=B zHg%KyEfPs@0Zp3mM@-XGsKN@alwE?DJ~kV1ni)thI+axcXK2P(v+0PKa>wmU8QSZ%XC?O&ahqK=+}nElmn9;edUM4z$`-wZ+{Ke+Lo$4c}xS*2n@^ zWsz@+4zdh=@UWR^H@RQF77(ieP;&ym&5qK3Bnm!haR$A(rnzixVj?t=yrf^zrC#DK zR38%u!{Q%nil#O=kcw8D7H zEqo4_oIo=+^~vD__W_SPV%8wEtpLTo7we&W=zf; zvSt}_A{RWxX+Vlf=rHZfJ~PB_24`*o9QglPj^@|c0ooRh$ThH|W_XMDxDA%qFCfpL zAN~QVHsFJhcc1-yFtCj{sWB!e`r!ZbE*GkN8S?7?dH0M&_&hF$oq)fw4~VxKy|xXV zz*>Q?j^OtZuW+n!1xV5hxQr`{;BX7bQ+kiJ=M!<3l~^bo^Kou8`pbM&@hCgR?e{<6 zukHn|W`sZWke(*<{Tz1!y@T4K_J-XF?R%S?fCl-&ezIZg7`;o5`^7-C{5FV_*nxQM z@z2ONx*Oa)Bj3ed(;H;A@9GBI8)iAMWTsu?KKe|w3QL1--5phWo6Z7KL|Tips%6fg zNiU7(1fwTihr84ZVK)=^o6i-?0>`ZW31nj1K96ctj4m%(&x zJWw~C?S;QF7bh_TeqR`!jJG(>SMqP{0i8-R`RDeVX^qo93nlg4H6femY1GFtaLprZ z4t%ob=*FkP>)V^(=BF(OUj7l?cqRTu6W~=P@N!HBk=>);=u#)=>w zAF)PDOpK=cbdJICiwiWV495G-ccbf}z`ye|JO*wmjszy|vxiJBlhJH6TP*j3(McUU z#s6T9ooo;d`hQ##+XHx25y%`HT~5;atRD2kTlAm`n350m@!St&MSR3G3>6fMM1T>` z<)8U3=%o9!3$kLP-EHzh9~{G3CG@l4IugDcIw*qAXU*Wn4Trxp#$+?iO+uU3&GejB zM1!xkitmmp0%(SO&(SI%m2$_8rdprI~CeyTe}LE#6=!6%o;=bPwt0tGgCpaa+wF zcoFxZz{{b-B!qU(&KIyHv;)Zsmp99Q{3(MqHj!^eX23tn&)e}hygA+?9=YLef-$cH zDmJvG(J2pujhlsL`(;M9|$Fml_Bt}Ps> zSM(CHyC)QQHWmdpLx+GB9D{>(&h*A?;1qV60nDQ#Doa}j9_Hd+K4ezbXE4%)}K zeccAVGA|-pjL!gfDT&NGYk!&vW|~Q4@omh0272WIbci859%kT4=mkFom3{&7*aL>u z6Z}Y%)J!5X+wvFeE!1&eUmP>+rFh~2-0WNMOxi)076DJJ4|On?)_?+eY?+;67C@uK zaOJSZWn_0#J^CRuXUvwv&w8C(?Rw4i2 zqmEy=OmNcvVI{zy>S6{s7Mjt)tzdQze9bI24f?|xAny&<4*8!Pw@6aMt(fg5!{r){ zHTs}}&Vc9T1|CJ=yx+4(Xsxck5VB&snQGpc1NOPgOuEnrAi*fCv6xMxjYtgeil}xm z>Ntaq=}P-)U?#hP-UE43UW*l=pZsR1=NIU;XH0wB9-Z(#iOxPE9xb7yJiS6D`X(+6 zxVZrF*lsUE9h9NXSR&pYRl1t3qCJ2|6n$(PkU5Rj;H#a<0y+~}(E3xg;&^;5Lh>A5z z;&#PFcNC|&5$N8=hJ#C9AzzT2)e!T?%+h<%K^|P1qb0KM(T^*W(R4iYd0Y4y9pEHnB(L2yj9tOEjnQh zyoF_1fJf*2L^lpPW*zv`G&|SbMjdCwS+#`UcbZ+M3oxN5h#LVXk=;@4eXHU9bfg2J zFj~OR0Eb7HGH?On+70OW<>72LhWB$8YO4gEFf1!N;WfY9Ew-P{X7dOtWg;-oqvyZE zTa?2Zcj!9O1#^-xd&TTDaqM02ymX{FvO7L+h#u4z9IPU;`;$#)_hF5a&}-F!?z`y{ zpki&X(d@|Xhkg@g7$0%kPcaV~;K7>yv0TABb_Vh`;(+yxuoo)9+= zqbHdLM?5~d*mIVfWk;8~2}dLq-t#Z+Zw!RrcM&&Dc0!G%0W*0`&ygj51e~1^d&i`- zS22|$Bppr79w6!gJ`m1-XZXZ9;c|Te?$)+RTn--&f9e0eO-AHycFbHuz|!7Ye3z4< zcAV|vCj0H=IGx7?oS*Gbjc3RjaNdM5AFgByWKA#d?!Kh5FNV|n z4&1E+pDaB*tZuX}`wm~_8OsBQHZi)?4_gvm^IxD%2RKxl;ag5)F@cGP*mwGz9EOVD zhSPjzQrY8}F5LHmMrB8_Ml9TxTnSa#9A5Mg=+ZZ)f|V}1&r2H8%IMgs_)As^Hwv=g z29pH-zr`9oZFR8C8Su#>z_Ah`=7RkqSN#rm*rr6+k8iiZ**WY#kiT>nFf9gH!)_>y zzP=6e-+%`ev_C99U4^VD#GXLi{$h>bx>O*kaW|+2aPu2_X>r#8^PVZle~K!;1}+&F z-2JS(4j1f!;TAU;-D=!bc?gY>6j-tZzWp&W4?Taj6^MFPxL^;R$9=G5>>AbxvQBIy zZB0t~0+@3A2IFl7O;`|^HrBXsxa0$T;xeeAD0ZP83Qv6>YHS{hhBelK zHQxtT%!2YvfeOfj%)@6c&_SZGQ!ELlRz28EI*`=&)!>>0tTv6{cV`Axw1Q{;oW+I5 zm75iTUZdcOwJfIC7WZ4>h_8jeIufin1a96(xYft}Mt2^0qfLH$9h3Nna5OY}3`Y(2 zfnzuhb==xbwTa=CHML1yN?!yWBoDicC$3d0w!Zfo7ytcG(4cppeF22t~ii1HN zWZ&2cIJ6aMKHM%!4{T0kGulOPvpf6o;G<#eHvYz3tnnSL`@hKls&H{D+nZS95+)Qk zpl;u?d#nRnK&M0f4tHB@9-GXzLj}d~$w*F`kga9U*j}iZinIvH0DUkVh?E^M?|{r3 z0?k*8UBDBw*&F(cy!3b6Pn<$(c=vbh0dSWqU>-x+BX*0mX7lJ=ID(VhK3fWz(-ZrM z;uDb!G=wcj?(JpiSp`~-6o4cCkIe~hX1Q(WTKXRFK1;GA>>-=PUQtb6`)4lT+Sp9C zp1p?(I_OWqm+ZxE;ys$P#mMeiaNW;fr#WnIo5(rfQDPcN=inW-;jC)Ynxves3sz79 zC%zgp@TR^KX%CLI3)wjyF=z19f4F$Augzy0KnHDhn~`~I5V4c&5FFf9bUj(_*SQBa z%oejlYzikmzB+(@qGQ=vwv}bX8VyNR%-6Qr>b8PiV_Uk0z@vIVtaWgsCt{5dO5t;5 zbrWq_oc2?@5o>IL_gN3;a1i@gLAR0Ze!u%*yVy#0IPxlpS$&Y)r@h%>wt}Sr?`}bw z`CiZ%VYZIlg1oBgYmiDbHsXDh4MjYn(H=%MJIHzm0|VPE{wXFx&2Uo7 zST@#$4u>Z=-95D3v6Ia}v>ZMMNlU-OuiMUsv)?ogI(jZdYy&c@1CV*58{`L(&NLmH z&Nd;h_JaL9@=sg>aPu123w2lAIe(UHpmng1m8<}i>r^rY{rEY~p^jZ(i@Ib!IY~fY zW8!&$O@j_9N=uQ7z6BhlCU}cuwl5~w9WeV!f{D~N)&+?6m3;9(T}ni(y6u5U%we}3 z9b^Hmij!Ew%AiV@!gt)_-eZlbb~4x~h%~YPMQkUV1FxYxtp)DV7Wv-*r@6(3IsCE& zsep)0VLMrW=pao9{JQkmM|ITsGrIv3qm^K3WwFK>VW+*>6-)_V`S&g^?j@8(#FV|@F8XU^J8cCt*~zN1@#wZQ;S^uPEV3Lp zL2j4CCm;#wZ}6XUsIi3L#1+t`8oRaFM=iVC;`^3-M<~UYB>arulaCJBCwXFg29>iHRLVs9GRsbuW;9pCk#!@=!Lr7Hm4r^ROOtY{?Gz@nR+PLjl zqav`P8S1z`up$kr^dd6jE9y9a`(RlywP0%x0;sSvECQVP19|7) zxoEDpEr42og&W|z{4uhXHpM=V0!?Pq6=WeCz5B?DqR7p(j{6uS7X5(JJdH|E$7-U+ zs`_xZ&X&h%uCvXd2^*2xIL#G!Vl+C41o9e}%8fe=Ay`9?F#ZlH6YHUB* z;&+32v<12kM*e?-8~p>0UO!~sGIWqIOl!i>j}O?|K=);~4j642f=}wPS$JYJ`$Xe{ zyTn4ToQdpi0>qjRy*3>+hP!BpM$=uD z*SXHMu4~^9#o7ov?@1qJazE63G-2NP@_0e`m5A++u7jI~kaun-d$7--JDXpUWd+*BQ#nA<*>zMw5)6U#+7B&9>R-A+LuA%ZM978Scw`et3s#Dm5Z)EpJ9)(eTcoSgJ}j zVPkvxt2&IK!P7Wz2EOUyEb>s`nNeoCx~_<&sf4@SzFuh`OX0tYTb*w>9UDt+K4~A5 zAg^$Ims<-LO#j!?6`Anry;O5cr!7rn$QRD1d%2&~H7t&;R;q;C!ee;#bacI*udaw) zPTv}>cWd)wI&ZORStIQ=mGCW{=^@NsJN-?SFl(G6$Q~Z0!B(p3S<@=fN4{?)nu~3k zr7y?b?c$E~Q9;?UEUiC%bT;^%KDtrNYs!FM(-%kcspiRH18g)G^4(F%Y=yfUgn8|9DYdyj^y?Sn%h+Z(cJGw_ zlhghWb=VUy!5r$PUfP6kPB18<~!_HrrngS52J! zwvQUc&4PORoH61t*cqQ+jUD(c(Qa_m<&C4%|CD$}FgC1d7Yo&(55gsGAAKJEsnR}} z{)#!k-<0{^&Ov&@)#S8WyoK~w@Ob=m^f}gSmOfhs)kfFgNtD9KaFu^1d zeZra2boW=^H7UEFUY(cL0=m})E8UVxPw&sh%jwsv=%=0iZo)PT-J&X**3JaByP3!i z=x)MBlc;iBDJT_QsY_a_i}^jwuRh!?gQlj?y&t-V#UpgsWgu^%3|boQ4z{ZgS2^Xb zqt0C!edledeQHoU5&J-W=p7E$6TTVkg6{h16Vj_eUg4ln_;)yjH%F4U#r2)VoLtSc z%16_;;MIQdGeHkbFb!_J-GI9f$H$^(A~eVosAbEuf2$U7R}PHO`;kWrBLy1I5(IbFNC~KwB%4{ zV-D2aBROc!^|?H}*?pNdPC0r&#ec|a7uAg$s}DJ$dx?FV2(Q7bnNzBvt{ge+ymO+d z;>%%WYn+qT7_TnXuWiz=Ju3@`>Cw+dzu?s`sD?Q*C{h32oEbe27432JCvMr6#d*bW z#q)GUKX)DT;Jm_dZKq#JFZC^DGQ^o_(o4OKS)01ywTB!kwZ_i^vDMXLLKO`i5nRl{iwCN<9U*5iK57)#bqDb~>q(X5C(MQ9P_24xFVOgOsl$S+m^?VyCXPOwSAt>8^@dV zO!n97*Y-r$y0_mheT)v{H(c?fnaIO5!HBf{-Vo~Plw(x%z23Mp?tUz)8kY%*gnu|; zo5oi^K}BJ`R0}s^dZZ6BQ~y90KZG*Rk4eVj?vKK4!CrZMt6JHW8qKFC{8k=+AY!Ae zG@qH_U^?%k=zRL?IBTW*NhRW2gX_X?!uIyrS>IWJ`mY;zRr6oOXlEerX>;Jl7;Au4 z*4D)w3{JBF5+Hx0^!r>){exAzu=N9A!gbNA&U@-Efzg9L>r%>HY9gV&g~L{EF$& zj_^tvteAPlcKe)U9}A;N@i59fJ72w!R?5Um2i{|t8VmBWyd-vu`s3^@>J>5NR zVrD+n{4`zlc*6-^cUrPokTcxnF3Awg?6=mq<5WXudeAN=ncU2cUU34^C=A83zZ&#z zxYcdof1*nIgAw9UEIJ+Sj+dK>+-4QsOl}@Cft_e3a=o5!q{(Lc=x(!}e<{H_SoI-2 zdLi%hHW#s9biVJV|Dk{G7e8*|{hLbnT-tq5aU}RV&Mjiw(%(v-qdqv#j%S-!LpNso&g6n+16Zx-W;- z-0JUPUhxZG4(H8hA+JhsM|i?MhC{^{=0{m6+Qxh}z$#)IY=WBK87lf(#a+6XdBJLm zHlIB14;A;Aj-H9XVdH)_y3(t)a9&ThRgP0OgPgSVF+VDA8h6w_hN(fD!heHoLBY70 z`*nlZi16w}%Diis!#-YuypiF;V1sE~0X1fj8dMIio{kTCm#K;;_^poKJ$TYR-1kh1 zN2k9Ed489j`B8!Jh(2eS8niXc0C~mDM0Trk6DgA=W>l{PkB8Sn)pPdoCSKiPQc#Rm zd|8iDhR*xRnbE|s1g3Z*?ceZ0{qu|X=pPY(9V&X_ygT)TJLu78F!OeEfWl@w-OU{n zmwNB}6+GTH4NS{9* z+qA%|QG66C?$Eb3Nc%+tCxyeH(VK3z;R-W|{+Bj&Gr$_V~mC zll^^q+c8vHV50Jf9;I9OPOvyW6cwd^-nEZb`sYg~9`Cyy^n}Ug58<7ZdGGjrGr`$r ziWl66d0gLlW4PP&vwhm#X>-i;u67IXspzQwdzrJ!o!%iD8MHBBYLWJ^9;J6UBUm4w zh{{D9^kRE;mDk7TVmCS6@J{w|BrJtj2RSpn)=odNkJrrQ?>7hDC}JHj+Cto&J1Fmd z(Fs3au9j|hgY|`=DFxNkKC+u<%%+ibUd{2-iTtgLfGK*dzgKngco|VTRnu6tsKG)kmO9}qwETs)KI>ddjuj7kR z4d+givCRXx`%t`!uDF3GTBe;1>*#G?H}T7*R!*e}o}n6w1Zm-F?EXYrm9!1GyGT$C z@=p2rCnkdPop<)4kAC--TxWB#(at-6j5Df)+r?vbdX?xxw8uI5aGduPT*P5hz3s$! zE#!SBra!u=STVkZ23s3eh3{ zos*-J)BmO#y6e~O!AA+Y?=cZS7+i?2cRH|x_L*m%@vWKRdwP&-#k>Qh*u|;SC(f5| zvyZo_(Wa)nk%~SGS9H^B9S!dZ8@s>#9&}HK?o)1*JqCG&!u={p_cU+ghQHwMJ9MT8 z)UkO`v6K2AiH|avPdTwqds;>>e6nzkBBa_u+S~4dC}mwEe&?iPFtnr!K>Tpyz9&o4yDgx zqnydjW~#YUn1+|@IPYm14yFG0N5!f8IlBBVD*BbdPj21hgZ>I?P;S$RCq(Qj_s$kW z_lG#Ip_#~bv$Q7mG0qL|%ki!H{1be+)!ot`-2538v~sFc)4b#F@NrslnbUzv(O#T* z)Fka@e6-IgcMe%x4tL*blHAulsoXTyGBs!-KJx7@;mWYMPvpg`$DpFTN!g!%zKx9o zPQizH*WodFBZ=es+uS+W333o&M%rHPb4&on6I=(D?YvsFYLeFEGI<$h(aO zn-S)<)0=da{{%&8u(9S`+f>TCqOH+*6WG#ntTY>GboUCUHQA}n-#x*%?nKwb2fX#w z&1pz1yPU7JF$pMPVz@Ib?H%jM^igHl+$83&M)$=zgCl0N z8C8cYI`E6Zoo4?ZM0r%Y{qE$fHFa-hp1mx*-V=TcyX(ucODdI9s^2fgR%r*+Xl=#J%BtQR^LKI;zPwYXwJR2cV0&QUIy=M6TaT@_|F zCEgjRx>^-$DT7YOrQ9Sb5~b;v21R+}ujBFVSA4Duxe(5ye!946ayjZM>i_c9C*}Y- zgPnBsLAW>-4tIJ`)(!4$(SsQ2+Nimi$q18zFdR*rN8z@xg;~S-xSTmxF{`{WdRCTh z#uaym3!UivC9=0c-r_g|{?Ef(#iOZocUrK@+ko3ltdE8-hi8Iv!2nZrio-q{MPHgE z-yi&`KR)6UiH*fL?|wDrZacj(>MMg*;|kwzp@#hCpLasuDr)oT=oagA*5MtyNW>lr zH(SHe@RjiQpsKU@ucC+K>D4UlhKlmk=1bw>@Lx9Cs1Iq**XGE=Yn+Amb@O4ViC>{` zCJX2B);-RYHpaQkpbD~4GMXZT{#HR+v2h9d-jbz_ObQN19iyBwwMELty}=o*_>-uA z9X_Sw%M>(o0(YNP+!S>;CthlveRKGcRbG^7l}*I=#s%QNs8yDT-i^w~e>jV85^jOs zGvPbo)sXkLdfbi*yEdw;%kP2nj+=fS5wZPY54t;>+Y?Jv)2p12Jtc!?}uUSP2n7M|}=SL64*9F_u<6XS@eK>&n zDG~IEcSH@uD;sPaiQaMt{2zIIlrOe~jZEGDi0@U8%TVtHqQOzN_zR~T#lzWFan^qG zyL~zzy8Bal-ZYJ7m}g`NK8L(bo^T`_68Uhcn87NjkoZDoB%X1NDA1e4WC&PqjU0 z2U+d3z8cgIx=-ozzqg7VG{NfN3YBmcbZ3Ch?sWHO?jPMO66g48fm-@H4VKfq&iggd zUD0^w=-$6o^EW}wiEvC9xRd{e8%k~Q(N$4R^`Q^WJ4!(v6w_~@duNb6Xc8~r=_}dj zq;vfY@@|B@-$iVd`Y=jGzfsLEr0N%iyu0GlCYOnge`U~nCW6gW+tbnG`2VWtfoQ#R z^df8wu#dov%)Zc_SCv{6Rfo)M(EYpFXD3tj1bK0|F1+6j*8_3E_${h94a2|gbosbS zSYMZtAn!e$Yg?7@to2-qR}fag5dwx}&%;Z#EY7a?xvgl=Y?&dF^yRPfQGN!K>R;!tvOo zFn=tde*ShYbw6JJRyBND4XOhbzpE`J@nR>Z{eyAdSKi=U?v#2FCRi0@ahq!=+!TYp zS9GqQ(MMOvKHt*JM#1p4;5(IYwmNpF{P-AmALq?G!wGh=g(kQZv6DZ zw5N&323^W$o-jXr2zM7S+c_epWohf7GH42Ko)y#2t#Y!{{s~Un>tMx4u|c zyRLb|SFPd;n&3(|n3hpcldPZ|cHOQU%M{F0kEh9iMd4%K%`D~K!Wn9yiggS&?;aah z4i4JsCssK{mp>!;H*Sd)+rdSBb*^B%&ie^hhR?zGTKm0D#5Tvp)UjsKz0qT`v%fNBlyqa21X-Ro-tD&ti#?Q3qRnA z8DUMDIFHHwQTA)|)%Yk!{IL_!-zkqpcJg|7vpY9G#WkH`bcF4W(Q@ys{9@u)z|U66 zf$l1LGp8Ka({atPLI3Eew|?^mD|P8pObez#_nIKT&h&Kjd{oOi#!@D;MeK~(&8KX< zst+0!9FOa=(a9>?MoaPPVbi!={J6?GdW9FDViCrgE{AH{$4@vfcd*o)Yobp~vyaud zVvu_xLq(&ZE+;D+jn(7!e7!P!4Z2@aXd6_KH8&~Rswz7fVgszb&~ z@5THazpCaB50|jfRR%RN2fL1q|Kf4Pjj}w!a=LmVUrnV6R^!!vYW`rX*O;%e+ecIA z?f`i!XtezHaa26k;*pxLyNrz=>AV~4V<8()h9`rjwB+>|t2ti{+$MP_tsuhZ(G1srg)kY>hGP=O%u`KaP_t`)n@T- zo^HrrODU{<=JHokHS^TkZcgGKqw|Wn@7T#Ro)eEQ&SE}vWB5(Dn~Kr4tLUTSG5D_ z+K&k?Q|7l)_4DAjx%yDed4F!){DdqXhi%?)H(~=+j1iGHsL{EqZGUy_4IM%kGv31S z=k(DTT=4-b{ngT!RH+8k%|M@cma1+ZpYja>IoY4(GY^HwA#Z+M$o-wYjjjxC!tUw(*gkqPnxlW1?VkC5JAKC!o^|8$mEgCyb^NWJwiS;}?gJde z?is>PcJY4r2qt*Md4EZhfbKr=iXNrA3NqDB2k_+xnqZCIcC=aA1X{et8<(ci z9`Ms?K9x^JkK!j`a|CSn!)O(Fvm@>PXgD6W9&SRItt8GH~9)CbL=%%674<88j^Cxc4Ht9)a_2^woG z8|}j==<7C1ZC%RqkU88euDOZFF!vq1!QBWwjOHH&`KscU(x_^g{BSmZ) zY(5oDkFvz?Id%QQZLOi!*;9A?mb;^mWB1`A*4MnRT)aud&IT=67@?m#g;zI1#j>ck z40%aKZwvpU&8vG`#jD|cy0Mbpv$%$h&Tug~x+)$Wmtdo!pHA|rd}g0f+&N{V4Q#ED zV;fbe->~91t7vbY(M=a$FcR_y*1eE)}vGG+Jegx}uLBe59X00~Mv?9Zos^QbDHTtZp=Mf0eKr9XF7TVQNqZedl|0^$R$0 zGG+2@P{YJ84>sw{o6~T`ICHSAwBk5<{2H#9t=jfA{d_%z?qyWNmbe2OPvHKUym_Vm z`2gnKA|CDd>L9MTFy zv6JG_7B1%N_*PMyCv+*}^ixl`Ti4xvu>0bTcG(6h_PL*QMzzhw{$#vYgEFZsgL2}d z&U`V(^mM3-zF40>#2WgmrM_d%DaT1F(t99~SBPq89vr4CM$&ri#G|*qvwFN;jqy!2 z&cIvXBk$|uiZMJw2s3H_b{t`)O@Lba3pRLAHV`#aMA%;dNh?mkNWvAHR$W0t_pHTydoN4m6Ppbu$$2{ zf}2$Pkq~~Xt}-tTmIisfeP*$)a;|B_4k~RhwrGP12I)K7V)wDu@H!@V(9E!(*?x1_ ze91n(#j9&{rrYH4GxFmfHgW{-;iDeV-`CB#g~9F6oeNhyZ6*l2Ik z{AzG9?&|K(2dd@UX1q=DYFC`s%+H6y?vJu`J5=m9^?WIsiEW;vB`4~^pY(~Iu)ADu z`@UMa(i&fj)_A9DE-ks<`PtL>y?uCiuxItBaVdMO*v%JcJ7 zLx$iD$a_*e20Fpm7*v3~LVPvMIzMxQ_pbZGE2!pvKJgSA6WvO9HojzqWNoEU9X z9}e@?i{jD5KDt16eaIW-6BDWGPVry9Z7L&Fz6!hd`0k?$!Az6;6)X(2*34>9q2M#B zxw~~fk=jRQlY(?J{{i;8%^NA}-Te7Z9zVxMV-xePW`>R9@5H>n{~eDWjZb=C=qVxzGP>KvR@2?w%LSqI)) z7gNnd!`(jnxFUXD4H_e2Ej*#KEL~taG%1DdS5>mlW$ARBx0E-Z&Bivp;(w^-3??$q`AjjI_}*ZSK7Xs2 zj*z7{$5Uyrc|m?Fc;0TW2}hf%3{s`$v+)i_s{wf}Q#ShXMFZ-mr(5I?Q)$!q;zN_> z9wx~z1bfQ9(LIH{sv=gB=5-HPmga%HTY_17kPe>E zmWq4d+3>6SwMBkDo(;;>DfriBX}vw8ksBWUbTRGhV=BElU0>?kR;lJcT5|>}`l+?|`1Yd*f~C~Y78ag2tIh;@*@Aayu$EZ04Q(^U)cp;}TV|DS z>#s}3i}322ps*U#Bdlts*pJM7Uu!m`zDeA*0S~B6hx7fHV{?zTF&s8bE@`U#&-iPBu zIPVyJ)W@H4YH0(f`S-gg^{%KdcIGsUjfufkY&@+}7BIIjWJYz1{$MO$ZDC`McQ4nP zcy|}EM$Xv>Q&>Hnz|FFXNzNIYne2BAexzOcdBy{3(4#7Pecjent6WNV40g}(Ei;jp zkk_7mS}22jGp6&i(eh(!R8)PqmnJU34=+;HSID3Y!80ZW^XQ)s^mC1HcWZOt``CEa zoZyi9@O9jf276z`=E|U+)X%HVS{|~>r%YXznrF<2eUC0)ep3b&h^M$``>AT{H)+Vy zjJnFeTs}<(y_L$+n(vdrBqzgePXX>-7labuuZa;S;^W3RGM(H-G9lReH}p z7N`%y^|s^PuXr@Yc?EG^Qf-TvbB(c&Eh08YwOwK+@|aaLr%c{(hCv6w|2Uf96jb;w zRQl*%7JA$15fS@F_xm$cjKF!9RrG(%s4l~QPyV{uIbxY$F0R-Eccb)L`SHvA3Y&x%evHd=v5xn3=5C#cYL}F0A~QV@KrJ89Xsc&-k~qvER(I&%tIFzAEH< zKJL|bmT}@a5%yMj=l6~HhG4Rq-&%gOz^m`L?e#LwTgJvjs-Y6Ds7AZoDaShN8BgHV zV{v;GWQKUm6tTxmJn*2&OjoQ{M72%$sH&Rp9S*#Dy@=(|#ayfJ?dyM;RP>y>nA~dq zDErv!KJgNHyi5gojE$ORCjC=67U`|uWak{+eMBDrObPl;V&;9jbR1cZ`H=x~Tc*b(4YcJB0quF@SDjo@QnKcx3 zSE~r-ohV0k%b-QNnB}_o1Qqq{W0(`VXCQC6ea@4mkDGFJGIw!CkNp!H&7EvD!bfve z%C%xXgshPWl%+3{A@P1Q6{)YmHG$%TVv)66tt>^-MC?Z!!l?2CM-1A_qgII=qyBc*L2UeHYMm?-Dm?Q z7=`mDQnYn_qG_1Lx7ZYNZy~Yq5mr0y%yk`JT|v9FPI>ceFi;oMpFUcl(k-Ix+quEf zk)ka{6+G-@cw5j|kKTf=SRjixm;w)T`}9@aZ*{n7!JBg+Z@QDjJ>oUh{l4FJ^5C^4ejWJHpL6zAtnbC+y>WHu7MCtAp2r3sBKM zY-S%HnBh*2Z-cxU^5cN{(o*xh&-7B>7{kmz>RiwBRVKcgE(b3{_aT*Vn?CvP>tS~`_0vE_ zf6%$q?PA^@x|1y3A3rKf=ZeQ#8Prb(_2sL|KH;~pQ9q^JR4=EO`p`4>Qa>M<*3Hsk zG!@eaD6D~eH9!qohD&BaUZZ#qCfMfWsx>Ak>a3=@uCk>XG)Iml>gQR?WSCB=MhaKi zF+mZGb|+?j*PlI@`O~<9uCkytCd~XVX8t>V8Z$5U^Us_!wl<6IAP37*1RXH*q2O_< zp}l@>jU3x$^52zSZ5M1;L7IqqJ~OQPI*c~*c)bk!)`WJ9iv9{kTgoRY%F;0=;!~aS z{UAG*MZY=uTBpt}ggn1>9(E4|?Zu;uer*$Pt)~=w#b;^aL)2y?Q<<#TzpOK*+PHhE zXB^g}v&wtz;!PdOJF??DYxEWzRDBWOXO&m$3u-#a*%LhFZNuJr zm|b#ohwMl)P=^QG4MpP`chJk3&pv3TvPlO1U_PY}R^@(&?T2vx)#2peW{RM=&UPkr zf2)#yVs^SvwQdEONhN(gh*MQ=y=ctmtJai#t6&dQHOHBE@>K^@fDSOR5iU2=iDt?w z(0`byALjBFUu84*dRTQaOdO;W-*@^viT1C-6E#iyMwz3%Vbb=cRm^c_Tg%x&ZTjja z7COTAQPttJ{^II*4R0pJ~zc@OJvv%lONObz&FXtpZe5ntiQb7S!OA9a4}E(in$lj zB_FsiT+TB}(A51^hyG5!c2J7zOwT4d=4-f0VFUK#K7=CFLo8@Q+Y}Cw5r2tRW zbYD5)n!U0(!u?B3A_|)PT!HU@hW|#CO<+D-%=tuR-duy*e~W6z3(V1Gspk)HeiG9%R4A`0MmLRwMswE!lRO@e5pe$~)Y^Gc86!R;Cp_u!2tEfzAzH8<< z-^B1D{BNWJtEr@=)uk)dj(QZcn;bfn5o+xrHg-~)UG1ZZ&Nint)-*f%f{pLl$Q17u zuOqPghBq1qnY5KqY478!sT9FN$otI`mcdnJeQQzE)yv{hN3V98;>#S2!$ybwe2sZX zE7hSs#>r7Yi%c@~p^xwy6h8%cz6>sK>`{+;}vWjeK zb#Zlawo1BMCH;pl*W!wzI;|`sc1TPshPRt2j&YVV*G~WB&CQtLe%j|Ym_O}SS~-ef z2R`}>UK8HkAKUmIIq*Q{Z@H5RHOwx=-I58q#zsPp?k4v zoJrJ=!Br!j*3Hz>5mCQWec2uK60u3n24eYj2KV>Zw>Gl+OKN3d{oqo4>rX0WVG%hR z{X;!XFeOfGWRv9u!c)Q9fwMM~oviT*5xs(qMJAuG1wYUocbM92XX8pY&hC#d+r>$W zxtF^x^)T<>I*cN6aG_4?M^P$@y$|YI@3oJJ_F1oj6rrBZ2NTqX^{R0WIrM`(ZwPre z*~ej>YY{c&Jr=-w#i%BDZ-6$Im~<91okfV zVGZqbTGc4T6JOFsx8UwyD3`^ij<@q>dN9Xy^#Hw}%PPMXvC=d{V8XYX4lEqrB!fm! ztDo>;CUxq7-k`C*^;ZA&7#nxG|FP4jW3{c43Df~Q%?o*lp<;o3+-|4;26OSM-^*nd zeKtBw&6bzP=}s56>A>%@lj9l>?0@S^?{0abUA&Zl)PkG1f)3@OF&05V{Y;W8V0<&ftuQy=vy( z0h?sxtGpO(G?lkhf04b_LOW|*`+Pgmshgd95fZaD8PGsTl8dEeo@4*I={PWuxZ zcbS1LfxLtADz{AEX0LwR1_kzrJifvzZ-I)5_OXVIkhC8*=grsXijN`h5(|geXbpKK=<>gCb9ubFJH-`Ae~}>XnEEil z4XDTeXX73bn;+C>ql-Fr$S2O5xlF~p&rmn{bs%@(igz&Y0vQysu@_e~Ge;}yEdQbz zZuv03zO@fjjN;8xR{103^>Rm~C0}LM!Bm9*6*SlpSh-QezE(?1r1+?>nx8u?Z6D`w zUNPFGk$!F;#)?ht7t#K2tH-xv<{QjTdyCj8n)x?B|3SU(MjzFMyjX=OPGKdScR+UM z=BsTwt$XywSNdCf#JmK~J1ZZH(zDGd_dRy<4;%A*pU>OsaZx+H(LVZ{>yN@Vzu3ni z9b<3j6A#MLYt+dy7;BCG;;`LaPseSsid%JV7ogkiM4q^VihB<#R?5vN+J&1htG07f zz1T_IU0%odotl=(Dal=Q#TV9*33u(HkCH4cf!(iDZCl~h-fVbV80+0+E^$F;yVE|( zsHK7ayR8$m@mtavzh3FnO$kK>-;+t5r*)|&Cord>+4{% zBPkoXRP>c%nq4-X!gv^IgcApbelBF#%^BUUzo9K3YbhYOm!333L*>TL1m!d5f z|EgAcgI~Y5JcYb}O%kd>#aH;~FS^3*T(xw&o%W=U9+IV*MYOz0++v)!-+9nAV!k$| zE8H%i=MVE$Mcz1X-c`scNp&&b>=&fV<2&M?dHZYo=s~+Q zGmFSddE5(mOYzY`HnOP4o8Z4V&dWgO9ahDv$)Ss$Rvaqo+36mC()CG;=!(f0t)!Y? zP`@`YaQaERAGL}DboZ00Z38y4&_4HunMJHQW!?j$?UU(0QU7no|Hge}=}lHyO20Re zjb(0}{S6h{^kOwt^ulIC7udKDoBUu!fvM!3Y-|*dtD(Y#U5B2{&+o^Zn}RknsJk9y zuZSFy$1l4L(A$~QEwoEnGvH||;R;iLKRjWb9J`N}yal??>ZPjVwBvZ=YTD&?^<%l1 zU5c9HimEs!gI!cs>9(kZKS6iWVJzm)?5g{5oVNutSGA7-D$4%P#%lWiJKlW98Q?I; zyPcXWq2|A;mzoTDKfzUkyhe~$9`Z86RdqFHZ_wFo$>*SZ7Y+8M*=Y~D;!*YC22ZFU zVvEe5cEu0H|BCo({9jZaXQsQ3x$RO#rvHxJZ#K=V0C}7I`73&mb}3CY$0=_XfV{4H z_#yF4CJC$9$O?Hs>ZP`%(EXo{I!;O2>Opq$)sHI3I8(_Np!+u7E{=~zJInkqt`eWO zi!HQdy?7UM)d2 zWK-u((UR5h$$2(zaXasR+U5&<6X+B+r0PQkf4`bM-iy00s0sPmSStr}v60k=?ZJ(F zS>=B=wy_a71=&bflyZKZLrpk^6Kke?HOl$KhjCf#zn-uD5%abB&ZNVrtV=1F>TUhz z$M}}`8@~FAKI-pmIkAyw;)=LpsT#CPT}c;@HI!cQ)HEYe|3%qIboUrpI!&EBZIxS6 zG5?&6nc`8|GxF*9x?zIh>fER$j05C0zZ%#x1JY7xs}$&L^z7+CD1rcE*&i_Tq=DFu`WNx(lOSO^tp>GgnlD z>@R(T&xNf0x5M`6^-NlJ;={YnJ1yuf^kEy=c<$ZD1oC-T56gx+=Jqr^@oxQuoci z@n5q-#imru-=w>r4)z5}ohzKG*M;4Xtxb3QSLfDK=C~lR9?~g|$@2cNh|K$4B&HXJK~~X6~WR<&hnQ%>BpcAC}MrA@2T0el(zX zZ@0S>c3K)=?R7UVgZWZ9bC-i6nxF1Gh?y5bUQwQibrKEO=#2>y-SM{`WvuSF2XyBU zubZ6!zC{x(rwOjbSU=$1mL?*1vV0sW?$QTshpRM-w|x8;8zW|?daXzWDHLAD zpAE5pPfTzUFaNC?zvIocZq6sJf~wrO{dHWjh>eW=ooKY?DomoPe}IaDJh2rgMv#)I z>Tg9fKTh25e$yP@%&*G*fj{dw4edog{UToHqcSSYMK{vcQKL7}C5KHH3#u^9Ol}U@ z$5raUXV`x@6_?#UuVJIRcudgGU9^v1DEIE>%vEI2Z`kA}b#Pv)*Lu#x@Qf#15|5E8 zOglC*ctRek`fVC*L)^|O$#LGkMr7uhFAWR!2iK`#*NAz4Z`e(PytMe3zO?|a?9*#a zQ-kv2v&&RYOMTE#we(NfaoIjb(l%|KD5cXbx#h<@&N?^8ooM?rR+)*7Pn>YPU=p3# zDz4D`CjH#ZxR!Tsj??}(nIt62eFCn?W0e=#XySxpsJj&x)#AVH<5knThfSa^Sw&8r z)Od5|jjH&KBKEU*)Thz1(PewFVtzJu>c+3bn)Pw_2{vw^j(1>!chouGHR`7D0u}!Z z_AkN4Q5C2Jb~`6a7nw%f$i{IMzZ~}Og7Z%CRYsZOUc5eWhf0`_THTF#a=B&F03RKt zR`bc|FT7PhN*|O1*IcsRN8DH*0o|8VHi|i0IDu_G34XWJoV5RB=c!+%katdf$w((n z!2~_kpfonJxS2W(6D+{p*>UrECkJiKfD6OpA(?)Ij&PL|r}TItUQOeROL%vXYFrm8 z&IP&IIHON?i(0o@jCMK2Mio^oGOb%f99G%u3n(=3YPx-h5q-Wx(%=jklc)Jj|Pa@vXj;GwlAzn^ke%0P6n|UtPtU@7ZZP*!(NVf=hNf8MvAHX&dLEM}O0S zJZ#obz)pAIo6M$?D^0UAx>;INOwXycMby#*P8Y_B=~eE;{i0{8O<}!c;`c9wmQk-h zTIbptp3ZwhMyI}SQgNGcWx>==bLqtw()Q2LE|EVaWZExQEhqAdIYkcC(gQtT{_K}^E z+|J(hW)02l^bg)F%=5c=a}>t98r%3i7b5n%+^qyR=X8V>;Pk8tGFPT&Q9r&{2}-Mk zT~qrwN98qkXW?3F->jNuHSbvG|7UmiuAv%p#>DUzpG_k6mWW*qSHF5zL*J?OI$x#Z z-SnuOJpR?paJGK!T3LF)e7!hdb;0fl@~(r5k9EiW*of@(GGFyH5%lf{WquA5+-A>v zskkxJf3lCWbWjsEhFgC|$h!hMM&qM4>c(%7=esbmbO|%uZtAh`#M9Zf7LREq}JnVMUm(GE=eCq90 z@?(%Jt-x3Nts(=~oMs1m|V{17>ai(hN0V?6euy`zBB! zPW+OMvgX}=_#mSU$pfPw;k>6z>(21SdA!sQuP))k8|^bx&AQ^eJIsc+vk|N733e}- zlhtxoc~KQBFaF1M;8SI3gq@T8xKGbGNIg#TiCpkAjcRxTBmX9*=_>mmYIG&eD=35h zjS8w)U*OfDeDJS4{=?mz4*Iz%dh~2!o&kryoa8#_{ug&YYkFD* zkNpf+S=5K=RKqbg?oP2y2D~~(#2&?~zq4@#4kEx0;^c?+j9=w21{n0;oIL9=AgDb+T~($jWY0o(kE(U!829sUnuf_tF5t9|@I z6J%rK6LYeG_%s6z7g8oqW1G1$C_7&Lk8O5CUXnpsu=`&!rIns>qz)rtn~1_1;ih9t z8tiv9C@XKiYo@+8#b^n4XBDy8Dtg}(qve(%JK4K|CT___vX25Hw#hmCNct!f8yBtj zai|!l5{Bx-KPviAcNXf{-zmPzO#Mtz9}Ylw5tuloKGf1{WzZ3dp|3Aj2f zLlQpv+dHU>Rl;1nnP|zvv`Z4PKWXOdcC^qr{2-Zeg?*%joAJ@L^ihLU#1e1zc3Y;r z+1JlFF-NLCG)rlhQX=+?E~f&HJ*Pe_!~{8c^N8q{PjTL@;_+*AEpN`%<@e*wz?3Xa z#0EIy`xvKRYai!mmnV5M(JtSoD*E4QW^46f6x_#Fc~O5oRE{;GXwTY5w(wXgVqa0T z1)<^>-mGcrcr~%i0;QvM66S5HBUPnQllj)C9=C|YgZ=pMn8~E4u<$1ls{%WOovIZDJHg{O;ldL3n+ zRFF-nh$V_PE1b`!T?WYGEA1l;x1~-GX5xx0Y@Ca3)^~ZU3G@CagYu-*W-Ar_4PE?Y z-N7ZNIm7knjWF4HDlJ=h96wf~1bg%5f7on*u`=nvHp;OZ)Z=}4BR_Apfd60A%DlLF zqbb*GDq*71Vi9Xk3BCiHAtiV*y3^d@sIwUF+S_SP*qrW-mc*My@X;@7Wp&;RFz;H- zeFJPBP-6<2OSW@{aaPoGiP&-{6vK6m*Vsq8Pd=dsc^A89q^mDbg30N@1DN+$IhI3( z_%NuKI`1jY#wqx}&x$U2qjx3ko|9_+){1X8mwcT1IVVH1z`yr2;eQwAzFs|Er@On- zT(XWFJ0)U8*zm?3R!rC=TX5CX96* zug#~y2GU@$m?k!Qsg+5NWtJg{uIMYr%3-V{yqSfKPtCheVyW`H`5PNm=&^GY?Z>=% z9b6qup<-uB=l#RR_4YB(2}Poh{_%`-+NFmxr8#PT4$9hK@n%>) z`}hR%x~WcQ>59MYXOKKzWv6%OO*8R&AEzL9K}AyYub_`UaW^$hjy31+E7+;2{-4CW zQ-VX#8wUsUc2(7}*Lg7)8#i0;Y$p^Q@&8F|@fVa2md6{czNCm`rww}eE|P*QZ_>x4 ztNEW`_l(qLD;aVP8}~coJ3^(6veVyj>pohlnzP{%SUopZyG52xca!TOh&+TBPs8pY z9ri|%F3mbhq~(2H~_{d}1%|u7i2sh1uJ9qM&_DR4Z$-y_1cf>7y5+d%ua_1N@zX276Kk z$t(u5vEE6X^O>B^rO)Z9rswpTDwNQdy!kAS_(4>6>w}xBl~b{05uYfiM<1;-tp?K@ z>4IahJ5-1913g;bPjmBCduM!Cs0q__g~xdGQ#SI7SYH{EhbQh+SGQ0%J!zBgd}5D! z)l{vVvT>=de-Q&u^2@BcY-+53OV2keCnEVV|U$8q>`;@>OEv zk`tk^xZp?1c%BvCMDxCgHw*gXJMbvMJXFD)l#QKG+|rC{fi9(jbJwEcG1@$@G_A0b zjqmN_HFpk9i&$&uEhLYglBLN$#?zTU%JxsOO(A(aj5mu|Wi54XqkC3eAnq%l*n$09 zJGWY*npS1u9$oM_v*-$@CM)dXd%Jl>mj3ECP+QM0iZQyH4h1|s!9Ko+zEA#V;}t$A z&X@IM^menI&bpU9*16dyTcm9C;LW@E>tXl*j@icx6zexUv7FZ`P=b^Eq^wU=)@v*_ z2WzDJUGEcnt@bJL*sfOA7t?!K8e*!F&wu^M9`?zSF(yzUmDWvu7UQqSc;%FHs~0`} zYoAyQ=VdX$B>O05mG|56awkU3+1LO#JK^goY_nZ`Y{;9XvFGd9zp&lSrjYiFXksJ6 z=eD`!;nmqp?Le=cI9oGhKhdPTQ?>Zo|Db*>LV%N7~3gN=@`yIpoa=;x&| z-YDnOw^N&Q?ebeI8S5l6vC*B4yYvvX4b_sU)2@UIvxp?`mv(PW?R0 z#-`vPrkEk;b5YGNuwTNThH`0-9PAEPUwX!B%+_tL&prY9F1%`xfhHEYH8wul18#+ti59*eE5>-xkZ_GN_c=Kixc|I_vSMZQrPXo3%7YnLp*=b_sjIe1Uv0ynaJEn z6Hmni<@l&`%~WB=QDFf_YUg+4LiNZUTdhTOQE8ryx9D|o%#kY{n{WAD++r} zA@6I9)=k8A!2cS`wJz5DSngHyg!1sSGN{W4+l3O+m)M z?bj((JgJM>jtS~n#oh2X{C`sE8dm@pxYD?1sE$x{<2b zW{OCaga4{>?NgJ22I8>VDtB9LFSYalCTuF6rRC~N>UAD*oX!v5;G}o$Ix7wKteuu( z;}L9g1S$sF!Pi!?%1*0O4GA`@SZ7r`o=X$gf{JyXum_{{H;p(1o2^B?G;V*+9l@Mz zOcC>gY)pmCoUr*4`}g=vE2uaK6@8#$7k{rbGpP-mbFgJ%<35%0L#TMb{+3zAE;gQK z<9poQDz(#^s_Igzp#`p3W1pKS!bkO;U#OsUWk?zLdQBeRfOkA?vHF&LQB}-GnsODuAn#+|?e;Mu#Yancxjq}!aNacax|He9On-l)fA+%_KeN%v zPb>J;vs87uhz*nD8|3nM*_7M+C?j~Y0&llwZKp2&VO+OJR6kWIYq_boP_?b^6IJW4e5#*M`~EBV_bt(K;B8ebk1qaA3aYxDJZv93)a!?6 z-lb6WF^;aSmadfP%~_}`E+5!OF^GL%)K_EXzPS4|RP^SjT73S34*asY%Mcm7Mh=X} zJo%yfZR@M&6YW^rsg@=-7PIlGN?A|;uujBUi^hXg&eL&L zREL^@Vt$F1d;xBjddf&%$!8y9Rgmg1@dzf^j*l8+tWP}QBde)yI<#6HZ;q{egM2($ zuT=!*-;qO$Sn5Kfoq~j3R#1hVXH}Cwam4_8T*wn6|L3dGSfrM9KFY>cv$kfm`)sS2 zV>Pwa(p9Qdb3d=o>rNfoqy}F-uU`Ly^9J$N3RcF-j{>y) zc&MmrACK|XZrt5mU6?1LpNM%~@mPz6T8n8z6=nuaaF<9=v4$1y%JzlbUoni|1#g|r zQ@;9F%m?$rDytYvcjjedf_T(V*?3Dla@*Zokhg-JBpz4j(T6~HHBadVyFc4U7x}wX zMEvGY(~4gy#h(AOk>tl^Oz9xXaSR4%WiWbX-!-1nLu&rJ*PIK7RE&P`O^=r;XM*i6%&VHBpw<^`|}F z`3kNc=jWApV2QXqXp*)zWuqE&Cb&B7l)OJ(w9cQ8{J(p>9i8CnLE5|}O}R?Ft`9W} zu+#CC7AYeRO=A#B??rrP-LC;>7Z5 zT|b&R;gVt4{|o4y;A#2jjuB#29_KZN(arL_C0s4Dlh1gyu9?ar=&z|-R}isrs#qa8 zIK?N{@KrDIIO&|JgMHp3Cf()Z@6_n?cDli5#-(Du!^uZ8f4>yY8cdC5Vq=UwtbxAQ zd`FFT!xXEn;uCebEWVmyAAU!Mm2_8)|Dr~RSm9=$n1b7G^VZCm zR28et=Rc={t@Vj+Ws$zr8YT^l1V0?me_B;K(1Xo+} z>QEZ(5B}*c=4JV+hkU$%uU=+AjYc?HN36~_kTIX}!**I(>@~C~3mjlm> z$JKauG(T;T>+j&M>=f@1Y?tgKVVnJ_m`~&DO>7J|N4ty@`@mFrc9JYTtIK&#to)uz zx!PZ){1GZT!eu2{I?&ytE9r#S@!fX5dKWY2avth8+QUsH|9l-2+>}z)>#bs0ifz)U ziszvFUNL_{-92tX`;3TvAs(|*=suZ3#RFDRM?NpW{x3?&^9IQV=*oI6@L%aU!TSx?d{`kyX^-5>8UwEqIkcu z-`;k*7gsc9p%jn3XkvZ?jW(Sg{@yx2;Mq*P`K)LahrNe+fBq`rH6$v%qd_>wMv0p?A* z{Ed9EnXSht_qFy>%@c}=>GQhEymtB_Zzjt9O^lT&_fC9On73MEg`=hw&w2V@>sZLI zWmKt&esYg>R+b^3nuyf*H&dCH3ZPIZ;ZPIrk|Ki>R~Qf%q(C%74qngg`9 z)AdlXkvH4I=0=QFm#0e0kT+%F%@|^qo$kRmBlHg!Q*(g(JfQ+_PE{+*$&2|c{MSF+ z?N;Y_Y!SZU%2k=rQm1BL_2&s`#nB*%lorPm>$NwYYx`K7I;@92q-vKta z@x?|p^C9mNu1;lodGTnEO%fY}>E%5s{12Du=V_PDqFz=F=<~lE8!2M@QW5K(sxfb+ zYGrx5844A7?BjjU-;>I*W~Qerqw{F$KQkRGKM#!8s? zBQd`_HJ53{mr0IwPNAYEmRaZ%pYudpUH(?9uPdT=^Z8J7%VeH0!A`f~gI801(0uyt zeygac!n4ALEV_e*NGgTzX`dLeZ^`(!_+FdV*NtF95rmLClV`R$4T6zAMdEZy~_z5h~(0(U~XhG_4l6d&7 zd}5yHyhL|DqFOh{OY_*MXdiQ|VFMfe=%aIX*GC4|R@aA94Odd2Z;IwdF`u9>%P&9P zv4h(7+L5pJskR-cC|bES^qxm&OFsd1{q^COYqw&ZVn}Ri&^7 znMI_-ZqobuO~(-0Hl@2qv0oi>{Pr6;nDjxTL~JF0B=LxKXwTYZMN#dDAAh7>I*Zsc z-k8E#33JP-&`?AEJP22x>PxGrAhUd8fx6uiu6BrdOHa5Dx?aMXS^n40t+JBAY@|bD zGTA9_oe6fo_vTqgxL;gigUFiOzeUFIRicK(p%e|Lfh7UJUGTaH}J)q{FV(fzwT!>#k?DaJ^H_S z#~RO=%bQiz(nab<6Q4+IEJ*3@!H~DkPT#ejA|_Om?X<2>JPO?#)$10}y}&0H@>z3} zw#_1z;Hox^Pmnp`$)R6gb9|XCJDLSI2)sK)Z?qC#N!8=_(|(q;}i2zOc3jL zU$Vj^OP^6SFWASxl&{vXkxX5VQ$M}@{Ujd4;Oa`b{hIts`k;hua?u2Dv0u+RJJ`oJ zvhtB%>=lS$|3wpY_p?uY z<}F!z3$B=n4O>A@C+cXk`cMt?PUfxm?V}F;w4IHv7^#)DOw}Fd{XeG80$j^tZQGIp z0)ljRcXxM42!beL7X}8RfC?gr1-L;>WE%*gfTSRxv~+iOcQ^d!IkVW`f4s+9u8(J) zx$C-f=KVJL`XeGH|GSp>U}caulNjC)ykjrvtE94zxKMygUQ8`!I{zPoC=t zf4{I|Br6-yDJ`XCdyQ`VL&BM2_z0>Nn8$ zA(0>_J>h6}@5+jIk$D%oYD3355-Q(f+xpO0#+yCBeph1RbpF3N6vJX4v*HKr_7u0b z&tkEUkyJT`l1pz>HN1qRBhW-2FqVxT{TJkT8a{jCr5lMyO~9rrhOn|ax>|w;US)^p zz}4?anit;QAu4|fYC41NGidNbICu;X8$!)|5sN*+vteY1x7aNM{lh3M_9QF1!|y7z zSqt2JiZ(uhMm1`J{&SN$J*U!vnB5if<}M*qZIa- zL&SOwUA#^WGn3A>AlU4KE(XC%E9#DeMD!2g;)S5AmC&dWXbeZ&FEJ5GL;w5>Rl)OU z;(ciBqcLwD#0Dt6$7V~#2K9QN@e6S^4LRXu;^F5Y^Eoto zH<5QFGQN(!J|+GgCi1pMn}gY-50)uTmYPSV?}aAbg2p0ZUTL)4haCpvA8qL8j*!O& zgWgw>W(>%?hgjMb8ef2n7lSr8<5?eoiyma%Y0NXSVTDMeFWRg??Xnau`m#eWXw4($ zl@6#Ff~38P(go^|$ZT^6yZ(!H2(A!1k-46weNuwiO&KdI9Fnp*7G)B=8CQm@#P~>=(>L(-GoPd_3 z@ezOVzu!b8d>sykW7nsMtBJwp&(P?B&A%kiodbCt@v*^Zs~54WD1JPfX-iM!7(irR z4f3i6R1C%L{BPnxV=ozWAkW`|(qud+1OM8MSPg1=u?I7gnMzqO*9OIDSO;&tHw%NfQ^2VUz6ZYtft}0UJ ztsvg^CHD42BeS4U0Oa+Ai%*Eq?SnmLfxNfSW?xpW4Dsj*-X4OsUnXkaOFa4=Jah+H zA3|+A=&pxv4SGwyA87O-lgy;<&X1+LLt`MnA7F-cl(_mCJG_FVBS6Rv zy0J%zBcDLyg@CI~;B5e?c?FA&VOp0KR6GkRK1JJIm>ylj@1mE!2}1v*htCy|_c5{L zb^gjnZ9fz0Z{TCS;AUw8^4`T>1Ho4Vs)p^zF#zv)1#OD%)M)SpI2eRRp8{8jsG5Jk z-pVluPdH2lX$!hPWyQPHHbt=c9PIHne7;L`{Fln41af?Uwm%~tH76eJ$L9R6-?4|t zlScHQVet6^f2X9b7{jwy(dH*)v;&~~e%}2IFMW@_DkAAZ`0vdMEjB-(oBw@D>@gT# z+EO(f!Gne%>B}JFdr*-S-k*Yt&(ZkHNScm#GzNck!sq1e$LQB?W5;ia+i$|fcSObOAz~SMhXfB1S${#}Z6tj^!PBeZ>HT^B z5j5)IAzP5ty5SAvjX9GJaucJ;D`0ORF>ODy z%_itVtM?&37Ue#`9O6t5vMv9+-OxKr#o7V-pRuNUi2wURmhH0tlZ2i{^ZH)tauxJ zFJ`J(5gl2Zd>mq0>VV!p@X!r;f8(1R)<50Q_2)s-isYCj;N@*R{q=yXlf;|0=t>WH z2O8Et(}@yq1UbG%_qV8upMk?KSn(Qunt_-$KEwk3^fzkWOknyo>@^HoULcYuC&w6@ zUq%-G*Laz`82^nX!$D|g>d~`A?9X}nI=(t0(5QkJ_9u@G#-Hy8oBKedsCbpQ`8zc3 zMOUw(hvCTb9Q9T*X#9wOy&O<6jmjfG5xF;Vd;zZyL*pXxKU%C;$U@gR9n%ZdW_OUD zDa7Uh$oVpU^b^@M75jE&e=YVLwPI2{eI&?xfi)kJS7SDP16_ZLt{w&z=9od~>Lq@@ zz~9b?nAR6bKSd7fxc#8%LnyoeuRnsk^F7izJ5;D0(?^u*?UsIb>jdDO$VhOon+P_d>XdK*pN zV244V^k|69@ACXR_PvEvOyJijPA!jeh(dT=pQESXY;%dh1KH8HOC~xt)5H zcr*kb?gkhC4OM~ls1=m4xgj0hM&#>@HeUmOzY&`=fydX8;dA!s5_A>oQBg4zjGd+` z=maW0$L4S1r8z>(`-t~n0eK_I*h!FHH2iDbt$=IE!-7jJD5yX)<@uCs@u8c>o^nGmgwpubomxlOd-x9=M%f%XOI49VmWjB3S^MpJpVA% zE*Yr7rVt5wv%{x&{7!0@y2$Y!*n5ZQUyhu=23_@sMjwzlk9d`j$ox9*58!tjka-gQ ze2K++qlsUz=xt(2XHfAGTIi~%MR2oH;E3T;IaolUz6v~ zhRXa^R=f|F&9LY(r27&Mdg4E$p^*g6IwMD4ex9TnO+!rl9SM4Zs?VXZiS2`_c1z#NMvdgjqukrW`%d_9&``L~zy#YxjZ6$Iy04XiPv? z@ACN%=-)$ZuMPE{M7=lg(GtY=rD$LvxcQK}V;*wkM^`WL{2gLub5L`F$=0{vx-W5W zET~CBmhZ&=ec{v`m6m#SGEt@F$kl={Ix27wjYd!JK;v6H!4AAN zmUtg7Ukdg(NgVu|_j|KSnp!~?zsL8p_kKu9#87N{`AAA!}Dii$UH+0b-yZ;OgZLS=>Sz%Po z$IN62Pd-D6Veq?&nz;-#-hjqCfkp;m`z$Q>F_`$8%631vQde)Y;!QlM5`F1LBEjd_ z^D}&K3HHd&&U)#4Sfy3)!DU37PvCP9Ic#;X#{=l9FVW(;;DbkrXx~EPLv%WgIx!8k zNEdYVUV_C^kZb=4a(o0r=Tetv$I@Mqq7T|^&3q|Fo6n#z7zD1NE-#FhU*-9`cv>SS zvBWlSA=hT6%X(GIpo^UdEPrjw5?2C zD#L%zK;vO-o}8M&9_Af%F&vqXVvi=!Febi;Jqm_i>^q+7A3qWw@6hN2AI}hB(-TXK zSMP%V!Pr(^)dF!n*uy+nhJJ4qnaBLm7vwo<%Zbfj2sZDA9Qmlw<`NV8v&YAH(kkLr zNit7&wD~qW)eb%U6!_~4jW39aJHwpdY2@e|Vu`be)kLAs(9{5M6@BndEa{3xb5LnY zV<7q-g2eWe=D{9d!aP`mT)UH4 zg!`A#LrOmBRgNI8I4G;e$P~dnf3nM2;!YH>u?VPHiLM4Ar?F%OJ!digy(_xzjzybr6ZjZe<-ffDA^S|CgGmu$ z`x}7^vqLK6kj6l;J%WnhI5ZmKoxQ-_Ygn#0=wE}D|HBI9Sjx>q%f0?=k!52y1-?BRy;*Ea>g~4XMKasX9Qg}KojQL9@HhJ zvByem{b2&GmJoLf1}~PzOVHqdGafsBg}r(q%Q)sCDX_<*JRJbO?N8DWjmDvcp7@yg zV;_~jUTmFc%}^3uIVIMM`=U)}jz#IC-e3poM6r7m4;zMeyagwtp^*$Z9%ui7tavI! zqs_?o5gpDu_~0aJpG?@}C1?ylAJ%Y}s6|HLJ#U9tvLN*59RiIHS!XR8Cn~+D?487| zgNAZg*%(!;6Dih$%n#{%-@@v4lo_EB>)r0?q&PGdVySo8p*I?tn@}g39r_SU>WAuS zBK*BY57Q5=uI3(ZMQHRu+g9aO0*z7F^G*0147zudYwKY1_lPC0hwNZJztdfOg|05( zAFauNA3(*(os~Jm4D|dSdwdK!oSjxc%kQv5ZxC9OSh5Z7jaU8P@-KW)T)ha5fp~y5 z++9J^=;uEL>=sA9?r35F8f#7eaRDx3t{oa8cPi}sIQxH$J&ZP4s4C`Q(@*(~z+VqD z?K3;P4~_Tm<0@1~+ld6<@O&uRUV?uVX6ILtq|35q^$hy#A}EIdg*7# zvjlDChx=#AU`B$5L^P{BBij3LG8S##Bv!OW#_q(cPSCiA%3f>^05x_G+d@3*0B&E$ z9&MR3+`!I8G%Mn7LS3E?F1moaH;C;GIjKBFEpC433$8}N#W_w%nn1rhyL3jg>9FZ^ zY&8J<4uOmH^n1ndkgmjZ4|mPy8j5h zlhlEYq0s|bpTQrq5RVoR^F9R?pJUUt^m`@X|K$)BA0ca|L(=JJ`oj>9rsHF2@rozV zW^Yz?B=RPLiwWrB6L$HDOnH`BMN_OEqq}+TBAz}LE(Vgz#^c9{;NAT2E*6Wm%?Y^p zo)v?jG#ejF7i!I3*t|zV1~Iyen`p7~&}fb(-Ua{mFvaLP%^+W3vtfA9I^-xrRJ4Aw z7i)w~j}ozeWCv;3i$ytNFV+Qpo28~fV=y#+f}a!g-wk=!JohrzFTh-9B{cqnCWeC1 z)u6(N^$O2>g0K42Hb=qRf6>H8p$^dqKnt@kG5oYQ858c z4`9s?;Mj~_9W8cahbIE^F3^E!4|O#LnJx$XKY_Mhgxl)G?gJs)4uQ+B!1DLVQOD~3aB6?I@!s)6eR7~N5_D`Ob{AzT^K^o)YS2?2gT`o9d<~8HXge*^wTH(yf*d93TX*1_ zKf%LS*ku*bMjDU*PmTg)5asxW=RXD-x5()C^L971r5tIIV?NKm3@hf~gQ=m>0ZqOV z{Jc1FYzNaL(bU&?*a~7vHlod=$ng@twTHU;4I0DI)t{WF-sYzJ{aEu=XxQ(iql#OA z1Yh#`85AERu2#aoUkEf>k*||t^XVY!i=eAz%qnt)h}JDZS9!<}tB~+3R%nmS)U$=4 zp+#R}rz+^`2sD1-`Ec|xC+MmYx_TZio&&oj$w6Duzz9$^6rC;&y;yDdQ;x^MPwt?r zvG6z;E+!F6Zs8wIpz$)Cc7Wsih?;++tIzTC5$Jsvl}TCleg>;Qjm?XLt4+b8gV5G! zaCL${sdlirG#YX)tggnut<9WEXUxYEym4lDi(es*Dx^5E(vJgh77 zlwjtv4K0sg2lL#bfZh6dhWX(sKZitgFRbM_i5K`R7Cw z{o@`q*bynZ^V1%gwE0wG*?;)h8=t^G>hW$jkoPn?$%n42c!v?shC^d5m0}^H<}*Bh z37ysqx-wo3!q2}&?>mWC<=Fd0xO^Tz*B%?u)o|j~XP|378T~$N{z#zlM6ky>yk>NW zC7%-;S3)B{w4XwXZUI;6i6yha!$(A$ub{DqOj#CGzeL0{UX`c%*@7j%BvKf!<`WaM z;U7=H!)tKS6gjSg1ig45KQZnWg1eX5;Z4@nBa0mdS3knzVC47<8Y$4#!@U0vKb=Ac z67z>EMxm?I^kP=-X0dMURfH~QHTiESJV;|bxU#2oV$ut2HVRW2ebA_>96PXiRZ!#1 zr8`=$Om|=>6(Lfx^0NHK)x1Hg^bzWdOXlTo`h>yB&GHlxv$Q<;&Ws>nyYdXYT% z9v=+*_!Z6GW)kN_(aOCmT5)P={qzo>|6u)HWPB^$KFpcl z4cbh~q!jdvA6afG!$^x_b)V z;|Yjo4_oNatAosbOx*2Js!+MC2Mtd3;|cg7B6Bm4I{+%Z(0fs6{DTg9akAi@g?Ys8 zLPUVKc|H*Q*`pl5<{$I?bvXGc^e9!K(U-aZ`_QP4m#%}x`{8urLp**D=xz#s135c* z2c5)wDqkXnrxQc*74KenPWcI+?pVwoWfC5G=LCE?^^<3B{WzT%fMq=a*@CX5@jARX z_s@j<-o5w?t9PN=@(l57bony6_)nnWRHhePeu@t~Ks-80|KO?bYgqLMES8v9`ZP2K zv&+laKRwaL6Dd#GBaMv6@me^^=@+z_ggYdj!*r*|_yTO6Ls#vf@fmw~T2usW&Vh^A z&v~+z3^|@bPlMUT6RFH$K073!;xq8Sn_f#f2C||*E2~r6tOHLUqNxwSzdd6bBIT|vBE37FS^^~$DZ|!L>KQ7Ek;A*7RY;uNH74q>&Ka(F#|pK0TcG6J3(Ge z_I3u`gW9DMa%^GJK7{Aa%jXcgoejMN{`-Kxw&=>a<|w#u5;q2W+#p{+h%Vmc^E@=N zlCS?E!#J(@io9$Na+2U=r#swK1Qk0HYRy5=a3=4}@oD(%1!`L1rI*RqWANjF@H!Sb zu26S45wQ5Ehp*7p7SL_4Wxjq2OcW=VZpLEY5DPwq z#w=pqy=d@3wD2PPG@vH94DVxk+iW`yna-ij2I%5tWa$V$89}$XbQl`{o`|-c?zjXR zc!m|v2RTv_^ZsFlwaxFL*31;-=n9`tV*NsRkeP5K(O?AJY+ycX-C>@yTdPfeI0qLK zz@ByB-yzSHVQ;ISuJ}QDdfx*?MJvVcps@^lSTn1uSExIh647o^GfgAMd;^!0@Q+J$ zIrWHX&%>#8U?wIzi?P^O&=`#u@4+5cyicObr-+Vrgu6hKRor*1TS(n;FFw|eEcpUl z)(g7Y0RP{!;(Pj}RnW)@HakJ%N!C@Rzc>{t_wV51XK3s~S4F{17gjtD4Qu9o(D)TS ze22Cd;~yEYV_P_Qmh4cUSaK69&L9?ihb-2bSMZ>ESo?Xnc#uv@Io86(&qSKv;Br4_ z?4^<8v0(Goz7$Ot6~~?e2%mLfp2I#w4Eq2*wxCJ>NxdR2v!FarINOku@jxY+@XT=;QoXmns* z*&u18F#&`wWj2(8ee1J-C%97uO@qb(P*DI~DQSD;DTp?Y;5TwPj%c(pcxef!w?|to z;WroB+z#?4V2ugrYztL&CQzZa9}IG2LRagdF%B*!v)5kiQIHDi0W{GLT@_`Tah%G0 z8rb{|IaZQk(t?%7K@N45kLqVHoykO=kB7!q(47tWTCl?-NK+-Cdu!n0S7wxHCik^x?0Qbzaq(0sGT6sm4d$x_>htoqGvq7o0C}~y4MACmj%~d*r7Gp%uc%POP{@omUn5omtZy1l>nGT8SU)K@+j*PApasEp~v$ zL;SZmeyki5cs?3QS0QN{?9rU(Pa>&)d;|T=hs&S%nN3u-B0u0M4pd? zS8IZlM8)QC`5b#RgI-E9$O3457b2D?t{KpfS?YOgriWZ5R?L9QZ~2*mOc&8+U9|8l zG}^LO{4Ya~Bguq6q3@m4t3^OwXY#}2V7NHQQ;wg=b6>-&ooiYw);j1)N%LZ{4d7}d zHN&@PWEt4Zjt)Dr!&B(AI+fxHH1s=p@C#Ns1xp0-8Y07saN3%j;!Uxy0InaN3zR|`2_qPB5n zk^|%|$D&_TQ-8(E_4siSZ2cs)&GYEgsl2u156I~}<8Qp>9w<98e+@cqgB;uNt&wPZ z7}{J4^6tztUSppo)GimuQWKD22o{@+_3nbR7QFo$l0HICbT+zxcZWbjRO}%hRfLAK z(dWr_rKwl9gR2q9IUMvZBZK51hCGTc-#{Nt@MG;U9vYt`%OB9V&5XA>G@SfL8pp80 zD5U%kH0DAhO_1YNs%9-#n%c#Q`ABy767)FN&qnlr0xq>!GiY2UYEDFk;czlJc#tQN z&%=W>YLKOzhl{IWXlypw*7;Hgs{hx)R$F4Mv!7`^Q&(d^>1pI}hUL`7Iayw4X!9@N z!ugUjtOCgKB%11qA2~-ph|NbK#ZdO~9OZ6!e*pA5Cu>0lxs4SkgUms!_=RkGips>= zUUZAyTtv;KNcaW%cCxvFm{$-Q*4;0Jn3tN!JeO#s&A$UzJE2jInEV`?dW@At$wAKh zocw=5tXPB`*^vKXbn!e^sT;7XpMMPx>S{gMElkvWk{!fuC1%x!6KD*B(j4OIJ+|8&(g(5M4;oyCoV!=WJ4{NOCE8CrObNYH_(k(TObe(=Gs z_&patPY15r2TkaMMw=bT_cInB0j;&fs~l+PQFsuU^{|I?WT)9y)iXj>t<9go;?5K+ zh5743bnrd6P!orsQ2{M>WrwHGRbjBZ8SZ|@;v+!MQesI?Y_6_eY1dTzJ1Jx0K_*njU1Q?2 z&?`x4fBT?0$fz#H22|KBuS2z+oQ z$ji#kov_!lA&X@tmMp`TU&6&muxaNi7oBc8BRB_fF**Sk$ho_H%hcT?&LETZ181gV&Jc_Q$Qg@$(`zfp# zg`fNz>>+Nt1bZ|kD<&rL&d2Jb*kK0tu&b zPe?nL`pGlEnmkhzR-@^t6xU(XvG6gGy{r_a(SbeMW7{IoI79}Wh!nq~k=4{4_rY5; zxOfO$)u1N05&8$a-*MP{JE*X$Y)-VY<1-!|Cz?!S2Rn>~SnMurP=hGp4t4M#Og z6OeOCppg_gY9mJnkXav_ry(k?#@cqr(~;v8nXni+)D?mWoRjOVS;RS7)kDB7F>vj5<>G*V-wdejZ=p`jd!i6;L- z<9Am41IHJzSgFu=wnbiN4VUn-c}N;N@O^ZZd3aM@*|`=c+nxdyP6{RvjkI~H(DAiF z6UvbX8VAtk44zM6r%mW86Q0)?WVPkDy1E!FHZiPlE^~u?Tm@~m1)p`mu3e?NniRNL zMMihlPy;Qr;iock+#*LW0S~_+hc-Wl#Y&;g=E0(6LkF@IZQ4Ii;lH~BjmluI4K!+@ zm3#2gb@P*&onZfL5*qdo+Q|cJNw}NN|mPmtxJy zpk_HWL1Nx=_S23vjo>FeHSreSorcEek-N{rm0;%!8mtZ1k) z7Hb0y(R~f?U&Q`X_*oHXP_IWu%)64-=7P3I1^_cxe;%XpTIEz}0c0?LVxT3jWq%^OVHA zXpg4kL1$RIdGjxJm|K| zlfsI%&`80*)GB02^9hk2T{#&rFFx~T6&^8wy#uK^!4Y?z@>7e$3M0} zeQJK6n)?&1Xf#)gE+}8yaG}1(q#}59%L(^Lz^SSk0U)+M@+rL>h^~?g~6(5?uU? zuFiwq(s0?DPa|SUrV#xX@>Cj2sLO9ruU10S`bS;-TpwHyjVbIg8|xp(9tDu2707Ff zpXVoP9)ZS;AnAPMP*-J>;-2!h4&rqHTg4CxXSasOUZ( z>`@y@+w)TmdP(t*<=E;EK66-m4qZ8mYXcv~lC03!4vneUd|L2x?NJF1^+97vT9CIX zc<}^itU*^v*ta(C8#Qb4cQSOfl4nMOzwy#D)E&xU)NBq$b3$VeTKtp9>@;sTHqU`Q zS`hJ^@s=VoI|ZIYv@&WgCzd3~Yw93pM>ws`-^q#oE0Mz$#;dc`9VO62JE*h-6*)lO zZX(45;*}X@2hrc@QB$5v!ztKVc$jXjbh#}YI-n&>_qZJr`B z7vtU5#P0jiNzBFzk<+>5EVObd)CA3gq)m}IBavAe5qHf|Rrtp!I;niui5G_=?y09Uh!jFAwN0f@gGKMLT@4Abz|D?yPNo2bXv4bWZfdlJJo^PnX26eAFljhlwYCuz z@%%x3$JuCBFzbxB6Z$q^pT}=z^7I#QvxNFdT{VEhBmCHFS&c43($Pp7bE(s8YqTz` zP}0OmYNa>^yDP^v1Q*wGV2@`%l0}Gi374+5&d}34PB*ucwH4Wzp4RVMT+08!N2w zP#DLGEug}R*Hb_zv~@ToG!xDOxxezGk0nKpCUjEv=xsr-HSucj|0^`6;?XCFN9Ew} zG4|+;F1&l^8OL}kcTW%2gMZH|A7q6mc-F*MgEoIfn`6PvPPCa1|8386r@&>1SSQiY z6n5~$XdYZx{WL*J=km_flQ7Sii+_w@&E(+ePET85^+!NOWwdz>RQv_5zK7FUaN)eW z8WLFjHv?n#D4u*tV;l(E3vLR7O>6rOczRKy;sNll9HWrbS~5MT5Sx!9O-*VS`%Y)k z_75|O|DG7tLYtlW7;iF!bHpiV%G%!kAqD!eCye+nMl?AHjmcq!o^IV;7HvL^r0v+t z-gXN*^4!W^Yddv?bNf~S6?(e%i2cKltn#cfDRLMqI>Tin5R#0FcM;EiL0?m`o#zi_ zu~aAaXpI~qZzss}jKkBlEm$l&G>qLHk)u4i@{GfC6es`o56Q{%4cNakyVS=vPMQ}W zhy83ybG%wYU_HYMJl(vQrq&!@-$7ap2j5%mCsQ&|9O+l+WL+@+FYpygh zrzV#!gGrBu!s4LeHO7g z5uDWsG|bn96Y`uj!6f`>1;|Vb<=X6EFIEL@C#EA@4E^7*`vTB^orpt8HrcBkkkt6Z}!?3&N9sp?U2-SM)TbI1TN->+9nOytp}I(FqMNGR@JlE zVJe!i8qJJ`8nU7#J7|xqSjlR1B6L zG=|F@!RB@#6Tr5e)OjkbQoP#?Y}ElDcAYzrV=7!2xvfTXphLTZrr4!$kYfXKSXKWG z7gku(h}}U2{!R*Z<-$sGJ_x!14ZDLD@M(vhg_yXXy=NfBEVQ~GIWmKa25>12JM?q- z)qLKc0gVmV+>`n$;HEVzszW0Q$XkyDf5GW|aAhp9FKx#2`tX~DcxA6O3od7o@pode z49HiPJ(>{*>`_F;e0Z1+uPca&cY)V(XrT!^RHS}#7O@u8PKS$m{ACrFA4}B>G)hC` zW}xvGD`ujz9Y~rM->tX{EsCUVdmPaFY)|w&PRZv*B|SKBlBq*rPET zD<06j7wWOU{ugatM4Ls)mGzOcTF})6@{TqaH{$;~6>Vv(UJt94!-MV)-SHeSF%3!9 zFri8g@8!b|b@*>)EM}Mg4;Hi6+7ABj1vga#m({SCy4n>iHXRzP!KSws%CLW9cF|%f zz^3;aW@5AX==&HoLDYA%1W8kX&5iIm6G`pz&x6gv0h>+GsrbK(EVY8SrLhXzTm%0l zq0tDd)kNldp|K7AX0eBUe4P1GPVz?GU~{#Zn5w}JdnS9#g<9+hD+O)VhTm*d|NEig zbM?IoZ8}M-4vogpC`oN|5gpHG#WZxW8g1IIc@w1(bgBjXp9&V6fusxg>p0ph5bU8G zWr!w;=m}S{|6lB~1R58pOiF@>CVc9$S4O;W7nqyF4o1Zz;6EF2NNgIri=c1uznJHL zf$`1wSPFDp4P1)<%E8kk^3>*1Fm??;E(VR}=t3Hq@iB2TlO5(l;}8)mTd?{4c$v{8 zE!B@SWf-udRWGvz-O3wYM#duAG_Z$Jvo% z4|~VRD~+oluY8ESqQY6jd2lrc8qToRgn3^+_HMzaF&4`Q{*5;C$q&=en9=>7fbQnx zsXBP0Q($#910Jk?c2g;4M+5Z}B33D)&3SlRh@?)SoFl8Nm_eEa8usW;Rb~V~-VTj> ziFr;0jsJF)w}Kqb47~wm<(@Uz<9_sQMz?m^3XMPc%z=h8#eC3c371YC^CQPeyw{vR z4QV!E52HLwie}I% zz#QNx|2{Kh^nbAaX=s!{OUluZz0{Sp%M|4J6E3!*tBgU8JF)v-s)h|=HFL*#Yp zsLh=(IpcM{&LGW1X*8j><2?KdHkgAZoa-;b{@18} zDkH}OaA95UtlHVAT*ljC-hiru99C&oipsGJIh$mq)|`PH z`^csC@GW`nS&rRvw7E0Izp;$x6Q1_i!&?(OCs0>2q2Q!#JM(ZWUUm5(7Kzi-SdC7? zCd)yMH_~b$L%h9L5}Thw$I%{ECe}|@ylvQ{4cz3V`q>BSr_+;K`)^B#6wRQ}KG-8m zs76JNbNwaY_%=PfsA&fsXQO6((QgMb4V`#Cp{}$?3*v50V&V>B$Yi>`@kFh)VWwzz z;3Q2=WF%5-1aWqAPH6Yw=h;H!ZVL@3%}(t-XB-O`-q<{XpBLnbH#VEGGAEH@7c`uL zjbo<`_<4GKw+T`xhgG#x$=Pr)HuT@(D$eygVAF=!JQY!OMbL!jUMmx(VDZN0{XtiW z(N(uSzA&WV; zX@D;6Tg@=uN$|$z7$o&hr}I}k^mxMOJ>Dy!p7k!I$n%C^j$rc+Sl#(k2C%yt8lECL z1yfgr@J?~{02V7k{p8$oV(8H0q%9rq#T$a&Ld-=>+z$VC&lC8srwg7JwPJ@3Sl$y? zCv9;mX*af)nCRWqNTWVBO+_B`zU8mT;mMfuCw=fi_7L59sL^&|YrD>|;D2+N0(ft< z6WSEro?4knUE$nv7unboKIeUE)7mE9O&yCLPe-ruPN(Ok9pR)t==M(M!hjp^bjI7n zdXQYSLL$$WH-VSm;c_zGx+kF9S+zGE8zX5tqKzjQzr&?-w4+om<_{(H#yd$a8Z0E05U{#Pq8Z84hFY(^r=zBc-h;DD;#(77FfX$R4 zQ#ucKrtaCYG@`}K$MMX`(;jJfwtN*2s?6>k;j<-cGliMr|4p-@s9Z^Di@LWGrXFmZiiW%=Zwzn?J5rvPyc|2F=#C6+aA3QR&9yavV;F^ z@IRgvzXwl`Ug)i7k!KZm2Fgw)e?{ZYm%Phjuk|33)<JakVZxbOxQ?>9mVCgG^?{ zOsF}{c0${U=gzxJ;^{|2Oq<9qb69&Zpt}t`7}Go_adIOzy%oI}8h4QiodP@AZ<&A_ zPbmHfa(L6%xxM#vqR5+o#q9X}c7taFe*Yof5xFZ&%$?RnUB$Y> zTMg}zv^+E};A2*!TFg0EVrayZNONUjXdFQfCxT-G@{Wg-ujoO3E5P}yx6r)};60Qr zp+1UdeD0tJ#cu_8V}D|Zyq<@~`pEoXU9lgV8+peejnUonX{+iub@bZ}-Z7uZ4$|=Z zC|TIqoNra&-3sf9IXpEJ>ZP~C$(J`(+Csxx@+cbm6J6P1c;@Q&QR?!32Yk?P9(YH@ zNwfDijH_3OHZg;E8>V8IBbz~-#lAWzqftK+bT-SHW0?l*|y3TrU$ zh%Du8=g9N0e~jHN*g-D*=7DvEr_WYc%ZZxFuz{6{ks!uy?^eu07ZdqyRh^cM?l(d7 z!8)MOn70H;<5|AbQ*+S$tk8ozbGnMx%mq`E(1bpi6m3hxxqgf`XQ1(Skn~P>P=W|x zhH;u*h-kAPJ6W&(iOzOX{bXlnGt9$88^7J42U**D?_oKWdyF=oIdz1FCo$eA_$REe z!ctexM%%N8vuMx2&9;A`iOIp{&dK~Hh>^gm!P5n&r{+>CEbj~C3Apl`IrXr)k$E** z@>I&n|3T`bq9JyRt7_Oon_K7ohSlZ5Qxebe%^wZ;D?K?#Kc7Gbnux{pQs>0Yh$iu7 zL@NAz6*7(v$on@;1oNT`JEca%lH7DsR_;a{C*rH1kp?d8eIpGgw02VCSs@KiBJJU0 zAEbZWiM!*2e=G~yEP$l?u|0}$S6uzc9)Dmr^Yy);+evd9_Hjbx{l59=$Gsy2flu3Bvicm{>C&77lOFYQ?0!OgqRE}PeUxJkTspV3 z_e~wRXwC|6^{A@@SS;RkSqP1*bQscb4p5)Jys5GSIh+_e8}8OAU6rC)C8w zU!$bf9iFr_MUqAdx;lgpiD z$Y+_c*de%^gQnc!2=$W{Z#{Hnv?&ohXb(1>2ak);>lrd-oESPAs>1tW@s2ow!E4;Tn&{zzOljNXWtf(^tQXEhgz53AW>PLShdkhB!~ zt_vsS@xc^SyqnO{JU+|NtEUUOc~XlN)zE}9_hVpT5zm$M0FtJ~yDLJaHeBYT%ejOc zYoPHDI$w`wuY-zWcwbF+@N^+5=-!4+=di~Lu3zz;Lml=gfe$7Qu|j>% z54yU7t_niG7Hg^^ku&#wJXyr^W$467TQ-oVHq}*rkarF=tz^Z&$gz{^C>1m+L7_e? zN`j$e_}C_%E(D)zLHZ5q*%IttpHEf(a#no+ITjmy33)3y2z@o zlH;YDcs?tvaN4aL<&Z&b7KKZv-K&wpvo-B;16>v8Sxu-^#^zQP+mPUIJ_|vI9+Vqh z)x=M0uv2y{YP4C1&F6q%Px#XCUKQT22N%Vlag&I@3NA#AxVni&J!cWy74Y-a(AdGd z|3G6gz9wq&Vh3r|f|hyk2w0fU^Ev3-QYdot#^ zmtK4_Ak!T@TRu(gp99p?Koix`c4p$0$dt=}ps^Esc$QfKUDZRH{Aj}SYiW3Xw>HG~ zEJ2Ri!5%r84SBD63GdGixbjwL4tA)+C!!_^kwPz?3lB@#?Gmx1Xuws&V2=zz4(;&| z*xLxMJPo~r8qa$4!R^>w>@Mf8)1W3Fn(&OUF0^ui%+sLBSh5f<50D4%gG=pMpWnst z^PBvu=cS%x>cz=}COp+@0Co!?=>_Cp#SZh>XB((7CRRd*hR`Sr4NqLR1Ua;G-f>T%8Zu zxH7t|2c<&95~J!$q|k%bfsmWzkCHrV5H#Ug=Q-@Li1%mlTR%@8a#U?3sR=hRs+vD$ zqs?W(&+lR9D!g9@^c$}(6IGY6!|Y&lW4mW-HCa&;E~RlQ*kd+aEJdrHE#&1%ZM0nj zd}RxAm>u+wmF#vAFD`;M&6HJx4_@J4Jufo0Zvut4L*-ICWQU3&8tH?}f}~4=f24%6 zky~7q!RN)*2Kby4Y`zzJNJ9^+$M3?y2lrwrGo>fXda?bU`9*Z+p)NU#9Lw2ZE^;`N zHyXw4pd2}fs%D2JJf8=*dypdoJBx}s=-hM4s|og)9k@7*9QsFPsMJb8ji-!@*ufQB zi0#S9o+aS07CeZ|+eC`BXyPwuEQ6m*3FxkdE-T{kY2b1ecycP20LG6jp(L6>H^ zq^$5H(^#?udl*ZyhRUTTbc%vPYq&Mg*P<(-;mMiRQEj-Wj2zxbh!ugT@P^3EP?uK; ze3rwyc0tBnAzZbLO zUo>$Hn`c7H)uB)mUW~iu53}4Nu(2cfc`mSPywc{`k@O5Sma)U4VDqa~1O?!)CX#qI zor(NmU1Aynzm!^G~%K;vI_(LXLBsa#kQ zR6tkpWO*IdoX-kliS{VWGxyL3Q=_ZhAyzDa(l-1&M$L*mH_J)m1eRXO`_kCM^eGiI zv{;RRJmb|7EV&FmSKuFq*&!n|s)C7{L5{S<-F@JHDf$#Or$fD~#Tu}p3|vHut%lFV zXyj0+6AQp)Ep$~4y+mBLBJ?Y|mvMPvNCe zoE5eBh%0N+&1k{OMgKS-(5)O|w4jR`(M6+_K`@cxwRAxS0 zE(8_FLS(keGy0ncr6FqcWAn#pYEh%VHFZ@`?j5UmH$z>l1VLteJ;*w;Qiy0*`B&r7 z9B{J%izNxQs5^Kc$H;shye)$ZYwBJ9i_BIw)mV{}nCCYWTw$&~h<_OUYeJ&|+Klz? zL3FhUImPC2e9+pbCK;w);NqSTZHxp96YP-+L|au@Yicq3Jtb8WYq731wf3;8Fyr4x z<+3**(}?CBEAM|8ZH)fq(3SOW%wk67EpQ>ay;-Lm*3@RPwQx9#*u8?a=K{K8Z(ts@ z0^5PL{~qR#_TQzTAQ$?e{h+r6yg{`H zt(`#9+`MZaRT1kOOT6C;A(t9g%`m0dzdlu(Q8693^7e-L+-!W7h~~HF?%b8J z3-UIZ(O+$w#fn1;k#LD@i__n(Z+-eNqC&E=sXb2w-AWwB|@ws)%4bL0=dU*!3A zOEm5`!Y-m?z0^#(8%@N!dwws+lLKuUdweV6ILq*Ku2JaV<82!AwSAPerjZ~SSgArK z>i3IkU_0js*1NO#SU>rl8Ed$v?9qUjW`ALaG8bLV4qod0k22`mdqvJ%{3g|oK*L$a z8hqLgr3zd)*D)S>mv}Q;a0l-jIPEZIdQ#tr6@HV-x#nCfcIVbyRy5Qg%yr^7+pZx; z?1JW_RinSR1^kv%3wA074LwNY*(aH=<6TPcU^NUhyn`6gtsK^YqQ?6KEs#T-JD*w% zX1rtQZRAAQr~+Qxn2-I1^FpyZl@(6njtBp6MMHQfjtBW2zkiu;c|SJJj#7f@YV02? zm(;{Wd(KIGW}pjiH+j3YIxAww=nX@^`RVthyxrtHFc*BCTs4+h&x ztX!1TnMvZ6Q~l!rzxw5>fhvA@-m)2EwAnwJ%2*rNzCC5MJp+;TqF zaObg@l9pu;^I!(#ID@3?kz{R<<2wG4lO4*!Whrdu6wRu7HETAo@@$yY72#R6pl$C= zU!!K)%G>MteH=~Le;0$t%KQ|;=4P0KSY#8ia~CUa1un~=%PMeD2pUPi=3bs|M2Z8% z0sD(WL7Q@6?SB)W*b`Q4N6t&ctIVt@50~XYf96o}9)QpF?6C`K*8bU$v^1Y$_>%Lf zlh9a$B%6Y^uc6JH>`{^xd9i3puzQ$iE3x=`xIGST(y&KCWGDa+>Db{kJgj5I3beJG z|LZoAd@zBa>cWhmAa61}EFdFjwIsGqx-Y zCt2~)+sL^W`m5k`Es~ssi+kZyF7mNLn;(P!4S~z;NP2~;`aYf&;!}{lQqW%c4XNE4=1qsJwX#Cutrh# zN<*GI3!mG0e=`<47i_8&Wx#fR_)G!*k3wAv+tG@s$N+yO!bfc-hsKeBitXs+7+EYO zSSbvbC6P5J9;C(g^Y%t~-OJyJ&|prUmw;0xy$4B;22^Zf)d8YS5_~&1l4|pu&`Si3 z!*I9(E|lX6G_oOSG2{@LX~_>KvD3DIyglgZW{4*0sw6g3j>{lHIk&;ZVLZtGI&VN$ z5ww{JKfVBM(Y=x1hXW0#6(!lD@c(EWz#^N29HJ&I+!cj~Qpk}7F6>M9Kx2K7<2+eR zKNguK;5GxexyXv`(AbI`cYLg1;Ie4Irv04u*vOi_33B8@Ptqui%+gTOEy1EkkRus3 zuqHN^7%P%uvE%Hnm+r z?5Gc|hf}$@j(_Ch-Eyob$Y0{$$<1c)zZn`^ipHDR?mYrjl4T}zKS2?(aj)f308>Bj0wow!gD9QyWrw3ILnK+ zO7NqXYV(~$k|hZl-49oF#NOt?8{ zD>af7KpTaEJ#N66`C&0DRtIe*A|9!&Vyws&^0jewdC0a##f#`73lvHrhhAt#KY%7y z!^b*yI1QKg!ll|Q%Pv_%9y`p6RoHARYuzDN&}CKfZ87*s9&EZ58I1oY(UlXCvSgSz zU&@TOw5j;t0xB*dXLj^p$L9>onfopDE&k=<2%1QZ70mfo1 zma=3*J9Ou-x8Z#!xLL!;$SW5GL8f&>sbJG9?656l5Rr8bAM@O+0(`iGK4zX)xgfeI(IX7uGqqMxRQd;QoIIpNZ?H75m&pyTPB5leKNkBvx9vv(w* zViSIPAz;(VO=VUU0?BuUN>EfR3pQ6$rz(|M<9sO%7CQ)y6;M!)lV~$5cBp{1t08Gl z>~R)dt_ZcuI^?)OK8{mhCsf6uAvU$Bvo>oNC!6_rR)w5i5^bl3hPBIL`Yq4aP7pVZ z1m&?uWi*llZ0aA@d7j^i|GSBF)^T!a#T(DooSr%}*^REu*H#{tpyl*X)_dL+K zo@^p^wOA#7ijZT(zw_7mtXTmyJ>9d;O4u}Z`PbonBX2u@wI00^a8s5QmDxjGUEsYq z*<8k6&X+O=n=41LAcys;RkbzmZfx${(#qQv_9)JHts4GimFL3Ohy;buc2)FU7Rxwm zaHeS8y_~(wm1^5F+sdFK2k3XExB+c?;-o!NL&MpqE42AdIP)BSF?8Ztr{}2^v8USf zjL`4pSa&Q5==Nke&UkCUM=Aa`cJB!mT^@G0Le-FmC(abLsnZIl*{ea-Dv)^;WQzVO z&`^%tNO}c1#IESy4VOufqZAxEQz_2hiP7eMo-Ip2=4CWt-CZr%qaYFMI>^++oOWAb ziQO0ps)ik$g6$3VSR81?F2+go9S^ex+X4sbVtt4>kC| z8`)t4$i0C7h?>gSOVngTo0qZZHmqf|vYt(ou)>R~i_MhQ zf%>4aWHoY}2=%NzdZnOEd#!W)`#PSRVVw4PUQ~pqm4ZF&a!x{h1@`o=z#;fdgMXMG zw3t!V@9Ih;o(y;jpB~S%Ix?!7qm118xxJQg*V$+h_|&Ea@UTQgRpqqnTpw)i$#IFW z!j8~R>Hrej+1gcF6W@~%+wG*3!zynLax4$pIu&9`LAa<04ZAg~qj>tDm+C>u0vDyQ zh8DFZ@GNHqaxCM&hoO<0e=o{&Bc8qQStPxaVYafL-B^C!wih#B$DYy7b$O8E0@^et zmJfRr09W1?*cJSI4RUC))Znc&I~a{(EU`v2<2z?Qlc1~eAg>Y{G1uy2n~`z_TpkJ* z%Ncg?1kl>WTPW(=nqXU~OU%Y4pkTHZ-RASyDVfpDQP!Gf;|g#qZY~FX+tF>{FSBtD zB-BInu?*<@JkfMLnsQPtHm!Y1AV&ps;TgzPtfWm>q3vzp-y1u5Sy2I8n4_#UjhZ4$ z4>^UTRwkvfmvaJptyBE_8dj`?(m|rX8O9anN%PmNLEvRm5?eSOSn#f8uZ*iGIo>?dEBzD=MD~HkDZp&+I$=DYY{taGht>VDa|5ACduUVVE|=NCELDV0 zLFBv}IqcBg!>Zbf*Vt?qQvyD-1}^Ntm2?|BoCVz(*ugHwJZI->ujO=TOVGqs@>~wy zEyar1Gv35Xak8_K|DFl4!l{ee%#SuxhUmVFJvKt)5MJs`B@Z-8z)2oxI7x6$u#V48 zR;a7{kkJmrOlizJ5j6qaFXCu#b|Sw9jx0o!lzu=NjV2725R)u(*aj& z*dg|eVlzKaorvTLwdNUU$Yt!no!dymE~g+`6BVL+8~QXGD~BDp{hYYTj2v;Eu@xG7 z(Y8|;Gl*Th6G63kmN(6&yO7u}#~E&Myih;R4sM*2$%WWGNSB`+P2_`vQqa++dg&1) zkV{c>jVdlHPvS%*J969tjbd{>T<%3zH?UY%v{{5TP63RXhoP@Uw}GDvWI`uY1)$(8 zB0cE7j1`=j*ux(SwM_;jEeWSa8|MV4dD9-D;|_kzAI^qyA%k-S^L5Igt9|Uh z6RRAB%Qz7!%=?Al*4kem+sF9%2c{lJGebCvb6Or?vB0ZAi=aUne|1aXX#~y4Ry)+Lr^0AXs03+UZ zbgf0~=5CRtvcX?L_Hep%3%d5LPFIyQ&dHqC=|5W39$r6p7NO0ZDP~3vXNpD}r(nm> zfvDLYXe4Ie9N1hgvWHxI25m?5i>r$P-Ol3N!C9TrW>2uF9sJqg=UJdH7tZREL*qF7 zZH7yyd9Dywd62_+C9afoCtNs3J{oF*j6u%A@S26leHHyU%~PMo-57212QG}<$;o0z zdEyjc2P>V2Sp7ITEx=AjO(%vhx$E&krHW-e(u!9XyX+4zJTsS?5}?uL{~-|GmKfP2>Oom zCwr+){2YLWex3^%V@!0SauM$JVk5y}kZ1H)SH?3lj6S%Zr=A@c^UP5h;Zt2{(|hp2 zljzD0!^mv6=A>Cnh%9I7PN;VAREtIbaH4K(FUpGa$Z;9|wTCu84k}V2zd0yQC9Ou! zz{M`Cx`Y3o#bS4&jrEf;&qR;KI0S)GW$xeJu7`8$zz#g+*>*^_GuMM^S^b@wK+m|WaB!6+1vWzFmgJ>C3ZujRBaMzqo(D?c(hJvtKh z5S>=;snC$~SF0bRg|*~ScG!wG?HO-VRfv9PyhY%~Iodh+w8FYW!yII#SO$5mkE|uF zkDTl4V=3Um8qA8;Jg29NZfi{|EGNx5**%`-h<~$`b;kzw*vH?ui4_t5PJy$aIV;6I z?BHb7d0(t0|8JUor(WG0a1-OI8N|A}IQ}Yn?VrtrcE`@q(qZ#h+c-xv6Dr5XfL<+j zXSy2a_8Egs55c1+N>*`^3+qv_8E3psXg9H91(L{TiV#hTf{OfTLLU7=`{B@uCpa9Q31H2Q_oUeL*y%UQWSGB2~l(mbMyQd3Q_QqAQ zD~$p?w_8gUDif#ntJ%d4Bc4?n)2styWpWhH($7WzX>gS;>|hSEx=D$>tTnCCjGESH zu{#j|dTEk??(OWa&ClVZX@bq|4k9LQ5kKOIs;3XyJVUU#_EJ(SL8~9@QDdUjXcnGC z^c%0tAaW5a6JuhUkYV&vv2C?^5?z?*VlIs}agp%cE+-k0z_=T8si%bI>paL{zIJ+K zp4%6Y89S*Pp(c)}LHUArTmth(O>>k{Q!cWDs90f%Olx9KTsI?$mASFxPSs%KzKzYb zm^)a--N5EnqvpB1Xu_$amEsoo*v?+wNw6|0gf$AXQ+njMh9=BF_Qsytdit!7DTy7S z6IvxT5AF?qZUuFxiZdqKGg_~147xfUP$P}l2SrpILl;`qD$Pg`qm5D1lghiO(d@Ni z%`7t0Bw#nzN6tI!Dy?qJl;;vMjFq6-!83BBrZt?k(zpM@&a*;5*qjk|Y|**qnW_X+Hi^y2@&w%3EK z1oNUxd$AbL+#%ZhN~n*#8BvH8_lBz4+W<~>oT;1#73r{~T*zhW;OFuD+6-kkmI`g! zqZD9;GcJ2Z(QW^I5S!md4*P4fTyD59mWYaN$m03+<)ACMbgGgQjuYdh)&$$prO3P$ zJV<1U?yN{{Hzo}~)>l@%*`aThW^JDg8g>{vg3V(mWldnLaY`nx>>tFJXaIe-A+X833sDwI}m%{`_Pe5QxCENyNsj~8;@0~$_`tnGIO8Yjs3A}<$D zrQyuoF8?qzM1}q#7wLl4z4hZs{#E4HLqvsrsXbvP_K45*Qhm%FME7B&P6S=^aelZI zS1}TZ3i-5iO^Uv=vwz-Tb89rGM?N#|t^Vy|%rG%lI42NS@uWU_ zx?CP)tr=fk6~ZFP*N|S%XEzm3^s_TH6@WV$9Qz^RvcDLK?W#=1wEr!}~Oe z;36kajc8c{cJ)$s(9@m2I^}ZSVGnO4h#k1`Uv0)%kp*oRV2!iUBzS3@1MFZ2W4AV! z3*%8%{&renevk_%ZB|E_*g+3+He}~27iQz#(9j-Y*ZHe;xu`G`Mw`d^k~Nw<=;=A2 zku>B7Bdha{Q^;X$;|}(|+Qa;yww-c0C$ra*OZ_}ITv*SFJS$D3Vw59JU95i6g_x(G zTaB9M%-2@-v3`n?*KSQ7l=M7)?A$(1nw3;9jT7^Tyf{6wb9El*%*5J8|F9Z0V#WGN z8lqP(RhzLsl7{zbyjf>tc48PKfpaqRgDc`4DZ8;E>>#@1#N4{XiJ<*;66}!|4qOrE z9iA>&fmv%>A2|hc(x!*F!miVr+0!7=n}mIhyT-*kxl}Ig2xEt0-I1TCb|CiXu^Qb0 z4-pmGBNxx@ORcI?V-NF#$g}o02YDVEPkTJ8Nsb@K$&P)iT*kX#p2T=Qp`=zOyWqi^ zz=^td7VN*{jMoWNoI09oosGs$%NeofjQ8QCo-S+&n((%yeUcV+_9HgESr^ZPoEh%M z9?rwF!>4tHU8fdRm)@3)r&iA5%!6tx);6B4txf0%V^wVxXC@RicGuob_55xnvK&Si zandaM?HHX6#nWl;LR#BfRU1`fE_Gt!bV(X3;l=*iTUMT~*$-w9$h1R=C)`%e);4zN zMMDK;zK(MIUsswG-U_0Jc)QgZuQwg76Rq={;6_Z8=LF9VBV&kYF@bW3RzF66?*gQUe|hl6fzxa!R3dXTJRAi5o}$a8 zr-M;vt5vPfuj!^+nskfC&<7KPM6%lJG4%ZJim?|i1EmI$p2&NPMfVNvo#IV zh$x+as7R(}da~-viSz$|MnzObRn(`GWsi!O2uLSFjceX}oFzLjGhK7N&1jzSjAriC z4gFuo6QtR^0RO0tR_?5t@jvD!(B{J2^dGZ_?zE}6Z2N!D8fBf&2e0mq#i*#rq%cJj zX>u`Bx)4oJd~QlH>t;22Yx+g99A2u%>y$dK>QiK;nx}uK(y;xa`YbYdQ`7QR+{UDr zevwJBzYJbI840TEvrX*h?YuP*637pl*o0TQYjrBck97X;v&QYi$G)Ha;TdM{l(GkC zQ;kbaQT0>}0H5Vy?g>lO6y;ianPRb8g{)I|T37c!4%>wlMM|{_`33t|M<7j|vES!= z>IC8k-eD>ZFHy@@X(;PdNBBSUOdW9Ng#PhaRv~Xy4}q)d$am)nh{bkUw|)ET}#PsFf9%2&rdL>Sx!?|^7x(0?1f zh_xb&T&?BM-szWcjZL31uMP zjK1nrvWjwe>_xZVnz{yz-#0=AYeEj`GEv z;j=?l7mi5x)qGzb^vl6*oKubJ^U*&3^kk6k>sdpdHmF39}WMkjX=QR3wo=~m8s!dhj8GWez{CGU^{n((ojVI)5 zdgwYB&kudj=y$W0$%$L@$={A28YQbAXNRmp4}>)TGCzNv)7~6uznW8;&6aikH2b|C zKPHRM=NY-!`H|*>`Rt$b#QnqaF3s&fK3hXA0=IEbeXaY4#xBpw`XsVa>?l@Umh)hq zetVqr(^*6QedXYqvS+6t@8@&VYxBvy`CPr@>PX_rv4;%&hq<$iP+!+QSk2|5T5*5Q z`Jcx<^v-02eNI;LYkPb(Ki{8eOos8#yr{Q0v(=eOpZdi_6* zv}7$ygp)eKOC|$D=vl*N0U+9NRUIfA6sF&klJz1-ton&Oqnv${D<|BG;0+ z^0Dvcb9VRp#8MgI$%EdXC+PJ1Sp!m354<-|KpRyT?-jVo_p^!$7Fo)2{&Co*>;P{5 zJfD6&Yp7=E$=*GzU%f*<_s8IaJ-$CYOhx_USzFc#uk4`{`Q2#iyZO)G&V0~0`B>T7 z(~*%URF|HO&Pm%E>Ih^dv)zh`4!G=qWqv+Cx93FiQad9lvGomro( z{yP8d{ju(+vvw=U<}S=VA=(%7#3yr}Ds4pvb%a-kv|-}k<|$d}`LSI#@#Wm)r#Xeb zm{Wo%OvlgN3uHHI01cpd|2L}ahF|PKK9@rB56G<6%lLTC2vQX zd@$Z|YcvLJ!jk)Q4^=V~vR}-PF3OG3*t7ZfGFR2UFfsn53RVUZGV_n`=Ly*U(MU;* zf~$01#OERR4|!L0$ts|c-oaKg@KyXt4B~^3nGcE|=6=*W)CsELvdj-gAF9NngU_o! zooo~Ovt2AsUEll$ySaAQCJ!>-ftNH-P&GsK^yWxWH7{wKLs647LxMG6iXpFa%HVYd za0cB6IXrp0$KS>_Zp}SN8>{~3+*YmP#lZvJ^NW$Xnu}9l1ffk)46kNc>S)j(CR#y! zz|mBXRG_f#s`6};)i~v%zlg8$iFa572UDN=Z6x6qs>J`8b71bTM?OEy8jx8J5tFRW zhUMKF#8S8XZnXBpfxG8N!c7*b0^n$GXAkfBL4P$zl{mJm?k|44JF+nMJ42_B4fdjhIraS#m> z*hA%1F7{@gxHX^i5EU$(KzD&gFiA6QSLWYC0(O%mL}OU-!`y>3*?$p)-JBbZsa1bF zKUHuq&d%zVe(+t@RXR0!@#DywA7e*Y!^b1HcLvd5cX;&6=*CnJ#T8S)(}fEcXFsc>vQ;zbwdK|=%-no zhddmOnp1H~c=hQ#ixa>+vHHh31EeTEkQM94y0eOJk0-D^Q3@CBrtpYe@xf#z;D(DUo;_qiCX0N|hiDWYv0X8W&auFl%hz)X{0S$( zo-ks_T!wPG$E7(XePAx^iX>PSBrxga9v_~aQf^Gzs$Be>7nUbs_I5FCDH_XA>I>*1 z^3s_7r*k85a63+zyC_Cup13zFTs>GGHY)~sLJlKa^F*iQVc*XVp4i;?{gDw5{b+VO zHSih=#xQmyFK;lRFces0Z?tzPbICsMA zbvR^u^6!_2?Z)DG@#O}=lJ9&pzwNjpDxO7r7y=BE5SYV?;dvbYV?Rd@lzde=#N+AL09G;I5iK^r_nip?P20)XM2c)Btcr`#xgCZl+DSc za0)z9kKFGk;DZ<%?7lfRf>qz2Pm6>272L(IT0^!bYYlhlL$0QeLL=qIn4+AriBA&= z;<-FX&kFj7?attx8t88YS*Plgo5G6fAvDJRyQvIbhSFpadD4ga)2%rN+Y5K;+#Il; zELp%+e)`G$0TM(b*QvWHjywF3Y6cjJAR$@s|MmF8;#k7 znoF6j+!s$&N23qX``vavLLgGh3af>ig}!Bk}*mANzeAq z>|Z59RbRx%D9mO&8m+5qlEtTUR()ew(oBQ9U@qeI?fDdP^Dy-*XOLltAgn@+;lt*N zpk`AjFi}S~j)u=Vr8?Vhvj%=d=TDD#&ib)#9mjd$>BE!y2fluK9Y_fUIoy z@tiW=E_)W|u8mc^n6uIPPjd^rq!moE$S?9Sk?_Yk!&c(Tf_PA#&ML~pnw63H!Wwx{ zn5Po=!*NfOFql6!3-ic=AN$-X9v@x|NmNX%P<546xJMCR98_nPl~!@b;^auG9o|Jz z1Fl94u~fmT60?4osKQnKR=%gYM5h=pz6%N9Srsl!&=b}D@ebIIHK<^HKBv5UW);67E@W2i`0|J_&RJe6e`XbMl_bOumIo`;vSA|2Dr00d zcmAY#PD$x&Tbv`Knh6Hs$)DcuG>Tt-f*&aW~;Y?|%MX)?sXZ;I4rb;Het`d!d z>F~*(F+mKBw6P9cQ_N4Nc&1&{HA(yKVY_^Uymczc8z<-;CYcsKtD4cw8(v+0(fpHo zhjW_OG`m{8MCRI?ZEzJM;6ZXN6%`DO#_W;KW7YBpy@mJ__EX)adSJEFmFXuCIU3zsX0{UPUpUryfWjLC1yDzD&)6tLW1Cih%=S1{0h*tc ze`BlVV(OZ4&6o+h!n#GV&*n+@AS-x;Q>)cglk_=1$H1y!(Mb8XD1|5DdGb9qFnC2D z^>Q#QxQa{adWj$R=N9#>pk@(-Wy+*To4#Vz@}&1i7WNQHu_L_1OejgvsP03(w<0>+ z718C^SOp$Pn$^Hq=Ie=E>i()WSOrY1&gK+m0YqcHgN>!JZW@K%WQDn7KeAH%ima-w zR_}nT7#6Ghbxujz_Rw#VJ?k>6aTQBFkt{_IXm$C}$D{c+yn8s$%4}tXakvi-eee&F zv{QHsP&I>Hs&Q3`vw}JtPH=v{6>qOwq|VHCc?U_j2U)~i;4+;*n0u%Is|=l=lc~x$ z$4MXeXAis`>rPg&)v7Z59OH!~YG9Y=9%O+r+&p;uuk#5`fR{g?Q|MFb#L%cp9BdbD z<%=Q^t8fn$5!nMTW_jui>Qh+v2lL%e=I7SvOB_^()~8{a`jDRR`JH+0>ahoG$KiB~ z*cDl*xZIv6NkZkPeD2{$MVC=NC&D>p8htoYAPY!g%GXWpq9&;~ZDxnQ>Ro#RFITs5 zkMm>Qviu+CjO-iq*E~9CPg2t1`?12FXbOGUb6sBac6EM zn|pR(#e+GeEC>VRg}lQ(^+=x16FfBI%LfYz{Wm>WrJkd>^!a49SGguGimR8_nEK9wdKC-Ly;hb_#UEl16O|VUT7NXH8 z4`VkpYMw?;=?U1)ZrtMW-1G9B4G&axNfxYI&sAppdhY3Mc-esr9Rqta`@)+Cv&z%) zFudK&PyHsmoEMS?=5Ou@wu`kgDd&XD(4BwiLduh>vtg?-u;1o<{KFdMt+2)lx(oG4 z_<1u8<;HcV%br=kIx=2@w?l$kvx|GOEL`-|^=&SK<*`gU#k!l5SHq<-agL|+QyRhY z)IIgy#JQ|OeF@%J!P{QqAWW2BunOIWkLPn|sLP_)speR|7B>5w4_+E+^HF=K=9&L6 z3!r8q3f>rLtCy=uipOe>RWsCmnnA%COx|@0QxVMqG>=0P^{npC_qU?>?wn0-Ttruk zfQi@-+=QBP7@UE|oFe|@6l_zU5-zJW;LOm5m#Uc1DI~!Cn@wUBI+@un`-i-kWLc*y z=k|fkH0CDa98RDbo>gE!Wt}+jsZ&$$%{0MBb51iR?5bHAY?T*pCWYI?pV*BzQOZE< z;U=;*I<0P_uA|PTx^#DLDMOJn>VP+IAd1N;WzSgN7xToOk%eB4DHBW+BYtOWsm`-X zGj1angPLxt1H#8l6PT*Q;$$FIKE-1*q3%&96PGj#AVy(W(8QY!SYA~CY_xbS-qWd@ zswk$h<_&QFox$Qm^$uVOor}j{(@K zJF?9Jcq^xg9nqud4sZU5rF<~%FK=xcE#x(ugt?pDfECRns7X4dTugRdAHNDONzfPn zk)f-~_{|yhoOlSkDmT!jcTTx6X_lckVI^afiDAt=2x`hHtIAY!(NAg?K=(mr+oX%^ znt#aEnk7Z4JMXZ?Wox$)jqkJ=?(9e_6C#RXWCrR zNZAQ`Ob!zl_ z^FmnStsJ#JYrxgIbGo|hrrB`u!y7YjHEEjGCrkQ>GnnE1;&>u{Wy+B( z#0UQ2Ef4lS>TjDd5#?}6yaa!On`{rF$zd=_ z{-J`!c17cOMUhgrR^M1OHuEVO>u_K$`0RF^a0asg_Q-be*{YV+h@C=wkSCD_S;-ky zEwNSHzYIi_;>9}&26=nJ9=%gw4;&3As5Zo|mf6E@H*F%pTjY2J)+|zr*ZKl{$RvyE z5shGhDke^et(xbQ=WK?d*CY3BBHBGg{A$1^h2>&?Lgpj^D^#e|32-p;9qJh{uPRw+ z0$0@{SPn@v!|t4M{~Kd@JKd!A;}oo0*D7A##Iv5R6`D-tg(@y88ZxP>`aDFIQ|eb?oKtg`&*@&% zs7aSH%4Y3NZmXEcT%A)@M)uG=j!Hw>!D0yy(#cm5tP*D`$oEZQIcfPdAJdzDarlP} z-J}cj-?~j!rP@cMasz18?Dy8h;(K;>ieqpL?j8G)+()O|%uG>V?XE)e*2K9dhWuIgBTqvTiDy=^jm^snDuxLK_~& ze`q{3au26qyU?G!J4GGxu!2=UP1!RaG_k-3%W>3S%NSLv?U5|h0QiSmo*5K%=4$fv zrS6&C@ecY@i7P&A=1ZDt@^s3s>K3V@v0L@PI^_J=6J)8nByUyY<>znb6lRyy0OUA2 z1FC&yGt_NlrToVk=sfGLc3OwLOslsNoPw-$$jyI{gbd!yTu7k*t>#E$59XfmtjQ!b zN&ex9OQUl+3XMLTdpLzWiM-VSs?Vy3R0R`1bbYh#FjFO3T?QkN&7Gfp^)H&HY5MQc zoIwpBdBas`Q#D*IyUqc0SIvO_*Ur{ZiBXC^zv9{b;guz^}pX>94o2c0@UR9N;rXqs)+#agkkXsaU*CuxC z;S9HrJxIF<1e=?ck^8F8emLh1DcBXfB2BSW?G$2}CG*xltHD)OaJ>%$+s%-OKqjK; zd@ZktZ;z@3b%RKgW#Vvp3*sC+YBDdYAWv~{`$?zyKz{t()r7I;`->p zE>P7wo7Mf<#mTzGpz8kW2));qhrm@4L=^gHc8C$k=R|93;F|Bwzc=+JhHae`ibHFjh-a5N^tRYux8jT0WJF55HnD2iy&tSwN zrC5sHsLH4{T%A+6hjX5vo9g7NG-F^gsrTlad60LY%7fI)VH*BK>nhDU2kQFgN8TnK zA=;xk^PBNf(H0`f*6L2fUbw7wY6bd$dM1JE96vg=%C6XEu~bb)yskr`=cEhy``kjW zvmPN&bPC-4@%$CfdC&)EYpA8MZq+(1 zVgChRSs(ix~{`^VhWP0TUEBRVrHsh+2b2GL9weK6;UVVPn#&#w-~ zlVzPWc5{Akw9AK1_15a&nJ*%CdN?Nj*Y*QTU7FF%_a^SyrP2kz1S)=~;sS>RRi0A3H z{xZ+S(ahq@#^9>=2z6RQ#rk^?4WETsqEw8RzEy^bwRxCK3)keOI78CbBkbJ=)-R`o zns&ehP4~HJ(U|3JuhV-XSOZRguhLfiRCu&q#OBpZhs#{c4w`+fY7cG17{68c_OS+B zG^6N!7m-p10}FJ(or2}*3TK%*mHI5+qhb{*Ojus`kafoW|21p4hdMDY^}B7#q`F6Z zwRe)mb5yY}lttV3=Z0kAiAC;sHy?^AuEwAZPVmcd3a2bSsJg=nmU(e> zDoVMhGc+NxEUqV@W(-RHjVFfPa$j){w}XgcUCjec-b(MKh!91`b$*ZL5NjM0h0*>Ru?>lqS z>$Ap{@gJEME*cW>LOv#^yD`tmIp2;~;R9uSaI;yK8*@%_5P_VNUF|(C%r{|{d&)Xr z%@df5yYdgYEm`m~Op*P^JMeN=(M%5yN)}CkU?zAuyV5U(inPMVFf1`$d=P6P!3VPo z%W&6s<|ZZ`=}X4wi7Uq`F=|l`JE9M9N-Y92kxN}WR>^~2%pPv?>->lyKEF9&Cr#-6WY%~*|9o-m;gs~@ ziDsx|g)G|>=jUb^7&MWWHp%7t?)q$=!0gTQlY}!|IyBZzol`{j#99S;u@;ekr;v=f;dC7_}!Eb^C0i-K!3K)`q}@Rkte(1 zrM%EQ8Z?TdiF0@$jW~xVAUCY(2{$#>0M*EvRhhO?p<3*IefSv5Zw3CN3I<1bGTRI( zoD*Joo6#O+uCSTigjcMezOX#J-U>9rOJ#iYMcSRB9oR2f`J7FNgE(Ayk~Lt3KK#}B zw%p?Vk#;M%DF!A=U7EAW4xIDd(U|wvWCz$&b$_`nyJi#R_(j`?^MrV=4!{Sk0l7tV z8h4A0bGmDJ>Gj!#)rhvRo0pdP;-lmY=F6NF<53>=2R9$)-7?*r8r%NNu%eLlr>( z4ws~HvcS<~LFM@B%y9MPJR!>Tz6-0tT(An$eO2>BbAQDb%mk7&yVpB4>K)=kaS*G3 z+hhg*%Lwr}XV8binMLcEqW;~B!+Lp86NWg0+7ErG%IHtXF0fgg!1SxP?_>`qh1rcL zR)!A!RU(>{gsdXI?sM~Kzm9d|%%)J%2fO0uapvY@)!apVPsq=!YpU?7IkIk50Nh4~ zpO4}GCJ;Tr$2J2I<>&(N^ z^VGojPqn&clbQ)tb-nxwpo~-y7X|E6J@98*MUqv1MvrIK!S&Ud~+LCveR<3Gk z`n&hmWazRS>_>)=J;4e{p$o#g^~Kb~*)~b=An`-BjwDnq<4>@}8F)H2N)~c4wLI~j zZL>Ui(jNyNxrs`|DW@bW=nQv@gVic}mr-}RAA9I^LUyRhCYp|wQ>72P^Qwau`@rMI_uLDNm3CRE*Km2&Nf# zxiC61ZOJaG>^KGH;t3oKE0;fX54^g{rSIbm<@imxvkLZKtRx3K5r?ZjRjmT9;x>@g z3Q(1->Hs>0SgA%!4!?~gPB{u$;5P9mvM9own{%mVh#-);tg;ovVljx$t6G*{i}}xH z1^S4W$I-I>T>*O%$JQ#c8tnVhfy}f)r(zK6)*FZgzB^A~nqrVxD?{lfcr`CCzbMZU zW5ghp8&$4r^H0)dH7bVI;Dyx`eR}PXL#!N{sP;JprpOQN5l>W$D9Z>Ofv-ZO>>G!325c0E@|!Ky!*Rh~;rBVYtki@PyJ7!gh#Sa!?SZX|LaL)! z8rytteq@~Sa`70q$2*);odE8MQECxY?d`(H!d~3nIeBTA$$R*z%8eXFcFhY(J4|G| zxWC*M$6~o^9+&4%-qxxDZF z`AE5JP951E&Hxu>DG*B)z)!X*7sD%Hdv;ST0{5pe%$!wJ5v=Bp|D-W~ZhFrt<@oFZ ztJa@}yEyM3^P@}R8C|RGF7R~RrCJ1y^C9RT2U9Ur%f^1nb07f^E1z?QSOs4fO=&|! z#WmxrCL`oh;w{_6)9fLKhiNR=Dav!IwUH+;Ene5{G;PLH<@h>A`QiE5MTGH8p6nLb zCTZxd;k@cHWKq^xZ%v#dFC2^Q!Ea}fqp&(W&kCZH7{k7;fyH6f*6)2tlPu~4Y#Yzh zeTK_&DR^TAowF+cn1hO+Tq;R~S2%-8+rB>}N{J8T8LJlMOxvsGnO-u7C^wF!iTH9Y zvjF_C`KBt9>?$rP&S6+;)zF;>vR(3)VfXfEZ+sO?d;2H$U!{${R07gD4cfp`rQPcRpV8Q+usUS#|ZwL+QACyKDa}_;b1Gx^|i{c<)y5uiJQ7_ zygw=`VIp&pFMYi4hLvc;HcXL>#OyX!5@o9R= zv`mvj#UQbk-M|99oF<3%obnuK6z}H6y&GC*th$VfIo50sb0|8MO?VW8a9;9;6lNAk ziZ<9)Rd)=nS_7*PWAwFn7{n4wS+{etELo*JnhPRL)2%Wkl~H@ZRguy%Yu4yQ;cJ`{ssKdf+%{G9#!T-~PM%*Qf) zp9l4}7B6KN(j`8$r)#9XREkieXZCWM| z!J4Z9iw@O0%qg14^!o{3%>7gpRkNKEW2pOVRt8w>cKGhna zYPD*dLG85ak>5^ZT2k$ZUA;3JaRPqs30)2`DAt^ZL2h2m3q7F%74xQ1wFoPi`Dw1Y ziXZ-F6_tc~wop}137W88Ymfq~fU2y@yHegu(_iy_v#;Ch(ZBAuzEtsmo7G==ac}#o z+vvKwM;Uh5E)vSMSeCAo8FV_c0yHrzBMxFtY)|!wMye9W;@A~^s&cU`?|aq3^Eun( zgJgjbh?Vw$yyUI(8xIsIWtIHA+9q$w=D zOg^NK=qB|aM3^E7%aTiB1<(X5G1b%WGi@pqBAK@|Bf>wL-vQ7Cl4D<44VniumF~xT!j1_QLOS z@Z+5T6%E}5=O}0NhH9N-`Ga$^E3&2$mL*a`6O;6Ko(#oog=c!NSq`FqT^FQoj~EsH z$17+|y}Nr-vWt!9p1~GH zQ}$ma4P#aX>#gJX>V=U;6~Q{LY$Dd-CdC-m&HkN&WvZS!r@0|LrK*nEM0Lw1=S0h0 zq3&8;+h$0hk*cKnkc<$c70>H&h}Uo#lf-^{W0+NW0@{dk`$RlZ29u}r57`5a;LJP- zGZA0lmC6og0!`R9A63n$gJ+jI`SK51udWFPLPg9Ti*pLdEWW6R*F~w)43AXZWgxOr zoiX=h6EKe~Q~_E;P22oOd994GTq}9AZnkL#(J8TASW-OZ=RU{%w|3eTiutE9aO{RQ zic*jOV})1o5_lvBirK5$KrE=sM_GklD74~7Z|C1ZO*Jr*+BnJnLmOl*+Z>DZKw<;%OtIr^Y6FP_I94Y8jAVJ`^~J(_B{g; zyGQk@>$eN2uXaz_814gA z^W);VGd6FAjj}oU2U);fI<-bR=c6>HN74jI(=b^DZqI|9qj>_datplu{gF$RXh=~X zAIqZ;(N;B$2VpN*oLDTI!%FzDx(QkIIeu2P+bOH4IBE63kOnHNK3$$4PgDnH-DH*B zs3SB9%=XzGBq^TDuQ405d6(xL>=x2gacqjWdLL=2srQ8JLfw>?V+C+kK4+drW$E3q zs@I3*LMvV@BWz;F8RJ*hs18?$g59Wcsfsuy)O8PgJs}RlimFHID(>O8a?8ZTQB@UndEDO`R$*6icu0d0 zn=I0)XM21YQml15rT$tS3bC|#8W9HjffUYAcLCan9;#?!>B~7MWEDTu_3fcgz$=O_lAXYh%vf%m<=@YgyA5;XKlP2BL7Ze&e~ixR!(@5+SQBk9 zuc-(3jQhY6*+Uw0#?Da9#WS#iEJQhoCIZ3bu!dcUA>tf)@z8j`th6X+s+zo?&-eKT zyQ<&R8g!2P*IVle8Yj!0(z1g%S2Wh?OA@R?C7`JxR+E2~iIMi@`KzwFT{F=`)rAMi z8Qp~xAPIemqgyf2Bf(7666{b_$LHcCj>aY+ggsbxoVy4`+Tw$FElYA16|QU>{|ZT* zfaOBokeej^g#Eq^C0M4u7g<`v3D{*Eu8x!W5IWcE;X7fGDjLg%0ZtKTjv2+;)$GZE z_iU`SLRO_i;vS)H3zjJ_ zE(d}&V&M88-|;zFzyW9#qP;%OXa_b^-U@gBn4MWASzeg`Eg!T7Y_~@%kSBYP4`L;< ze_nWL?#e$fDCfWkL_ty56SN__b_UYsANHsRZMZq^S7fTcx4TKE{0t=R`DF0Y2KL!h%dN{ z&$Dhb4{9AS6JCnbK%?L1 z3Fm|+@C@hBD|HXLcr`z8z%AUNS|NVLHpzPJ4zCDr_-Ks4UCZ^@T~UnPkR{urZ+C4f zg8kE{Gw?wSjLpCrSRnsknLg!%yx0oOHmN;94|ntzb7eI>-0=%5h#Yr>lp(YQYF&M?i;6)xp z5^j2Bo(S>6RT|+zVks|Wz1c4Q)pSV6O{b*k8A!k?=z_FcgQV7G#A2tX(R{3X@M5wO zDd*_(41K4M@*qCyXZ?d5JYx@Qv;)h8H=-^7;9;I%yWt@(q*2kKN=1B^7P}`u-+RE_ zFmEO9j9HCa6y@v{@|K}A`I=pYS0RB@G(kllD&6!U!;+mtG$)Wdh*5Ns)_GCWBeWsP zVY{B;Px1p&7d@J!l;Kwg@a`yI#{WIl45KPR6DB-KJ}1)Ig(S;-RT@~9IEKBf?a7>r zb8?Gvm=KFzX~b@DwxM&#BEuX zSjsz8B%7I2)t3?S5ST>cW>9c$)exV{eDNci;6-er`qU{Nv&_Xj9zvVd$ja7unA&O6 zf1bc4s~zz&{K*PF$I77%wCe33EY2O8=#k|+2S2ro{5=nnIjdkbuS4S2h-;dOlQC9@ zE~o6hgN=jb#U{|zD(b>lmvK`3sTzWeQzcu*Sk(oy?g=c=Z<|vu(OC|md2-RA{6!bi zEWMhFytPgy{1@@{4`rl!EUH}m$9oCxp_<+VmYRzvL@M*+Y%}h{zRjItiu!7>mG9_J zz!RvbOE3Sn1D*%ZNRs8qDK`&tk}BDvoDRA=o9QPt7kmQKglUjnZ(1F?$!$@pSi^RE0}jpFZF&KdUaZbMj`a z`rS0K4!LjBgKXQxgIag}R`wuY!-~VJ`hU&*uo-8NukD-eO}RUz-t@j{s4iM>qG@E) zF#6+oH;vRofh6ia^+@E#vsRgO%lz-4u`yim1U?KFLq`_?O}``t_&tyk3y6Lm^u7*&^4o^(3-piHXX z1D#fr5hwfoH>@JOQX{JqiupGwrn)L$s`lC~R13u@9j#`~%sjA~Ds?)#%|Sw}Iv{o4 z#31)9hu2MY4xU{7%Kx`YePOKDTbpu5{qrVz$eUH9QUA{o^1Y&QllCgJy}?ws)!SOM z5oUV3D28QL^6|U(&@j)zw|J8M1cfBB$IKL59ro=;K@m8P@m29Zm8!=*G42q9k zm=)@!LOp&Am+fGc`n6Sl+JP*rz$U6%))Q9wamp&y5Xnu$3UM;)CQUwA9bs$0#SoUQ zbB43_9+f@VP4y+b!yehcHE2wQ79Nq+sb1csjA|gA)?*YYV^=J*>QJ@3YTY<9KC9Eo zGD89#En2G!i66y&%<#$y<;MAksnWbutcAS&ZYEmCURa*0Wh}1gaok^yuX+U0>ajF~ z5+;gr$s79+gG>Uz?s}m-sG4K`feSh%+odrqE2~Lk>PO{k{1_L7CUjm6jE~`Hyo@Z^ zcD!7qj9+2Bb$!{sJ+>nS(IBtBvFTULrS65_c?A2Nl1}ZRF0*fjV$7$`KpJZfSWO0- z*ZsnrbR;2v$BhN*V#=vB`^A7c_NTgl7?xtYSq;gn|a_rtcGP_7F81P zV!UM|4D073EUyko^`~akp-t16B(YY**Xtc%q%f&WN!=&hH4|Fis3sx@fi%ttDT=oG zLFy`SQI@0{i2Fl46AjI&&{_Ghd<&NquOS|6RtMwhR=}ascz6Zf>nFjIB6j-VWArJ1 zsF%|$Jvm^T-jP*{z$2@dhb8Pg_OF6V8`j`stee(Zg+0ozKeYtp?_X{^e3wTKZ7i;Xfiw$h`G%`GXusZW>~V#rc?f zu&Z*Ix>og^igIQ`#i$cAksqtv=)}Mhm92VnrZw|IT|~Bz!^wGzbM)0)HN4O{MdR2H z_HQ2(>X6qC-Bpx=7HV9qldRcQ-Bze*zBXXcJz+ydrv&748w*s4lVIc(sgfeGFa|=Wqg8A&QY^HTmM8o2D~JL7EVchp{^I zp6okb!HbeqH5n0x{nioM+K%kg{CQa=4%Y9taSDvJdL1tgkBWn$jB1BGr_Qrez-7Dy z<5cgcBg_8lKomW~Rh49MtZ5orCo5~{fP}kY1s2y#g5T^Ny7Ld+g=JtO5Lt*1>{=8e zDPEihZUu^e@N~AVssp*55Sz3{8WYFt0SR{23~DwlQzZ37(Nq+p@p3IyD$!W|rz#jp z@nTgITvEM5EuqSPef%=Q=7wOi2*JzLw{(7eS~K;8eX}rQ*IqNo2YYa(ow8*N}rRY zC@RjWc(Xj`AZya5&#eQvi6>M9)w-(^6tCGorl(?9Ee@|yjc#f}R?0)_Vbu9@2A!KC zI^4B_ObL(ECGiA)C6eL{>IgDEtU)y8AypDsu81zDgsVkcSfYAl4VeL6LFV|bILAMD zdK#l~C$K|Vj_=o#g(grFRurYmVv0dA4^K3W76a=I?-&6zaZ?=3Nz`B2g#A4M7foB% z*P>Bp5KHkEtbtY3pW~nD9M-sn=)nio0pJa8uCgkI@p2I=4u@+x13&i!Bq2k24pc0b z%0co{Rs*fPGr+^Dr?Y-`vmO$1WA<(#KM+5lJG;tu^B{YK-QiK^j7hqO&*L3xDsEXu zNFSn*Tl!ornq@&&8ddwmOxU(luq<5;I;Yj%>Q^y@XZZ&n=nm{As}O_Y{#c$BShu-4 zb&;@uU(i}OfcrGZ!t&~(kZ0ZJVo)s23TypwH2tLJKX_OXNbN_LymQ9&TWpHwyy=SU-tcp^6A!XIHF(9I^_S zSUs;C=9G1I&w7BOM=_r)X^fZUJ)*<8k*-^KCRyO+yo{u1orgh1c17C!)aS5595iE< z2a3gfOqV@wBePv(X5YLlFJ-$dH+^7Oa7Hw)^OcXa3Ja-cYlmhM;tZr2I*$V8o2ND6 znxTK(rS2PEW0$&XZpjO`kCLo>&ibJ)Kc;g?(#$kTd4dlXgWN)dh>NlcyNHx|x;wJG z{Y?%yF=^9ixXQzJ+2+IsdQ3U}9qhiGg!PcPGA+v-AT%uR5`&DNT|z_4UvkiA$b z*D42*tFi6Y7lF(PPkcNi4DtIu8@1<^tR=-wIRI5b)Z zoYg>ob3xhuu3-flB~P-bK3ffpKFV^~MR6{M1&^}LDh*jy*_J+{&uODN0bPVw@ztut zeQuAKNtjm*N)k>{g}2I2b%gw*9EatFniz5OnDj0Sf;LqYW94F$x=VSdC-P!(vbqhM za7$QGr*c^w?B-)>)CwwCGH^RUwcgo`Rj-elc;g>2|16W|iG(B}%ZV|F*D;qWg7jIv zuvr&&hok9bCJRqE0}o+SxJ$KenC1>*W%C~!kJ;uT0Tx$Q88%~3F?YBsI*63sN#i4a zW2Td`%;i4vQtuSNytVGFh{W9MPhhMnB0Imqhhu2uu(cohzln~LHC>tnrD3CchqUX^I|KwMBI zVP{!r)SHU@OZ}?8mAsN&!;``F(J z+*0L8_jx;PGB!A{?@pvo{i1lax+9CBPyM9*To-Y@jLt}!P555Xp&FM+r^bc3G~2`j z^*5@5!6R=bct-Zn+(fk_v5vejMgGw_H>0#h@d4(^;yr;s@elDH!-9D5#0u_VTGHoE zTD~XFS)q801+lyGB(_EKsxlj+APpbHXW4xWEsenvkrHa+)iGW>U|{(u51}z@)Y;WF z63xR2n6@<&R>RLoL5$-)Y_bd;AI+21p5zAVQ%<0U+k0u)j~{m^((W4UMkY{(ff-o?8WXitj=^l7e|wfQmf86VNQIh&78Zq{Z^>0Vy!vI_O38*AoZ*V8c9x%_IjI z3j695`Cyrpj1j8(+3Jx~K!WTxn}N*rOZgZE$V)xJJK}(8M0SP8(Wjb>bCNSe486&! zY>uD%yu1}7h~+tb*_doi&As{*NyS$6&R7;UnxEH4%>J8>Ef*sZc6IukpR#`6FXmgr z^tRQ@KFn3K2vH28`Au=A{Cjtz8DN;{6fBR<=PUCB-Ym|F#i1FTNMoDNiL+$})c|-Y zKW}E5w2DDA?v9&(!vQnZK2=49SK$#(5&n}W+v8#RIbF4bbFvDvsql&y!wTz*r;9bL zSLG?RQI(;Mo^nd3AnAC$&zrJ^MLa#UDxUN6(1tX~T!g778#42+ytMd0mTDC;@L2ij zof(!#mT=oG%l3FW>!oXFf#R^{)a_we@J0n9A57NO*{ar+xmqFHR;!>*(X<>N_vgLY z9v^g0ySwYwr`U}j8O)+)9uT6XkFo>2r3vu#K@#wom;Htuw^bKSmURwr4?3mQ ze28_3=Jgh2gRv*9Ak=h*t&GYC)!egQc+-zu4c63MNN4q7(y81aFOJPZ-p;^(+_he2 zc*OR6&Vy75Af7j1y<5d@)XD6j9>}&~4QZCGu_(8QdElBfA_B=d*Z#>0`iH#mC^W$t zFcaD^{ihlWbFINgXw>hWWVstDX+rne8n7bdg$34e%4#k+5=4p-^N->OzUvma(^ha< zBrhUs*j`pDYiyU;QSWp4yct9cOOB&j2Txc&)Zt+=c!-Kgk~fA{9+ZEuOiyh5fEFSQ zA1vatJ6Poe{8?_0tl5n?1~ti>MzL9#2nDbkF~3OHJ4jAw2R2)NPF}1c)MR~CxY!(S zWmgmzJCW;T&rNrxFv@9F5fV@~5Daa{( z&Ia%f7QrU0;R$y<$*PEsyDXxSmd~p>R$WaSRag0tC*mb=x5`ia$SK%Ajl{2fUUuLd za^pNXROLVFPw+@JgBP+Z=mEE%1Pa|*WQ?JEsO%I$ zSWMjqc1as?Vs$hzj7EEJlRo2l@jyDw{$)Zi6GJ3xDQKsD;FjnnGk5BAQwTjqxsc zvsC~n;V*rt4Z$jBgn8vSD%IIHeiU9ME6gVr7|Tl<&gulvX0e1_@er6uW6r>Kp(4Hz z-qbm(lI#pn6Aq99oxpC<8h44AvtIVCE-ylu)+Y;}hR$xv%juluIssmWA3*~Z#qgc9 zVG{ppD#&D!cw2?mE^rOY(EsyxwYPT4_{=~#D;dO>&Gh&wBKNt>lIHS2H5nCknBx?X zm5(@Oc%&BRoGL$jXpt3@WLf@gxmKtedZ(@G%+(3Vn_M?m7EP_N5v0z7N(AfXL97D0 zW9GbwM&p4ztQaKcV>$94oYUvUpej^ybZCJWgi2{tEH6jzw{yTO>&K|qZpz`ELzdr3 zTPM5{1!0=|k}2DQJ2WmDH*ui4#0Tg_tb?viUa*NGVHrlY;m(EynZ@KnGQs?f+_$G6<=kaR#<5`CCjCe;#jo;`mhTh_tPG< zzVQLyWW8{cRv{;Su?UtYLf}7iZk66D?w;=B6xlAR`{N8%m}s5-FN;gk?h!t-eKKd6 z*h`yz4z*}KE@=;(EoLtV$xqo)on6v)%Cea5+6vf>o7#yi z>6^C5s?Km#0Q#Ww(5g5m)>iBLv(?0p7FYQl~eRaZrPmaFbbD^&`Tr(0k) z>Zh1wEKNPTys{i3z6z_XQ%!}uSC-a*0B*we-IUEGaWxaHE-zm`OPb<~I76Cl!Xo_e z4ySaBFfmRXk8=kSC&~C3%i)8hUCznRvWwQhUHE9y!WHQQ8+A@6C^sgH*iSh|va*vp zfqTRhtNp-5bxYRm`)oTFC}UGcvkTw0M_5oTB3`o6hMMjI5ycu6d1u`EYtaQOsB0Du zh;!A;?NOzHJgk9rxPb^nW37<9AZM{OAC^&K5LVz{P8zb)DUHHiD>N~UF@yzG3TPBo z!Bn^`zF1?kn9!RqY;IitBI^vh#i+dnX3|VToH$(1w()lCNp-0yMeHfY%WBw}C&~%g zXRIUJtyULqvpv!jyT!;5Zy5n(hc+Y;GYYx4hD+DFm2T>-9A2vbfsK+@xl}se?7M+AqLSoTxPpgaSuF>ZDOlV>+K>MWp%8= zDVt4=C8{MXn$#mPNZmLDpOJ$w0AN+~V$UB|!(-;zV zGt216as510rBQV5x@6Co5K~OP*qSb%1nMzUK^{k;Bjj zyKxR!!pCq)rzBJLK9SPrWFZ2u-R>FZZSH}rs}9AUtWYeiHtL?uKZ!A7x>dvvEF^4i zwup_HtOzy9vTl#>^TD+}5{jjjHS%JX1sCDJE=cGN30Qxhvwv*NJw!VCf>-WIl5T-J zTUDptPjQYcvkK9+J_;%Ep>l}qrW~C#ybM}{Ti&B^?Sk^H0rL_;AJ?HKSc+1dop8L8?WOwJUxvz7u19Z z53>eYl5};nP_No&NFk!v4P|-$rcrmKQ)~22f$XJD|60E~1MI~{;Zbpn)?_dYjM!g#(6A>$Y?mlHOY)bu=g@=mx0JLDc>I}{QL2sIK zljdL9aI>rOMc54;!)~ks|AGE|I8FeKSnlGgh@F@6&v;ytXm-1ZE|bF2Xu~Zq8>^eK zqBZaJ$dlMLt*;#DYvmnRtje|VI!yiZq?fu3eTh+KP}p^Lv2QNWsgsJ=eqW09xBe4DoiXB-9j*;RL)Qk>6&A zI(Xt_9gLmM;33&Atgr&Dcu%A(iETo(`eImx_ygPhe>T97&ftkh~<;arlddrEt!)~{%X4Bk+ z&#gi4<&5s&3EanQtnNX1OqBv2kgQ`ap2#xF56Gi@5N75--u_{M#Y*xjkHY+AIGy6e zUC0{)s}IBb;>7X?6Bmq&3g+VHVNvC&+Smp*`5RJ9w`RwwsybBh|@#zcbjQ zdZSa2Cr^)8gv(Vj++usOcwUu)?Iu+@a)P*I_hkL$TIwOiu=0z1@D$IBAXtGL`F|{l zX+mbPvN@D;-|UjcAdv}MGVivcb(t?&lVf#gI)hjkt%!_?ih=BYt)e*9Z%ZtXsty5F zmzm26;{;VJPTkVFHLxSbN)D^>P!)3Wbcl|3xTh*YmQ|d~J7SV-lMnFYJel2W&61Bp zR#B?@lRV2E)L2B)@*w<2j#6zD+L5)h(P-5?XRYp{3bx#-Y6g1C)%VBj{RetOicCrWNVs+T=X_=T?xGkgz)5M-n1&l+ z#JCHt>Lx4%XQo-1L-_~x!%y{##5q67y1qg+pH7kG%`8(URve6TI0;V{0oW7^rBRj#)o@X=@Comriz=&qf9K*z3wNPc`5>#o z(2Ari51(~H(YDGeZNwCv!IUrF6F>61H()QFmCJ30;-=PclWdd5AO*}L=`t8GMx+!2 zc@LZ@GSC;DxGQP-97fl@(>W`Pk=OCUJk>gGNguu)Ht+ZIL-)*oe2&eAcPtZD&>Crz zmDSlkCMt^U{H)uC1dt!*I$Q7lm|eyGJz*98$v0SK{Uq!ror=J*v*M%^_#FzW0A;r2 zF!^{IZ-SPOk!Q23atwA=wNE4#rJTSii|BD5vdBNm28$24KQ3527CCq+FCnWU3|`}h zb?1XV56|Mo1P>8zHe9=YRw-(>=2)R_6>w{JN#=pP!Srd%CIhSvUW1 z6P6jD4F}4<_w%aIBnfTEBD-W$G-ihojj#J$2E`9?%}$Xd+;$lj#Pbx?g%m0x<+W-V zWm#+{#1g|;#UeAlOHTemW?mR><{f;v9H&exA7gc8zGe8y$}Z|YtUq3q5A&c9Pn7cq z^B{#X7+&1x=_6jvdaEMHlH8*RLl$)r(p&{L>Xgnv8)Oc-%Ut8Tu!5huD_Jl5XHn!;PRYX{2E3|<5F>y_*@Vxn zQa#<;)lSJ<1gfjWV&GX_1NOh!M~Gw%E8)7NT~(8Rh#*ekjLyId!#kGkX|j&Buy3+0 zR<2IBMs}($r7=;6-Btk;;mVAYmfMA@$+D>-&yWSYfjG3aQH*zlMx+5TA%RmkWfKoc zdLv8~s_ZI#SQ`fLpU|IG<)b7a7J9}{`Jzwxmlzgv$;RoMy!jVSu782?<-7dP(=mHG z!#Ttw(t?T35DU!%;T~y;wxOnHumRty%0QmtD4qH=s|ne=M+~hx967VBa3GtD6J-CP zJO5#MApwoVG}#mThKS*Sn81HSYR(cDy#4lZ}AK)@F)IP-!7|bE;TG5 zbH0?WWMKGHHDp@zhX=zq?2eaLC39NVM@RU495Xgh#5a9P@9>;_>5^xK%kn|E>T`DQ z6m-;Yn~=5k9I2843vMrX%J%cIWNv-Yi6rSe7McCag4}_m*^3=Lv4}}8K4qI(zkP~P zMM?hQQ!$WjdYa5Um2KubI0?O4#cBC2yI^@V6(433`B_$F9Vd^KK%@4UEoKjEKqH@v z!mKmy&xh>7Pr{Mz;2A5hT{4AOKBooe+#Q_4zT_oWV*gm4?-j4rUiopKy9XaosTRrI z#y#x6US_>)lAp5h5Xm0ZRNR#&vVXYAN5dmg%sE(YcxL@19b*t@-!hdbKR+f{_aO5kL|zmF#GgDbmKM|P66?p}+*7sC9g^1Gg~pxGGdy%xpm929 zwIT$r8Z&{+KJ(8at?Zy^sC9ZOF0JnhCGnM6Cwm2C(^kAdn>d-%sl- zfp_E~af)P6ti)VsRGvllq(MT?!Y+1NExUwBy|-391iIDVbIL52?ZPa+-X7u@FQ#)^ z-%RU-XFd;~nn7o^%(@wt z@;x!8e2oml%Xp^hlnlLIXvj!LP6#V}*6-((b-FDtr#IfVb-JoDp^bBP3Tv>rPyml3 z&uk}7?524K#1XCQvZ!jZ3Vao+CM!(Q_i1!9#;xqYZ9lBDIszZew%u0-629}Kvh#iu zkSw0eopdcX>+kfnBhAr?im5L$jpm%W9qTHbpiVrF`=k2{D! zD)xS`|Lha;L4MD`8I_1w8r#G`*k{=Zn^rk(-k|Se{@G@TR@HN}XSZDZ57G84A`q4Z zBY2N$HtT$EZbPTyi<`r8pZ7)?Y%M;pi#T;tFip0yAMD7Vus=S`vQ(FfF(D_(u8erF zihK;JSeRuwLpD)8B>Tq;ieh$wW&Fn(;%V{!vUGBcQMg^zE8HFK(TdMSDK?`jBZA2C zX$3a3Ec)H}P_$e;3T?vv?YFi@Ow=u*3C*$Y{IqJIcm$)Ga>QQz_fsd7A3)z_NXx3- zR-Pq?XaD3_jDnobN$Z&P#=$(OQ?T6PtvDNl>T@x~3dw_ATBo_bVpNs?A`HY}+p!~Z zP+{{Mn#BMU`ElQ(nG@Hl1g}n3;?(rTyKzgsb8ur4wU7gjGo04fhRPCvLm->|79UHQe ztFzNR=~%x`sEVBxidFWf-!x^`01KnpVubT&y`%vBox=BF4%;nnWV?2;&fbC#!Z{I{ z#>?km1l|ERESQguhsX-L-_JlY|uhqk24fY}fY?XIZ9fBCKJ-)cy1Cr3lPV}Tv z*v&$n!YN*p0P#h_Ap=5;(DIghgbx)4xUSe9)%}4+<@l-x+1$xWwx(AIpZxxMd zea`5fWXWpS?#Tz-f~{v0qOGXRi}+cX#7bA^p2$Ak#1ph(hjd=dCvScZXGADJZdT2a zZHFVi44>>4f~Xxc&-!TfHdN^tT1fn1y0cSg1O$~_*O&i;z8y^p!tzdo6@IrY^$wVpx{v6c{r}xud(R zhp`!K0aA1qpNSo8EFQ^2paImjhELfRyyT;yjem-Vp6N#H98#op`eYGe9aionMTy=8 zrY}-wCoz+x>VKv6zD*|Z&KdGja&&f|I{`l-O*-Wn{#fCJ{^Z6ltw4Lui084~Y7L!| z9@p|ZUznCOTZNpuWpyrCLSAJ;NohL95VaIK=kJvYe7l(XgG{VbLhYUd`SvR={Zm~(mpBXaL+T)cnl-A(IzLf2GeASHN&!>P>2 z6(NOte5HE`Lj@H|p*6OzbsyD1ML>9y`;W#t%oIZ!uI zxv9tPJEXb(;S@4kwoB$LgPf~tvTt{_M_xt_uqaNh^649@D$W^R*#S#rH=ZkVwNI#4 zJXeGB6upY4B-9gh;S5%&mX~cOh4%0Sn_-Jt_^(!BWkjyJPo^ckm#V z$=jMN4Kqn1l&EG>jn@vz(J4GZ^3Ri8s`Sj4+jRQ#w{ z*adgXM_7b4+JWu#IT`X{aaR2`-ztWYLfM1$vtIaKEKOrLL;k_C>>xn(dQ>Q-*1n0ql+a zyQSx=$u!4uWmfRx+jencXRrr-(79dwTXLZLDvJD+7sl>4;)`Nc8^VDY5^33;Rd^zO zkW-e&?m};VMA~%5n)o<*yM-0#J9)u8i0*u38EYmOo=$s^yRI;E^R%HdUs?U;T44rXFLZo~589&Wl=FdTT@=uoS44(1!sr`!AY^ID4qOn=u_MiR~P3he|X}mcu_i)qd1R@Mcd4j&( z6n_(c$dESNHQOX9-5?Qy9PlHIARltmI?wG9XNcR7i1<-7$Mv#2rwq+lw{s-}URE5; zE@`|9m0Q+_43}x6_(GcGNkYBf=l{iz|FJuE;Vz3c&Y0zqBE*anlNOtD*P=0L z-NVWHJa$Br;wa1`Pj*!{N20tK>vqpbKWG<%U#wYdo%;@su7AtHU*WwW(zD@yW>R@hTydsqdl zptb6FETUaxjl~f5vdHWd^#|R84MGb#$!5Z$CS=V`@afG<-89=~x1O;B`=zD!*lZ4C z@QgiZgtU@XI8m?ES;A$zLkj0~)39nSt6DVMF2*^BbHy6iPwR`K^zIZ8i(PGW$%}he ztKW)2Yu4E4Bf^HM5KWYU)-bKe&x?8Aq9WT>2k?Y0zEkJN#Y!=VT~uq1nJnMsBe7Zg z(`H#r8iQr5ilvH_eZxz8 zKtOx2UUpSx4bfn)**spv`km1#Bc}NVNy|!oU$nLcndZZ8M&J1cn{Y}euuC3Lp5-Jg zv%FR$BMIm9?>Z-Bh707>_i4*LvOK&y%Yute%C3?o8EhovK~4~B5YIy+mTNZ_QXb^4 z;lFdZ3pub5_h7eB(_Q0mB7Pig9y!iX9CczR?K8McOK}R;Med|&ht^o82w7RyM&s<~`>gCuXvi6GKX$Y>A}$aVFO5oFqxsmw4Jg)EQ$}YoFckiiiFk(d99F6lctlBr=Ku{to(Jd zyvHe7j?b%dWm#dK?~~=qD%&DSf3n?jLJ@-P(@1X z>>>yq>_};|X*acj& zL(IZW=_7PM$r|c8pNQ?I_^Msl1X-Revy<}YkXOVmMzKma2z$vrdFO*!YDfXQouPZA zF_wA4f8X{w`)B=&Xtd4)oss{L6Xq{YRV!edPS^KYUU{qRp^9IaGmo6zfJS97@-cgf z6D%XlbqD{pH}t%vm)3mBlRASsw7x=JE!AviutPD7P1&RAC+j<*d$2Amh0G#4&655i zL8!Quoos&*tqg!&bl;*V`{6b8Aw%$Y<38N9Iyquj^#x>oO9^U&uzCBxv&CrtH{4J|24=O{T*EHl5_RxLs1TSKnZc9&|BPC3g2gI_h z&pXI5#N;9IYLXy(Rtx`of-NNhmdzIPpZJaSAd-`~p=Wq_^KR8&eTUTZ9jEi%Q%8zb zkX)w^xvSL4ULnp(-fs8;o0g%li|YDrE9dG7&#MwRg)BVW^IfuYL${E9%b@dhXUI0v zA@7G1`mM$6Zjtq6cTQF&g%!}O9Z21O#+rB+FYv5Wx+mRbEmZ;fod0CIY2P_KOvC5CO8T z-BpigS+qbS+0<%--IEmW%C7y>Ib{e?(>{BOI7_l{@}gMPqjXQ=?qU6~s%(r8lVs9N zw>}U3RZPgtJ{xs=*1v7yS&j@#NIR}5Ry2)MACu;+%ujf7HscA1Syq{Sbw*ZMB|%Q; zgyBHdh}c)w!MkH|PMkFCu#zAPe0npfSfD7h=g5+ZWO)GF&i|Y=Ta4L|lpp$XijAh( z)=4AzY1ItRgf#RJqJ&meL_`P@Euv>v?2NsX5we=>vD>h!5V0q$;5K&2dYdxI1MTGr z-+`;79*@HW*)_~@2AD|>?n*OsNRsK4cAbtc*+y}W%{pn3$~}DIDQo3HNtzt|ampm& zL{`NZs@;eI{_s>9WBahiJw^3=Ee{eQ`xAD@8+cpm+Jm1uH6K=Y^v`moB+lENC#zGz zvKQ&+#qP<+!mGFuS)b5Ee!P*Cg_DQB=}RORli?nDW*1fvp~|oGv(ULXVud(ba#Jn(`XtGuR4inIgigDF^27uCe65uzoco+e7I^{xlmZfw@5xTqOWCO z#jrfX4aktJi*m8r)?mM^_k?+S3ZJmL*fZ&ncK7fNnk-Xez3xPsZqps?Ui}F2m6P>^ z2;ffn4y>V*PRqJU%airRvVZbs86=ZN?ZqG10^3YqzGn@QbUh#>>50(W9x5K;rxo}i z@34pO(MkI3--dYnusF8ZJO?`d9h(v7#7me$d+zDP@%DNT{nJ@jUL>2%@ME!*myvci zSy{o=_JCzu2cS1)b@K{eC`JR*n_0iBG`8x;8QYUF;1xm z(o^Mh|Ja$;u=DSb~2^ai(ljlcPnHa#iI$r$BXViZ=;eaVr3xnCY)FE{bstRMR8L()ar z1C2WwyC*A}^$m={bAP@=xNIf+V0xZ$K3x@9Qgw%gvCMpjC$bD4QY>|bJbrr%ZW$)$ zKQVv$D1UXHGHWc5_2y;iY;7~0v#frno~TV4KF?;tT>T_>sqRQ_;hKA7*|H1vPhL)D zz9K!beZA0nrp|#ibW%6Yzt~%Qkrg(8O}b6LORLUu{k72I|MzuoNsinw5Ji9N{jb*l z#?hS&s>hE~v|1{Hcq9@atEB1K{=cg^euO1^d=CeGfEB*zN91@1hblcz^{s@m4)<1b zI$8~@CBDfB{rmeixYzEUjlRF6+On~}YL^97{h`i>zvaPw3>=kx+%#MDw2x~Y*5Snh zm)L0+SbgFIZrcxwR@L*fUIb}6+>781zj(=pD81Kni-C>umIheLHUHJI3i<9@bl|cI zr-KJ<)Y0NM9dVzga4+kv5PYfcwr{9&g>>tEpJS>!h!tTPbQ?dS&2Oxsfd_KcwJ~1G zTG0$82-zJ~Cg#uc!vy#)uGDOU{#1&Mve27&t+SE z(Zr$(fkoMNM&0=FT7}hl^_=dcu@VwlP(eJvD1JJtzN}?|nK)qWY2zRvLx7Zv(Vqt7K971cVQLwynzB_45R3F<-{5P&C%X#0W9GFADkg zLjz8Pws>WvI;Vlx<)U4A>!kSj0ZW{cFFsU_x|a$KtGHX7G0Qk4l8`SJ;IN(GvI-4R zi^AOcx(aiqc-i*D_bRx15qQ%>H;51QqSml#Adb5>XC?b;6h9r}!+DZfj*%k8@#>bp{PQrkKCy8gz#B?%;J=LX1-iyb z@I%IB4fpC6`AGtAjPs*aP`=8k^K_Y8zWZKl{96=dR-|UOzF>H(n7Fel2KzG)J3NvQ zfkd@vb&_xQ<6CX>gzh>rKDcYtH-KAdLnL03WXJ5*^ExA(p;3)G=L$<4o}IOa!ru7r ztaVP;?=XhErV;=wE6?bC8x=jp) zM%5uwST#Oo)w7{huIcbFF2?4_BD7l1p$#n$2=Kk`DPk7j-L69oR7Xb0dbAo>!+H`g zc$pO*x6+kykyjrWIrfJTOWhT1*N$7o1fNw|mg^I)JYuD2WV_D1I#L;|@WpsL4l59^ zs!*dbQ(xqXXI`q1stS8Ur#@jm8^)6%;jGzZ3oqr8#*!}26fT*;Zy;+Jmz1O(%z~&tKW#pD!=@XiFCMv z>3$(!V%Hetej{G>nO8eS-IWxiVig}rIAo!YC>!`0A7mBF;*)%+5r-_j-53A6(*eu4 zr5{+E_}qN6bws&m19tT)KBygC@pV;a>)E_$Twf*eR?KRFt~}6Jby)n|VLmyXl~jE* z56X26!+q;RcLI57)J_j8x<`U*&!=Gh~Xz^ix(E1XedGQr9;uz**sbTO|_ zA9V1%TyLyeX@#KYdb4g}jK3jX1+b9}>wZYGXvOwuJ&u2|v0b8T2Pk@0VXe3AO~_?k zuB}#B@#>F-SY_1yjUQ~13C%h|d;TNkE3CAw9xI_>(ol~6Qv(7-mix8JPy!mUoz zDka+T!ACVf!rvwHXy5J%#VRIOtx*`mZ?oC)+AJDy3lff2pGa-9*-rlIMelouEU@9Z~H{cnRb~mUk&n2 zA7Nj2r#o)Jix!+Age+s?o(Q?vn;$gc zerxvF!c*t@s4KPxsSR4>3f6G2S{m;fnveV_o3zl4opo~hc>t@>fPvo5zEzax@rj729;XTH;`>C_qzZ<84CnWn6hJMuTZ%%z?W2s?pU@pVh?w_{FjcwtMmK>bi?& ze-R_&wyND8s!yGk8QE{oZ-r`=V#Rpz@_`f|{69=qFrW9(*-juo?RV@2{fkcPo$QWTkXfYsXlmR(_sl!+7@%efHa$Zd~T!l@DE$s#02bCilkk7$^DIdTo@Bb`)wB z7Gl*2*pLO|vER6U)ODqpXvGv2wog$=?J7f@P_+A@MIR7rwNXCB*fomP%3*#oB*Fma zF)NGJSTR?Ft%E(wI|-i3KMn1kT9J#FrI=Z_QfGE~)pNw^;O=Ixai) zR7lHti05VeZC_%hF?g|xOUpSs#Zukg+dCTXUjwu{Krsy=)gBJN(-v#xmEYO0|FS<9 z4PM0yy!zJxk%&X>k~Rx>qEVpj3caC?b9N^&eq|lp*=(UdWhkkd}4P#%9K4C zG_b7A<{RDYHCi=Mg(0q<{C_);daAi7~L` zUwxGZye5Hjyl4$1vpdDSf<$#f4r;JyIPeG( z9_mOO?8153+GhjuNe4Wut%qgTY#Wc?=40`Al_zB*=IOmWnV+J;x;*gLYw*8+wT2_- z(kJ^O#AyGf#`Q6VLRB=5aO}GPeJ8a39)C%c0Xo!Q-5tB&Z!hIH=Gq7HVS7n))eajn zWqkJ(=YcGU)iWIXpM$D0b!wMQ3vWf*-bzmCtNOdEW2c44VuhnU!JhP>uY&010>kPmUax-tM}G5VsO2I278wN1vlo<%zmqvnsTq&-8QUI zDe&NdTvns`Ko1Gg^=%P7f&TV3{2Y7XmKJOgNnVg;6A}&x%R<%Fd~;<7(ilwEoc?}m zHCk4U$0>-Y5E#Wk5*Q&{G(il0^mx@8S9RIv8b78_C2&@9#ZDW-cHDXjp z}w%4VF16?K2Mb#9!5cPpJeL;h7SVbGo>yvsQ6)IrtP z-6>a`atvoaq+wmJ6<;i}!#mk3in6M67HwCndWe|o@8axRlC706VT(oIL9r*cE5Seq zUh+|Q#cloHnEz2h#jy=v@j^+3R{_(|{BZ{BaJ&5#o0}bjWeZO2s$uI4-o!q5)FWCa z`Pfyj%*sAJ5G*q2#l8BS%wl%km5S;}v}!x}Fkj-_DMre~44Qf9}q_UC2#7-@5{G9T;coSTu|^ zaus*^XWxQ9^&L87I}XO1Iy9TO-}(nhEV63EC;s8SOn1j4E_Cg=dphpeljvSRc(Mf< z*kX$Bu38Dn^v#NPa(q*p&iji(aR&#?rmM=uiS`Vxwl+QsG23-o+=_Q}%TYPd1FXiz zppkzNFiwiE#_CzsISXBPp1{S_+TgAv&!C~7wF9CD>)3(mGQt8K{9$#ya|qS-lHdij zjMdrL#htopRc@po8ryr-3*u#*&%ZGrXX0GnM2Qi3l>h5D%?P8}DT3}c@JRf1%)AtX zhwDjpszi($+EqaPikA?F7(9v=^F#qpp7Nv4-4)-vhANI#=z5I(N7q35o&yebJN$(Q zogRFxFSL%q+(VUMO*>ut8j*IlP)`ffJ9RQiE?=?Scz159AT}USb);eb%4PMNk2ubr z@j6z!0#%%UdSN?)b-Gs4uD#PzJQy8_d&9(YDCk|^cy*slEz||J7VCM{hur`x zX_oJOUl}40FQa1M1MY4Wg1YFsDjsX}wcV?x17@_;IqRNXArDmyRAt_?!bVp?vYr=e zP#pf3Z{t?;R@$mF=0dj>z3#4Bn^h)s00_cJq|kzcm5z^bh9|8X?L+nK*21zM&-wXy zEdoFNdzYA1PO1xDZk?2M$b~Ru>ot>TM=I{EZ(iezwY1*b-BY|RLiyFzjq0841=*<^ z@&ZqAw+E^3ysSdaZ9iIvlobzlcuF%*$jBNC<7K{xmA73Xh(gB7Zj8macD%AGCv}Q) z%1UxE%RUR%T_dOu5_WJ9=QkaV*ExBchPqK%w_?|Aw!eap2fy>u{3^&9Ym;%2(;=f9 zH_JFLTH*Mpf_U5g)wtg^Z)*@Mb=UGDQvCoM6Xtc2 zkO*0K`~E#nxqe9B-Rh|~P4haG!%UOw%vExdD%9r55553?`lB97a| zVAYp}t%CUxGg_-p(J$V$o93qs)F-g6=y|9H_>d)byvi@tL#DiShgLkaH>X{_3M6Hg z%-z$yE&?Me6=EVu7jM}$-wsQ)?>jksX_e-u@oGJrvO>FeX0Kb>36kZ#gD0UN+qm6b zpRytbM<_aHw`u-*QJj$77$@g18*q}#swYa#Da0GhlSH`OjqmwYtNVT@w(Q8y0Epf>%5~KKDm6D+^FKH2l=cM>z92&6lc$WBC zXXU-v=qE`7Ph)=!r*YiIb&~Pkc)nL*J%@f6RRwWNHgFePriCsRt)McFd)SkXJALuU zI2oK6^RR|Y*-5AQ{#5~)_TG6eCU)bkPlDXbsAn1DK?rpPVyz^rr$J=(MV4Lrr?jyj zpXvej1W+~$vv34fRdQE@i>6%(1ovGrpYdU!o@0)g?U6A?#{DMKXDwqx*Mr@M?p`xV z?1@EmxC`wbnQw%-Ti0HwnqURn{0*MIPv#nyhE}bv743SOKZeuGOKia?62^8f8v3DA zAERfz9Xrj9ld<7I`>;1=l7%kR!_2r@;-b+A84vk}Iu7%QrYZu?tsdQ}z@>O&yv)~U z>|QVhHLNdlx01T16a@);hvI>%HP5pj{57VxLf3rS*^q@a&?FkDiVu#(&^;BSPz{x`!V)gi zYQAdndi{fS9N>4=%7?L6zS{}%<0jrw#+uK^AvWWmRu7 z>|mZOwX5mhpQBamB}-LKI~$rp zw|>>Xc&DS)usWs}w)H-C^0Nx^9uCzG3nXQ!wQMn^gC9n#9d}2?Nuvr&4?CeP5}gz7 z@kS)%^HDhhsy1&lMPjRwr4D+yje8^u`F~(0jBpfWev(7Kq@7BNm=DeQ4 z6B5a*ygt>UE0(FjYM{EdO3_gbiCQG{Lws1<^_o#R5`!qok&H8O-m@s-iHEUiQPC4Pb8$yChm(2m6e(-<02OXM zkqh@@jQJlKL^VI@W3&6^`}~{4ILjXMRvP6YcCJEU5YBx1sJ|yMc74-*I@;hg?if#|D$hb0 z<-IGE_Ga11N?fSULeBX*N&E_x{D3Yk_+8J6X%OZo{N>$GyW$uDjZl@_EbC}}>cbyL zTG~Bs#l~=&mSu9}q33F>`;OV=om|1y?u{M$>X=AIrwXs?&@MB%tjm|2uD|y0O=KhN zaK|^*$XWOBw|@@>M?P)`CEl0&sW`B?(0apgdZ}_k&J^frQ5h;-DiO^Ob+%i{NujO;~L*z z;lVDIlGb`i_n(~Y|Bt|757z0ar_xc4*~`%z>-kSx_Xzy;4X^VOGFBt+)KjsZ(pvwBpyc)^(mN+p9y>r8iZJba*)A zv^7m7hicWv`*yE%;#XOb3vucN(5})q{&*AO)lUrL0UyR`v-1&t>E~%Z*ZlOez~8OB zB=egG#?s?lbxfQ1WuAv!+omrrW>4&*#F^dUs^5o9@w2@?Lg#9QRTj#Ye6`;kr^Y%G z>8O8;su*}2j@SnaQPK-1p469HOO4RUclBGvlPmkYDTCI)xDU<#ouE&4>c*s9Evhd) zOGonFdD?M>hvt^gD&43fMz)&CSJul(nTp55ihu1>#E4h$guB0ESaqhEU9X z;Q~8Y?JA!<{V?H)QTrfOxau;S?ni54 z8XEf^ZWZ1gq{i#l!x1-mN`HQ<50A$Vws3;q%ZRM>nq3FmA<)jo|1l;XVvB8hA6YZW z`^oB=zp;+AjYC5LQV>@SBx0`_y>~wa_vMz}K9S)s&2d%~VIRZzA9MK4lhym%so`HT zM*E+qL{pDwP4%2li-I0W7W7%iWm8Zqs@h7{ofj56pA$PUnZ`7P)&3H#n!pVbFXWp}Z*z(1duicoCZOEj_qlW*jPuohcXgc-<$SFS1ropxaLt zc}g38mo(==sw@CP;SD3Bnx$$aOG`05*fx~c6(CqXfeW$cU|4|$$>Lz=6fhc2kL_r2dWr$3N-{l|+X_N2yA3D6?LHm@j=xz@_@t&Nll46aw7$g>FcAhlb z@uxB4BGs3TCqqM?%Czg%Rx(wR15q-`6qwSBCL| zA9T~+YjUlEX%L;*B7}w4EQFNj*mO?ZD7k8; ze~YMkNW;N*>dReOYQKQ(NwssUKG<|trIZnKo}a9+?J8|u7egKPFgjO95#@DUgcbfc zva(oX3SV`-t)BfQDm(Nnm@RJ(@rIW;oCytxd%djYt?|`SynaTEE)^r<4WDR1mGabdcth4Q>3W_=Ot1i#{bF9XA zAsP{?Kbebv<~Bd>Sc^SEL&osJ+z`hJ8XTg@J9wwh`HhCLXS*s6#+?ei+%Q?xci0Tx9J$?R0ct1(FV z?jK+22mx5hh5SHTw8iT@DZlB&4EYN0d;7^K3BGwY64vl;#%J=4%YGSkN5uCOX^Knp z&$^=92=tHOFlM=BQhdZSuoBr#<^oH3YI$3UWJ`efs`-5ipS!O-0t_TB>n1`F~_TXlu zF~8-YUEHbrjWkg$RibVd#yLV-*05qVRP?ebO1bKL)%euTT--R(H>dn{gT6x|vwEexvX*b% zJ>~^&VgT>zWY}b+zT6sT6z=te7{OCLk)&}4A0OhZvu3{B_rCh#tGj$=cz!vAc6%Odq+f*f)7TI@d09P` z+xi_%-jiA1hFpE1uEMiohZGMybW5ute=sHt?F^jpsg^T&BP#aAA`ZV>Iq3E(jh^{a zFPAGhEt8FeSRRmS_Hrr@a3>X8JoCXhwy-vtazTU2QbjR-&$Nd^cTBQY{>m6HIv{|# z9YIv5s2>#i7M!brkC6@Ej(X5wBPlEfjc4 z$_PDhqJf3^$d)k{>&0oVm+6XP&bGPnOQ-RWbHB~zxjPm7i8Izo>#eTVX!FU|Y3yaq zz)x}7-DC^0zIR$?mm%FsovF0A6Q}l0eY94sLU`jx^k(n-x7Nwsw-7&6*tbTJYsCzc z{-RBlpI&=^`}fw%p?lNMR;yF%pK+)<%U^bQ-*+kdwq%{LULqPY#mNWr90%?lLs7oX zw$8RXm*2X*Z>m{I`_5CJ3(&!n8wZV=kMaU02@>RtZhv$6D zPm%a(-VOaanumXQQFh|BXP&h-7|$2_=w58C9CmSC`;v6bb4;-F@7h3xG_xH#@B92d z=C#lBo#FHpoqedkqt*ZGqejHsm3WdW(BJmx>bSig?5iCaBFC3BZp8_m>NFHYp$17q zu<8kCo~rikJIpHwaa+~2BWBr;9)xc_lNPs_n|HrH7R(P*&tzEaFbf08tmpB{SrtTg z|5m*oEyq;}o&JiI`^^42rwWOM=Eg8=k8^g4cvH97Xtfgm>BD4W`6$bw45?U0vtBW@ zpuSy9Ya0ao%`tm=-*S|DbJRh4&7vBk&~1OJAd3Ow@`&BX?(ZuCxLTx8n14w zgWfyKQ@kK$onb>4fuy+Zg@Ev#~f0xKB^o(okQi zX2ROtr@o0sy6=m0cfeVHE8<aL((|n**veB zW0aMAbnYE?l3`sPRrjJJ1(Q~X*2(X=^oztZ&%%x7B3&)eQxrXuNaj70`1UFd)8w7k z-^b21@}4KNGyR^WjXupAN#7fP5*wK&BaVN2|NH#CpSPR$Upk)r&sX0Td;P89=j&(V t=c{?lm+APnvg{PslX$OY$M1jYn696{n;k!XA9 Date: Wed, 9 Aug 2023 07:17:35 +0200 Subject: [PATCH 068/243] [docker] Catch errors. Fail graciously --- docker/__init__.py | 91 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 547762f2..f925e913 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -8,7 +8,7 @@ from albert import * md_iid = "2.0" -md_version = "1.5" +md_version = "1.6" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "BSD-3" @@ -27,60 +27,61 @@ def __init__(self): defaultTrigger='d ', synopsis='') PluginInstance.__init__(self, extensions=[self]) - self.icon_urls_running = [f"file:{Path(__file__).parent}/running.png"] self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] - self.client = docker.from_env() - if not self.client: - self.client = docker.DockerClient(base_url='unix://var/run/docker.sock') - if not self.client: - raise "Failed to initialize client." + self.client = None def handleGlobalQuery(self, query): rank_items = [] + try: + if not self.client: + self.client = docker.from_env() - for container in self.client.containers.list(all=True): - if query.string in container.name: - # Create dynamic actions - if container.status == 'running': - actions = [Action("stop", "Stop container", lambda c=container: c.stop()), - Action("restart", "Restart container", lambda c=container: c.restart())] - else: - actions = [Action("start", "Start container", lambda c=container: c.start())] - actions.extend([ - Action("logs", "Logs", - lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), - Action("remove", "Remove (forced, with volumes)", - lambda c=container: c.remove(v=True, force=True)), - Action("copy-id", "Copy id to clipboard", - lambda id=container.id: setClipboardText(id)) - ]) - - rank_items.append(RankItem( - item=StandardItem( - id=container.id, - text="%s (%s)" % (container.name, ", ".join(container.image.tags)), - subtext="Container: %s" % container.id, - iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, - actions=actions - ), - score=len(query.string)/len(container.name) - )) + for container in self.client.containers.list(all=True): + if query.string in container.name: + # Create dynamic actions + if container.status == 'running': + actions = [Action("stop", "Stop container", lambda c=container: c.stop()), + Action("restart", "Restart container", lambda c=container: c.restart())] + else: + actions = [Action("start", "Start container", lambda c=container: c.start())] + actions.extend([ + Action("logs", "Logs", + lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), + Action("remove", "Remove (forced, with volumes)", + lambda c=container: c.remove(v=True, force=True)), + Action("copy-id", "Copy id to clipboard", + lambda id=container.id: setClipboardText(id)) + ]) - for image in reversed(self.client.images.list()): - for tag in sorted(image.tags, key=len): # order by resulting score - if query.string in tag: rank_items.append(RankItem( item=StandardItem( - id=image.short_id, - text=", ".join(image.tags), - subtext="Image: %s" % image.id, - iconUrls=self.icon_urls_stopped, - actions=[Action("run", "Run with command: %s" % query.string, - lambda i=image, s=query.string: client.containers.run(i, s)), - Action("rmi", "Remove image", lambda i=image: i.remove())] + id=container.id, + text="%s (%s)" % (container.name, ", ".join(container.image.tags)), + subtext="Container: %s" % container.id, + iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, + actions=actions ), - score=len(query.string)/len(tag) + score=len(query.string)/len(container.name) )) + for image in reversed(self.client.images.list()): + for tag in sorted(image.tags, key=len): # order by resulting score + if query.string in tag: + rank_items.append(RankItem( + item=StandardItem( + id=image.short_id, + text=", ".join(image.tags), + subtext="Image: %s" % image.id, + iconUrls=self.icon_urls_stopped, + actions=[Action("run", "Run with command: %s" % query.string, + lambda i=image, s=query.string: client.containers.run(i, s)), + Action("rmi", "Remove image", lambda i=image: i.remove())] + ), + score=len(query.string)/len(tag) + )) + except Exception as e: + warning(e) + self.client = None + return rank_items From 2c14265ad1423ae97b1ce1faa55fcc39db77bcc8 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 10 Aug 2023 11:15:27 +0200 Subject: [PATCH 069/243] [stub] Add a note on the attached attributes --- albert.pyi | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/albert.pyi b/albert.pyi index ec5130b9..bb910509 100644 --- a/albert.pyi +++ b/albert.pyi @@ -319,10 +319,20 @@ class Notification: ... -def debug(arg: Any):... -def info(arg: Any):... -def warning(arg: Any):... -def critical(arg: Any):... +def debug(arg: Any): + """Module attached attribute""" + + +def info(arg: Any): + """Module attached attribute""" + + +def warning(arg: Any): + """Module attached attribute""" + + +def critical(arg: Any): + """Module attached attribute""" def setClipboardText(text: str=''): From a97b8ac4f8e7451499756bf947e70a87b62d952e Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 10 Aug 2023 11:15:48 +0200 Subject: [PATCH 070/243] [yt] Adopt to the attached logging attributes --- youtube/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube/__init__.py b/youtube/__init__.py index ee6fb6a7..f1b58ec0 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -8,7 +8,7 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen -from albert import Action, StandardItem, TriggerQuery, PluginInstance, TriggerQueryHandler, critical, info, openUrl # pylint: disable=import-error +from albert import Action, StandardItem, TriggerQuery, PluginInstance, TriggerQueryHandler, openUrl # pylint: disable=import-error md_iid = '2.0' From 6e785f31bea17aaf450172667060515571e28180 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 12 Aug 2023 20:04:17 +0200 Subject: [PATCH 071/243] [pacman] fix imports --- pacman/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pacman/__init__.py b/pacman/__init__.py index 11e9a841..51cb829d 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -9,7 +9,7 @@ from time import sleep import pathlib -from albert import Action, Item, TriggerQueryHandler, runTerminal, openUrl +from albert import Action, StandardItem, PluginInstance, TriggerQueryHandler, runTerminal, openUrl md_iid = '2.0' md_version = "1.8" From c3b4b33344f71f39cede92eee9a886701ddc2abb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 12 Aug 2023 21:06:34 +0200 Subject: [PATCH 072/243] [coingecko] Fix crash on initial fetch --- coingecko/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 328f5e6b..602d0684 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -104,8 +104,7 @@ def finalize(self): self.thread.join() def updateIndexItems(self): - mtime = self.coinCacheFilePath.lstat().st_mtime - if self.coinCacheFilePath.is_file() and mtime > self.mtime: + if self.coinCacheFilePath.is_file() and (mtime := self.coinCacheFilePath.lstat().st_mtime) > self.mtime: self.mtime = mtime with open(self.coinCacheFilePath) as f: self.items.clear() From 1309b21c92ee55a2353bb330842b8b3262b30cbc Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 14 Aug 2023 13:12:33 +0200 Subject: [PATCH 073/243] [timer] Adopt notification api changes --- timer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timer/__init__.py b/timer/__init__.py index 02760cd1..ab49cb5c 100644 --- a/timer/__init__.py +++ b/timer/__init__.py @@ -67,7 +67,7 @@ def deleteTimer(self, timer): def onTimerTimeout(self, timer): self.notification = Notification( title=f"Timer '{timer.name if timer.name else 'Timer'}'", - subtitle=f"Timed out at {strftime('%X', localtime(timer.end))}" + body=f"Timed out at {strftime('%X', localtime(timer.end))}" ) self.deleteTimer(timer) From 61a4a678d8f1c32404e6ae0c2d6096ee383a5908 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 14 Aug 2023 14:42:59 +0200 Subject: [PATCH 074/243] [pint,yt] Require maintenance --- {unit_converter => .archive/unit_converter}/__init__.py | 0 {unit_converter => .archive/unit_converter}/icons/currency.svg | 0 {unit_converter => .archive/unit_converter}/icons/current.svg | 0 {unit_converter => .archive/unit_converter}/icons/length.svg | 0 {unit_converter => .archive/unit_converter}/icons/lengthtime.svg | 0 {unit_converter => .archive/unit_converter}/icons/luminosity.svg | 0 {unit_converter => .archive/unit_converter}/icons/mass.svg | 0 .../unit_converter}/icons/printing_unit.svg | 0 {unit_converter => .archive/unit_converter}/icons/substance.svg | 0 {unit_converter => .archive/unit_converter}/icons/temperature.svg | 0 {unit_converter => .archive/unit_converter}/icons/time.svg | 0 .../unit_converter}/icons/unit_converter.svg | 0 {youtube => .archive/youtube}/__init__.py | 0 {youtube => .archive/youtube}/youtube.svg | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename {unit_converter => .archive/unit_converter}/__init__.py (100%) rename {unit_converter => .archive/unit_converter}/icons/currency.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/current.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/length.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/lengthtime.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/luminosity.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/mass.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/printing_unit.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/substance.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/temperature.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/time.svg (100%) rename {unit_converter => .archive/unit_converter}/icons/unit_converter.svg (100%) rename {youtube => .archive/youtube}/__init__.py (100%) rename {youtube => .archive/youtube}/youtube.svg (100%) diff --git a/unit_converter/__init__.py b/.archive/unit_converter/__init__.py similarity index 100% rename from unit_converter/__init__.py rename to .archive/unit_converter/__init__.py diff --git a/unit_converter/icons/currency.svg b/.archive/unit_converter/icons/currency.svg similarity index 100% rename from unit_converter/icons/currency.svg rename to .archive/unit_converter/icons/currency.svg diff --git a/unit_converter/icons/current.svg b/.archive/unit_converter/icons/current.svg similarity index 100% rename from unit_converter/icons/current.svg rename to .archive/unit_converter/icons/current.svg diff --git a/unit_converter/icons/length.svg b/.archive/unit_converter/icons/length.svg similarity index 100% rename from unit_converter/icons/length.svg rename to .archive/unit_converter/icons/length.svg diff --git a/unit_converter/icons/lengthtime.svg b/.archive/unit_converter/icons/lengthtime.svg similarity index 100% rename from unit_converter/icons/lengthtime.svg rename to .archive/unit_converter/icons/lengthtime.svg diff --git a/unit_converter/icons/luminosity.svg b/.archive/unit_converter/icons/luminosity.svg similarity index 100% rename from unit_converter/icons/luminosity.svg rename to .archive/unit_converter/icons/luminosity.svg diff --git a/unit_converter/icons/mass.svg b/.archive/unit_converter/icons/mass.svg similarity index 100% rename from unit_converter/icons/mass.svg rename to .archive/unit_converter/icons/mass.svg diff --git a/unit_converter/icons/printing_unit.svg b/.archive/unit_converter/icons/printing_unit.svg similarity index 100% rename from unit_converter/icons/printing_unit.svg rename to .archive/unit_converter/icons/printing_unit.svg diff --git a/unit_converter/icons/substance.svg b/.archive/unit_converter/icons/substance.svg similarity index 100% rename from unit_converter/icons/substance.svg rename to .archive/unit_converter/icons/substance.svg diff --git a/unit_converter/icons/temperature.svg b/.archive/unit_converter/icons/temperature.svg similarity index 100% rename from unit_converter/icons/temperature.svg rename to .archive/unit_converter/icons/temperature.svg diff --git a/unit_converter/icons/time.svg b/.archive/unit_converter/icons/time.svg similarity index 100% rename from unit_converter/icons/time.svg rename to .archive/unit_converter/icons/time.svg diff --git a/unit_converter/icons/unit_converter.svg b/.archive/unit_converter/icons/unit_converter.svg similarity index 100% rename from unit_converter/icons/unit_converter.svg rename to .archive/unit_converter/icons/unit_converter.svg diff --git a/youtube/__init__.py b/.archive/youtube/__init__.py similarity index 100% rename from youtube/__init__.py rename to .archive/youtube/__init__.py diff --git a/youtube/youtube.svg b/.archive/youtube/youtube.svg similarity index 100% rename from youtube/youtube.svg rename to .archive/youtube/youtube.svg From 4c07be6b386ca95a63e6375004d8628dec343fce Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 18 Aug 2023 12:25:49 +0200 Subject: [PATCH 075/243] [dice_roll] Archived. Maintenance required. --- {dice_roll => .archive/dice_roll}/README.md | 0 {dice_roll => .archive/dice_roll}/__init__.py | 0 {dice_roll => .archive/dice_roll}/icons/d10.svg | 0 {dice_roll => .archive/dice_roll}/icons/d100.svg | 0 {dice_roll => .archive/dice_roll}/icons/d12.svg | 0 {dice_roll => .archive/dice_roll}/icons/d2.svg | 0 {dice_roll => .archive/dice_roll}/icons/d20.svg | 0 {dice_roll => .archive/dice_roll}/icons/d4.svg | 0 {dice_roll => .archive/dice_roll}/icons/d6.svg | 0 {dice_roll => .archive/dice_roll}/icons/d8.svg | 0 {dice_roll => .archive/dice_roll}/icons/dice.svg | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {dice_roll => .archive/dice_roll}/README.md (100%) rename {dice_roll => .archive/dice_roll}/__init__.py (100%) rename {dice_roll => .archive/dice_roll}/icons/d10.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d100.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d12.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d2.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d20.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d4.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d6.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/d8.svg (100%) rename {dice_roll => .archive/dice_roll}/icons/dice.svg (100%) diff --git a/dice_roll/README.md b/.archive/dice_roll/README.md similarity index 100% rename from dice_roll/README.md rename to .archive/dice_roll/README.md diff --git a/dice_roll/__init__.py b/.archive/dice_roll/__init__.py similarity index 100% rename from dice_roll/__init__.py rename to .archive/dice_roll/__init__.py diff --git a/dice_roll/icons/d10.svg b/.archive/dice_roll/icons/d10.svg similarity index 100% rename from dice_roll/icons/d10.svg rename to .archive/dice_roll/icons/d10.svg diff --git a/dice_roll/icons/d100.svg b/.archive/dice_roll/icons/d100.svg similarity index 100% rename from dice_roll/icons/d100.svg rename to .archive/dice_roll/icons/d100.svg diff --git a/dice_roll/icons/d12.svg b/.archive/dice_roll/icons/d12.svg similarity index 100% rename from dice_roll/icons/d12.svg rename to .archive/dice_roll/icons/d12.svg diff --git a/dice_roll/icons/d2.svg b/.archive/dice_roll/icons/d2.svg similarity index 100% rename from dice_roll/icons/d2.svg rename to .archive/dice_roll/icons/d2.svg diff --git a/dice_roll/icons/d20.svg b/.archive/dice_roll/icons/d20.svg similarity index 100% rename from dice_roll/icons/d20.svg rename to .archive/dice_roll/icons/d20.svg diff --git a/dice_roll/icons/d4.svg b/.archive/dice_roll/icons/d4.svg similarity index 100% rename from dice_roll/icons/d4.svg rename to .archive/dice_roll/icons/d4.svg diff --git a/dice_roll/icons/d6.svg b/.archive/dice_roll/icons/d6.svg similarity index 100% rename from dice_roll/icons/d6.svg rename to .archive/dice_roll/icons/d6.svg diff --git a/dice_roll/icons/d8.svg b/.archive/dice_roll/icons/d8.svg similarity index 100% rename from dice_roll/icons/d8.svg rename to .archive/dice_roll/icons/d8.svg diff --git a/dice_roll/icons/dice.svg b/.archive/dice_roll/icons/dice.svg similarity index 100% rename from dice_roll/icons/dice.svg rename to .archive/dice_roll/icons/dice.svg From 134d26c5a5151f0116542e117fd7cd7c25f015ce Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Fri, 18 Aug 2023 16:17:50 +0300 Subject: [PATCH 076/243] [dice_roll] iid 2.0 --- {.archive/dice_roll => dice_roll}/README.md | 0 {.archive/dice_roll => dice_roll}/__init__.py | 41 +++++++++---------- .../dice_roll => dice_roll}/icons/d10.svg | 0 .../dice_roll => dice_roll}/icons/d100.svg | 0 .../dice_roll => dice_roll}/icons/d12.svg | 0 .../dice_roll => dice_roll}/icons/d2.svg | 0 .../dice_roll => dice_roll}/icons/d20.svg | 0 .../dice_roll => dice_roll}/icons/d4.svg | 0 .../dice_roll => dice_roll}/icons/d6.svg | 0 .../dice_roll => dice_roll}/icons/d8.svg | 0 .../dice_roll => dice_roll}/icons/dice.svg | 0 11 files changed, 20 insertions(+), 21 deletions(-) rename {.archive/dice_roll => dice_roll}/README.md (100%) rename {.archive/dice_roll => dice_roll}/__init__.py (84%) rename {.archive/dice_roll => dice_roll}/icons/d10.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d100.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d12.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d2.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d20.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d4.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d6.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/d8.svg (100%) rename {.archive/dice_roll => dice_roll}/icons/dice.svg (100%) diff --git a/.archive/dice_roll/README.md b/dice_roll/README.md similarity index 100% rename from .archive/dice_roll/README.md rename to dice_roll/README.md diff --git a/.archive/dice_roll/__init__.py b/dice_roll/__init__.py similarity index 84% rename from .archive/dice_roll/__init__.py rename to dice_roll/__init__.py index 5a338b75..95f95031 100644 --- a/.archive/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -15,7 +15,7 @@ """ md_iid = '2.0' -md_version = "1.2" +md_version = "1.3" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -39,7 +39,7 @@ def get_icon_path(num_sides: int | None) -> str: if num_sides is None: icon = "dice" # return the path to the icon - return str("file:" + icons_path / f"{icon}.svg") + return str(f"file:{icons_path / f'{icon}.svg'}") def roll_dice(num_dice: int, num_sides: int) -> tuple[int, list[int]]: @@ -127,29 +127,28 @@ def get_items(query_string: str) -> list[albert.Item]: return results -class Plugin(albert.TriggerQueryHandler): +class Plugin(albert.PluginInstance, albert.TriggerQueryHandler): """A plugin to roll dice""" - def id(self) -> str: - return md_id - - def name(self) -> str: - return md_name - - def description(self) -> str: - return md_description - - def synopsis(self) -> str: - return "d [d ...]" - - def defaultTrigger(self) -> str: - return "roll " - - def handleTriggerQuery(self, query: albert.TriggerQuery) -> None: + def __init__(self): + albert.TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis="d [d ...]", + defaultTrigger="roll ", + ) + albert.PluginInstance.__init__(self, extensions=[self]) + + def handleTriggerQuery(self, query: albert.Query) -> None: query_string = query.string.strip() try: items = get_items(query_string) query.add(items) except Exception as e: - albert.warning(e) - albert.info("Something went wrong. Make sure you're using the correct format.") + query.add([albert.StandardItem( + id="error", + iconUrls=[get_icon_path(None)], + text="Something went wrong.", + subtext="Make sure you're using the correct format.", + )]) diff --git a/.archive/dice_roll/icons/d10.svg b/dice_roll/icons/d10.svg similarity index 100% rename from .archive/dice_roll/icons/d10.svg rename to dice_roll/icons/d10.svg diff --git a/.archive/dice_roll/icons/d100.svg b/dice_roll/icons/d100.svg similarity index 100% rename from .archive/dice_roll/icons/d100.svg rename to dice_roll/icons/d100.svg diff --git a/.archive/dice_roll/icons/d12.svg b/dice_roll/icons/d12.svg similarity index 100% rename from .archive/dice_roll/icons/d12.svg rename to dice_roll/icons/d12.svg diff --git a/.archive/dice_roll/icons/d2.svg b/dice_roll/icons/d2.svg similarity index 100% rename from .archive/dice_roll/icons/d2.svg rename to dice_roll/icons/d2.svg diff --git a/.archive/dice_roll/icons/d20.svg b/dice_roll/icons/d20.svg similarity index 100% rename from .archive/dice_roll/icons/d20.svg rename to dice_roll/icons/d20.svg diff --git a/.archive/dice_roll/icons/d4.svg b/dice_roll/icons/d4.svg similarity index 100% rename from .archive/dice_roll/icons/d4.svg rename to dice_roll/icons/d4.svg diff --git a/.archive/dice_roll/icons/d6.svg b/dice_roll/icons/d6.svg similarity index 100% rename from .archive/dice_roll/icons/d6.svg rename to dice_roll/icons/d6.svg diff --git a/.archive/dice_roll/icons/d8.svg b/dice_roll/icons/d8.svg similarity index 100% rename from .archive/dice_roll/icons/d8.svg rename to dice_roll/icons/d8.svg diff --git a/.archive/dice_roll/icons/dice.svg b/dice_roll/icons/dice.svg similarity index 100% rename from .archive/dice_roll/icons/dice.svg rename to dice_roll/icons/dice.svg From a7cda531d3dc74bb3a3de11ff729421565a54987 Mon Sep 17 00:00:00 2001 From: Peter Oettig Date: Wed, 23 Aug 2023 09:25:16 +0200 Subject: [PATCH 077/243] [jetbrains_projects] Catch FileNotFoundError --- jetbrains_projects/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 9a5258c3..d8dc59f6 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -78,7 +78,7 @@ def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: Project(name=Path(project_path).name, path=project_path, last_opened=int(last_opened)) ) return projects - except ElementTree.ParseError: + except (ElementTree.ParseError, FileNotFoundError): return [] From b4563c204e456b3f4f30410f3af60b27a3f8fec3 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 26 Aug 2023 13:29:21 +0200 Subject: [PATCH 078/243] [goldendict] Fix import error Related https://github.com/albertlauncher/plugins/issues/119 --- goldendict/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index 6b1aba14..92ef6d95 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,4 +1,4 @@ -from albert import Action, Item, TriggerQuery, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error +from albert import Action, Item, TriggerQuery, PluginInstance, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error md_iid = '2.0' md_version = '1.3' From 2e57e703faf51de4148366fb65c12f32f930fe3c Mon Sep 17 00:00:00 2001 From: ismail soudi <30784313+ismasou@users.noreply.github.com> Date: Tue, 29 Aug 2023 04:20:42 -0400 Subject: [PATCH 079/243] [mathematica_eval] Fixed missing import --- mathematica_eval/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mathematica_eval/__init__.py b/mathematica_eval/__init__.py index 6bb38c4d..31de13cc 100644 --- a/mathematica_eval/__init__.py +++ b/mathematica_eval/__init__.py @@ -4,8 +4,7 @@ from tempfile import NamedTemporaryFile from threading import Lock -from albert import (Action, Item, TriggerQuery, TriggerQueryHandler, - setClipboardText) +from albert import * md_iid = "2.0" md_version = "1.1" From c9ebe8935725c3c434f460759086921a9f1a46e9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 29 Aug 2023 21:06:03 +0200 Subject: [PATCH 080/243] [stub] Config prototype --- albert.pyi | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/albert.pyi b/albert.pyi index bb910509..b659c7d8 100644 --- a/albert.pyi +++ b/albert.pyi @@ -1,6 +1,6 @@ """ -# Albert Python interface v2.0 +# Albert Python interface v2.1 The Python interface is a subset of the internal C++ interface exposed to Python with some minor adjustments. A Python @@ -90,6 +90,27 @@ class PluginInstance(ABC): def finalize(self): ... + def readConfig(self, key: str, type: type[str|int|float|bool]) -> str|int|float|bool|None: + ... + + def writeConfig(self, key: str, value: str|int|float|bool): + ... + + def configWidget(self): + """ + [ + { + 'type': 'lineedit'|'checkbox'|'spinbox'|'doublespinbox', + 'property_name': '…', + 'display_name': '…', + 'widget_properties': { + 'widget_property': bool|int|float|string, + … + } + }, + … + ] + """ class Action: """https://albertlauncher.github.io/reference/classalbert_1_1_action.html""" From 7bd0931f550ddec795d1c3a3e8a8b2e2faa53981 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 29 Aug 2023 21:09:05 +0200 Subject: [PATCH 081/243] [emoji] Add "Use derived emojis" option --- emoji/__init__.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index c8190ea9..fe918e6d 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -10,8 +10,8 @@ from albert import * -md_iid = '2.0' -md_version = "2.0" +md_iid = '2.1' +md_version = "2.1" md_name = "Emoji" md_description = "Find and copy emojis by name" md_license = "MIT" @@ -30,10 +30,33 @@ def __init__(self): PluginInstance.__init__(self, extensions=[self]) self.thread = None + self._use_derived = self.readConfig('use_derived', bool) + if self._use_derived is None: + self._use_derived = False + def finalize(self): if self.thread.is_alive(): self.thread.join() + @property + def use_derived(self): + return self._use_derived + + @use_derived.setter + def use_derived(self, value): + self._use_derived = value + self.writeConfig('use_derived', value) + self.updateIndexItems() + + def configWidget(self): + return [ + { + 'type': 'checkbox', + 'property': 'use_derived', + 'label': 'Use derived emojis' + } + ] + def updateIndexItems(self): if self.thread and self.thread.is_alive(): self.thread.join() @@ -99,7 +122,7 @@ def convert_to_unicode_str(hex_codes: str): return fully_qualified - def get_annotations(cache_path: str) -> dict: + def get_annotations(cache_path: str, use_derived: bool) -> dict: # determine locale @@ -117,6 +140,12 @@ def get_annotations(cache_path: str) -> dict: 'cldr-annotations-full/annotations/%s/annotations.json' % lang download_file(url, path_full) + with path_full.open("r", encoding='utf-8') as file_full: + json_full = json.load(file_full)['annotations']['annotations'] + + if not use_derived: + return json_full + # fetch localized cldr annotations 'derived' path_derived = cache_path / 'emoji_annotations_derived.json' @@ -127,13 +156,12 @@ def get_annotations(cache_path: str) -> dict: # open, read, parse, merge, return - with path_full.open("r", encoding='utf-8') as file_full, \ - path_derived.open("r", encoding='utf-8') as file_derived: - json_full = json.load(file_full)['annotations']['annotations'] + with path_derived.open("r", encoding='utf-8') as file_derived: json_derived = json.load(file_derived)['annotationsDerived']['annotations'] return json_full | json_derived + emojis = get_fully_qualified_emojis(self.cacheLocation) - annotations = get_annotations(self.cacheLocation) + annotations = get_annotations(self.cacheLocation, self.use_derived) def remove_redundancy(sentences): sets_of_words = [set(sentence.lower().split()) for sentence in sentences] @@ -156,7 +184,7 @@ def remove_redundancy(sentences): non_rgi_emoji = emoji.replace('\uFE0F', '') ann = annotations[non_rgi_emoji] except KeyError as e: - debug(f"Found no translation for {e}. Emoji will not be available.") + # debug(f"Found no translation for {e}. Emoji will not be available.") continue title = ann['tts'][0] From 9229b0cce7f92bdbd8bc652bc55b55415d550540 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 30 Aug 2023 08:53:48 +0200 Subject: [PATCH 082/243] [translators] Add "translators" plugin --- translators/__init__.py | 120 +++++++++++++++++++++++++++++++ translators/google_translate.png | Bin 0 -> 51950 bytes 2 files changed, 120 insertions(+) create mode 100644 translators/__init__.py create mode 100644 translators/google_translate.png diff --git a/translators/__init__.py b/translators/__init__.py new file mode 100644 index 00000000..4bc994d8 --- /dev/null +++ b/translators/__init__.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +""" +Translates text using the python package translators. See https://pypi.org/project/translators/ +""" + +from locale import getdefaultlocale +from pathlib import Path +from time import sleep + +from albert import * +import translators as ts + +md_iid = '2.0' +md_version = "1.3" +md_name = "Translator" +md_description = "Translate sentences using 'translators' package" +md_license = "BSD-3" +md_url = "https://github.com/albertlauncher/python/translators" +md_lib_dependencies = "translators" + + +class Plugin(PluginInstance, TriggerQueryHandler): + + def __init__(self): + TriggerQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + synopsis="[[from] to] text", + defaultTrigger='tr ') + PluginInstance.__init__(self, extensions=[self]) + self.iconUrls = [f"file:{Path(__file__).parent}/google_translate.png"] + + self._translator = self.readConfig('translator', str) + if self._translator is None: + self._translator = 'google' + + self._lang = self.readConfig('lang', str) + if self._lang is None: + self._lang = getdefaultlocale()[0][0:2] + + try: + languages = ts.get_languages(self.translator) + self.src_languages = set(languages.keys()) + self.dst_languages = set(languages[self.lang]) + except Exception as e: + warning(str(e)) + + @property + def translator(self): + return self._translator + + @translator.setter + def translator(self, value): + self._translator = value + self.writeConfig('translator', value) + languages = ts.get_languages(self.translator) + self.src_languages = set(languages.keys()) + self.dst_languages = set(languages[self.lang]) + + @property + def lang(self): + return self._lang + + @lang.setter + def lang(self, value): + self._lang = value + self.writeConfig('lang', value) + + def configWidget(self): + return [ + { + 'type': 'combobox', + 'property': 'translator', + 'label': 'Translator', + 'items': ts.translators_pool + }, + { + 'type': 'lineedit', + 'property': 'lang', + 'label': 'Default language', + } + ] + + def handleTriggerQuery(self, query): + stripped = query.string.strip() + if stripped: + for _ in range(50): + sleep(0.01) + if not query.isValid: + return + + if len(splits := stripped.split(maxsplit=2)) == 3 \ + and splits[0] in self.src_languages and splits[1] in self.dst_languages: + src, dst, text = splits + elif len(splits := stripped.split(maxsplit=1)) == 2 and splits[0] in self.src_languages: + src, dst, text = splits[0], self.lang, splits[1] + else: + src, dst, text = 'auto', self.lang, stripped + + translation = ts.translate_text(query_text=text, + translator=self.translator, + from_language=src, + to_language=dst) + + query.add(StandardItem( + id=md_id, + text=translation, + subtext=f"{src.upper()} > {dst.upper()}", + iconUrls=self.iconUrls, + actions=[ + Action( + "paste", "Copy to clipboard and paste to front-most window", + lambda t=translation: setClipboardTextAndPaste(t) + ), + Action("copy", "Copy to clipboard", + lambda t=translation: setClipboardText(t)) + ] + )) diff --git a/translators/google_translate.png b/translators/google_translate.png new file mode 100644 index 0000000000000000000000000000000000000000..63617cf578c9f2b502ea30349dd61b0a7604d8a1 GIT binary patch literal 51950 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
- -``` -Output of `albert -d` when run in a terminal -``` - -
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index bf083087..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,14 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Start a discussion - url: https://github.com/orgs/albertlauncher/discussions/new/choose - about: Post the link to the discussion in the chats to get more attention. - - name: Chat on Discord - url: https://discord.gg/t8G2EkvRZh - about: Bridged community chat. - - name: Chat on Telegram - url: https://telegram.me/albert_launcher_community - about: Bridged community chat. - - name: Chat on on IRC (libera#albertlauncher) - url: https://web.libera.chat/#albertlauncher - about: Bridged community chat. From 841bb4afb731500d8e8202896412664edc3ef6f0 Mon Sep 17 00:00:00 2001 From: DavidDeadly Date: Thu, 19 Oct 2023 20:39:43 -0500 Subject: [PATCH 094/243] [bitwarden] 2.0 * add copy-username action * segregate actions callbacks This helps readability and performance. Before password and code subprocesses were running for each filtered_password. Now they only run when the user requests it. * enhance filter_items readability * remove 'Unlock' item in favor of 'Sync' 'Unlock' item is unncesary because rbw binary by default asks for the master password on any action, like listing (__get_items). The only reason maybe to use it before might probably be to sync with the cloud items, and that can be done via 'rbw sync'. * rename actions methods --- bitwarden/__init__.py | 175 +++++++++++++++++++++++++----------------- 1 file changed, 103 insertions(+), 72 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 78d39f51..23b8bcdc 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = '2.0' -md_version = "1.3" +md_iid = '2.1' +md_version = "2.0" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "BSD-3" @@ -27,79 +27,28 @@ def __init__(self): PluginInstance.__init__(self, extensions=[self]) self.iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] - def _get_passwords(self): - field_names = ["id", "name", "user", "folder"] - p = run( - ["rbw", "list", "--fields", ",".join(field_names)], - capture_output=True, - encoding="utf-8", - check=True, - ) - passwords = [] - for l in p.stdout.splitlines(): - fields = l.split("\t") - d = dict(zip(field_names, fields)) - if d["folder"]: - d["path"] = d["folder"] + "/" + d["name"] - else: - d["path"] = d["name"] - passwords.append(d) - - return passwords - def handleTriggerQuery(self, query): - if query.string.strip().lower() == "unlock": + if query.string.strip().lower() == "sync": query.add( StandardItem( - id="unlock", - text="Unlock Bitwarden Vault", + id="sync", + text="Sync Bitwarden Vault", iconUrls=self.iconUrls, actions=[ Action( - id="unlock", - text="Unlocking Bitwarden Vault", - callable=lambda: runTerminal( - script="rbw stop-agent && rbw unlock", - close_on_exit=True + id="sync", + text="Syncing Bitwarden Vault", + callable=lambda: run( + ["rbw", "sync"], ) ) ] ) ) - passwords = self._get_passwords() - filtered_passwords = [] - search_fields = ["path", "user"] - words = query.string.strip().lower().split() - for p in passwords: - all_matches = True - for w in words: - for k in search_fields: - if w in p[k].lower(): - break - else: - all_matches = False - break - - if all_matches: - filtered_passwords.append(p) + filtered_items = self._filter_items(query) - for p in filtered_passwords: - pw = run( - ["rbw", "get", p["id"]], - capture_output=True, - encoding="utf-8", - check=True - ) - try: - code = run( - ["rbw", "code", p["id"]], - capture_output=True, - encoding="utf-8", - check=True - ) - except CalledProcessError as err: - code = run (["echo"], capture_output=True,encoding="utf-8", check=True) + for p in filtered_items: query.add( StandardItem( id=p["id"], @@ -110,24 +59,106 @@ def handleTriggerQuery(self, query): Action( id="copy", text="Copy password to clipboard", - callable=lambda password=pw.stdout.strip(): setClipboardText( - text=password) + callable=lambda item=p: self._password_to_clipboard(item) ), Action( id="copy-auth", text="Copy auth code to clipboard", - callable=lambda code=code.stdout.strip(): setClipboardText( - text=code) + callable=lambda item=p: self._code_to_clipboard(item) + ), + Action( + id="copy-username", + text="Copy username to clipboard", + callable=lambda username=p["user"]: + setClipboardText(text=username) ), Action( id="edit", text="Edit entry in terminal", - callable=lambda pid=p['id']: runTerminal( - script=f"rbw edit {pid}", - workdir="~", - close_on_exit=False - ) + callable=lambda item=p: self._edit_entry(item) ) ] ) - ) \ No newline at end of file + ) + + def _get_items(self): + field_names = ["id", "name", "user", "folder"] + raw_items = run( + ["rbw", "list", "--fields", ",".join(field_names)], + capture_output=True, + encoding="utf-8", + check=True, + ) + + items = [] + + for line in raw_items.stdout.splitlines(): + fields = line.split("\t") + item = dict(zip(field_names, fields)) + + if item["folder"]: + item["path"] = item["folder"] + "/" + item["name"] + else: + item["path"] = item["name"] + items.append(item) + + return items + + def _filter_items(self, query): + passwords = self._get_items() + search_fields = ["path", "user"] + # Use a set for faster membership tests + words = set(query.string.strip().lower().split()) + + filtered_passwords = [] + + for p in passwords: + match_all_words_with_any_field = all( + any(word in p[field].lower() for field in search_fields) + for word in words + ) + + if match_all_words_with_any_field: + filtered_passwords.append(p) + + return filtered_passwords + + def _password_to_clipboard(self, item): + id = item["id"] + + password = run( + ["rbw", "get", id], + capture_output=True, + encoding="utf-8", + check=True + ).stdout.strip() + + setClipboardText(text=password) + + def _code_to_clipboard(self, item): + id = item["id"] + + try: + code = run( + ["rbw", "code", id], + capture_output=True, + encoding="utf-8", + check=True + ).stdout.strip() + except CalledProcessError as err: + code = run( + ["echo", err.__str__()], + capture_output=True, + encoding="utf-8", + check=True + ).stdout.strip() + + setClipboardText(text=code) + + def _edit_entry(self, item): + id = item["id"] + + runTerminal( + script=f"rbw edit {id}", + close_on_exit=True + ) From 9310a97eeafd87e582a69ad24afc836a62325372 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 25 Nov 2023 20:23:16 +0100 Subject: [PATCH 095/243] [awiki:1.5] Fix search fallback --- arch_wiki/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch_wiki/__init__.py b/arch_wiki/__init__.py index cc70f54e..a1b243be 100644 --- a/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -8,7 +8,7 @@ from albert import * md_iid = '2.0' -md_version = "1.4" +md_version = "1.5" md_name = "ArchLinux Wiki" md_description = "Search ArchLinux Wiki articles" md_license = "BSD-3" @@ -75,7 +75,8 @@ def handleTriggerQuery(self, query): text="Search '%s'" % query.string, subtext="No results. Start online search on Arch Wiki", iconUrls=self.iconUrls, - actions=[Action("search", "Open search", lambda s=query.string: self.search_url % s)])) + actions=[Action("search", "Open search", + lambda s=query.string: openUrl(self.search_url % s))])) else: query.add(StandardItem(id=md_id, From 2067bbb3d8fa5cfa5df2be9cada29a7e6715f07a Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 26 Nov 2023 02:17:06 +0100 Subject: [PATCH 096/243] [translator:1.4] Add timeout --- translators/__init__.py | 53 +++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 3f1f9b3d..dbcac7b5 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -12,7 +12,7 @@ import translators as ts md_iid = '2.0' -md_version = "1.3" +md_version = "1.4" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "BSD-3" @@ -99,22 +99,35 @@ def handleTriggerQuery(self, query): else: src, dst, text = 'auto', self.lang, stripped - translation = ts.translate_text(query_text=text, - translator=self.translator, - from_language=src, - to_language=dst) - - query.add(StandardItem( - id=md_id, - text=translation, - subtext=f"{src.upper()} > {dst.upper()}", - iconUrls=self.iconUrls, - actions=[ - Action( - "paste", "Copy to clipboard and paste to front-most window", - lambda t=translation: setClipboardTextAndPaste(t) - ), - Action("copy", "Copy to clipboard", - lambda t=translation: setClipboardText(t)) - ] - )) + try: + translation = ts.translate_text(query_text=text, + translator=self.translator, + from_language=src, + to_language=dst, + timeout=5) + + query.add(StandardItem( + id=md_id, + text=translation, + subtext=f"{src.upper()} > {dst.upper()}", + iconUrls=self.iconUrls, + actions=[ + Action( + "paste", "Copy to clipboard and paste to front-most window", + lambda t=translation: setClipboardTextAndPaste(t) + ), + Action("copy", "Copy to clipboard", + lambda t=translation: setClipboardText(t)) + ] + )) + + except Exception as e: + + query.add(StandardItem( + id=md_id, + text="Error", + subtext=str(e), + iconUrls=self.iconUrls + )) + + warning(str(e)) \ No newline at end of file From 7f3bb83644a3a211405a3c62b3edf1f26e930318 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 28 Dec 2023 15:16:01 +0100 Subject: [PATCH 097/243] [pacman:1.9] Fix pgk site url --- pacman/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pacman/__init__.py b/pacman/__init__.py index 51cb829d..856b7c77 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -12,7 +12,7 @@ from albert import Action, StandardItem, PluginInstance, TriggerQueryHandler, runTerminal, openUrl md_iid = '2.0' -md_version = "1.8" +md_version = "1.9" md_name = "PacMan" md_description = "Search, install and remove packages" md_license = "BSD-3" @@ -87,8 +87,9 @@ def handleTriggerQuery(self, query): ]) else: actions.append(Action("inst", "Install", lambda n=pkg_name: runTerminal("sudo pacman -S %s" % n))) + actions.append(Action("pkg_url", "Show on packages.archlinux.org", - lambda r=pkg_repo, n=pkg_name: openUrl(f"{r}/x86_64/{n}/"))) + lambda r=pkg_repo, n=pkg_name: openUrl(f"{self.pkgs_url}{r}/x86_64/{n}/"))) if pkg_purl: actions.append(Action("proj_url", "Show project website", lambda u=pkg_purl: openUrl(u))) From f12ef96656be340283a20b00d36a212eb57d754b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 28 Dec 2023 22:46:17 +0100 Subject: [PATCH 098/243] =?UTF-8?q?[inhibit=5Fsleep:1.0]=20Similar=20to=20?= =?UTF-8?q?caffeine,=20theine,=20amphetamine=20etc=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inhibit_sleep/__init__.py | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 inhibit_sleep/__init__.py diff --git a/inhibit_sleep/__init__.py b/inhibit_sleep/__init__.py new file mode 100644 index 00000000..c5f56427 --- /dev/null +++ b/inhibit_sleep/__init__.py @@ -0,0 +1,63 @@ +""" +Provides an item 'Inhibit sleep' which can be used to temporarily disable system suspension. + +This is a prototype using `systemd-inhibit`. A sophisticated implementation would probably use the systemd D-Bus +interface documented [here](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html). +""" + +from albert import * +from subprocess import Popen, TimeoutExpired + +md_iid = '2.0' +md_version = '1.0' +md_name = 'Inhibit sleep' +md_description = 'Inhibit system sleep mode.' +md_url = 'https://github.com/albertlauncher/python/inhibit_sleep' +md_bin_dependencies = ['systemd-inhibit', "sleep"] + + +class Plugin(PluginInstance, GlobalQueryHandler): + + def __init__(self): + GlobalQueryHandler.__init__(self, + id=md_id, + name=md_name, + description=md_description, + defaultTrigger='is ') + PluginInstance.__init__(self, extensions=[self]) + self.proc = None + + def finalize(self): + if self.proc: + self.toggle() + + def toggle(self): + if self.proc: + self.proc.terminate() + try: + self.proc.wait(timeout=1) + except TimeoutExpired: + self.proc.kill() + self.proc = None + else: + self.proc = Popen(["systemd-inhibit", + "--what=idle:sleep", "--who=Albert", "--why=User", + "sleep", "infinity"]) + info(str(self.proc)) + + def handleGlobalQuery(self, query: GlobalQuery): + stripped = query.string.strip().lower() + if stripped in "inhibit sleep": + return [ + RankItem( + StandardItem( + id=md_name, + text=md_name, + subtext=f"{'Enable' if self.proc else 'Disable'} sleep mode", + iconUrls=[f"gen:?text=💤"], + actions=[Action("inhibit", "Toggle", self.toggle)] + ), + len(stripped)/len(md_name)) + + ] + return [] From 3a22cb4e684fdb7506363be73c93573224719a4b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 29 Dec 2023 13:53:47 +0100 Subject: [PATCH 099/243] Update albert.pyi --- albert.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/albert.pyi b/albert.pyi index a105ec78..787ec318 100644 --- a/albert.pyi +++ b/albert.pyi @@ -19,7 +19,8 @@ md_description: str | A brief, imperative description. (Like "Launch apps" or "O ## Optional metadata variables: -md_id | Identifier overwrite. [a-zA-Z0-9_]. Defaults to module name. +md_id | Identifier overwrite. [a-zA-Z0-9_]. Note: This variable is attached at runtime + | if it is unset and defaults to the module name. __doc__ | The docstring of the module is used as long description/readme of the extension. md_license: str | Short form e.g. BSD-2-Clause or GPL-3.0 md_url: str | Browsable source, issues etc @@ -35,8 +36,7 @@ The plugin class is the entry point for a Python plugin. It is instantiated on p PluginInstance. Implement extensions by subclassing _one_ extension class (TriggerQueryHandler etc…) provided by the built-in `albert` module and pass the list of your extensions to the PluginInstance init function. Due to the differences in type systems multiple inheritance of extensions is not supported. (Python does not support virtual -inheritance, which is used in the C++ space to inherit from 'Extension'). For more details see - +inheritance, which is used in the C++ space to inherit from 'Extension'). """ From f59426454c683904b61c9bd379c00930319dbf86 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 29 Dec 2023 14:51:47 +0100 Subject: [PATCH 100/243] Update README.md --- README.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 81e3ce5b..d6802f24 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,23 @@ -### This is the official repository for python plugins. +## Official Albert Python plugin repository -This repository is shipped with albert. If you want to have bleeding edge plugins or share your work clone the repository. Check the [docs on Python plugins](https://github.com/albertlauncher/plugins/blob/master/python/README.md). To install the plugins in user space type the following in your terminal: - -```shell -git clone https://github.com/albertlauncher/python.git ~/.local/share/albert/python/plugins -``` - -Credits go to our contributors +This repository is shipped with Albert. Credits go to our contributors 👍 + +### Contribution + +* Fork this repository. +* Clone it into the Python user plugin location. + ```shell + # on linux + git clone https://github.com//python.git ~/.local/share/albert/python/plugins + + # on macos + git clone https://github.com//python.git ~/Library/Application\ Support/albert/python/plugins + ``` +* Open the directory in your favorite IDE (PyCharmCE is a good choice). +* Write your plugin (Make sure it is upstream-polished-enough though). + This repository ships a [python stub file](https://github.com/albertlauncher/python/blob/master/albert.pyi) which gives you coding assistance. +* Commit, push, send a PR. From f7114926c9a5ccee0e48b9387061fd10652a1370 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 13 Jan 2024 12:21:38 +0100 Subject: [PATCH 101/243] Licensing has to be clearified --- {arch_wiki => licensing/arch_wiki}/__init__.py | 0 {arch_wiki => licensing/arch_wiki}/arch.svg | 4 ---- {aur => licensing/aur}/__init__.py | 0 {aur => licensing/aur}/arch.svg | 4 ---- {bitwarden => licensing/bitwarden}/__init__.py | 0 {bitwarden => licensing/bitwarden}/bw.svg | 0 {coingecko => licensing/coingecko}/__init__.py | 0 {coingecko => licensing/coingecko}/coingecko.png | Bin {color => licensing/color}/__init__.py | 0 {copyq => licensing/copyq}/__init__.py | 0 {dice_roll => licensing/dice_roll}/README.md | 0 {dice_roll => licensing/dice_roll}/__init__.py | 0 {dice_roll => licensing/dice_roll}/icons/d10.svg | 0 {dice_roll => licensing/dice_roll}/icons/d100.svg | 0 {dice_roll => licensing/dice_roll}/icons/d12.svg | 0 {dice_roll => licensing/dice_roll}/icons/d2.svg | 0 {dice_roll => licensing/dice_roll}/icons/d20.svg | 0 {dice_roll => licensing/dice_roll}/icons/d4.svg | 0 {dice_roll => licensing/dice_roll}/icons/d6.svg | 0 {dice_roll => licensing/dice_roll}/icons/d8.svg | 0 {dice_roll => licensing/dice_roll}/icons/dice.svg | 0 {docker => licensing/docker}/__init__.py | 0 {docker => licensing/docker}/running.png | Bin {docker => licensing/docker}/stopped.png | Bin {duckduckgo => licensing/duckduckgo}/__init__.py | 0 {duckduckgo => licensing/duckduckgo}/duckduckgo.svg | 0 {emoji => licensing/emoji}/__init__.py | 0 {goldendict => licensing/goldendict}/__init__.py | 0 .../jetbrains_projects}/LICENSE | 0 .../jetbrains_projects}/README.md | 0 .../jetbrains_projects}/__init__.py | 1 + .../jetbrains_projects}/androidstudio.svg | 0 .../jetbrains_projects}/clion.svg | 0 .../jetbrains_projects}/datagrip.svg | 0 .../jetbrains_projects}/dataspell.svg | 0 .../jetbrains_projects}/goland.svg | 0 .../jetbrains_projects}/idea.svg | 0 .../jetbrains_projects}/phpstorm.svg | 0 .../jetbrains_projects}/pycharm.svg | 0 .../jetbrains_projects}/rider.svg | 0 .../jetbrains_projects}/rubymine.svg | 0 .../jetbrains_projects}/rustrover.svg | 0 .../jetbrains_projects}/webstorm.svg | 0 {kill => licensing/kill}/__init__.py | 0 {locate => licensing/locate}/__init__.py | 0 {locate => licensing/locate}/locate.svg | 0 .../mathematica_eval}/__init__.py | 0 {pacman => licensing/pacman}/__init__.py | 0 {pacman => licensing/pacman}/arch.svg | 4 ---- {pass => licensing/pass}/__init__.py | 0 {pomodoro => licensing/pomodoro}/__init__.py | 0 {pomodoro => licensing/pomodoro}/pomodoro.svg | 4 ---- {python_eval => licensing/python_eval}/__init__.py | 0 {python_eval => licensing/python_eval}/python.svg | 0 .../tex_to_unicode}/__init__.py | 0 .../tex_to_unicode}/tex.png | Bin {timer => licensing/timer}/__init__.py | 0 {timer => licensing/timer}/time.svg | 4 ---- {translators => licensing/translators}/__init__.py | 0 .../translators}/google_translate.png | Bin {virtualbox => licensing/virtualbox}/__init__.py | 0 {vpn => licensing/vpn}/__init__.py | 0 {wikipedia => licensing/wikipedia}/__init__.py | 0 {wikipedia => licensing/wikipedia}/wikipedia.png | Bin {zeal => licensing/zeal}/__init__.py | 0 65 files changed, 1 insertion(+), 20 deletions(-) rename {arch_wiki => licensing/arch_wiki}/__init__.py (100%) rename {arch_wiki => licensing/arch_wiki}/arch.svg (97%) rename {aur => licensing/aur}/__init__.py (100%) rename {aur => licensing/aur}/arch.svg (97%) rename {bitwarden => licensing/bitwarden}/__init__.py (100%) rename {bitwarden => licensing/bitwarden}/bw.svg (100%) rename {coingecko => licensing/coingecko}/__init__.py (100%) rename {coingecko => licensing/coingecko}/coingecko.png (100%) rename {color => licensing/color}/__init__.py (100%) rename {copyq => licensing/copyq}/__init__.py (100%) rename {dice_roll => licensing/dice_roll}/README.md (100%) rename {dice_roll => licensing/dice_roll}/__init__.py (100%) rename {dice_roll => licensing/dice_roll}/icons/d10.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d100.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d12.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d2.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d20.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d4.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d6.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/d8.svg (100%) rename {dice_roll => licensing/dice_roll}/icons/dice.svg (100%) rename {docker => licensing/docker}/__init__.py (100%) rename {docker => licensing/docker}/running.png (100%) rename {docker => licensing/docker}/stopped.png (100%) rename {duckduckgo => licensing/duckduckgo}/__init__.py (100%) rename {duckduckgo => licensing/duckduckgo}/duckduckgo.svg (100%) rename {emoji => licensing/emoji}/__init__.py (100%) rename {goldendict => licensing/goldendict}/__init__.py (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/LICENSE (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/README.md (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/__init__.py (99%) rename {jetbrains_projects => licensing/jetbrains_projects}/androidstudio.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/clion.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/datagrip.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/dataspell.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/goland.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/idea.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/phpstorm.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/pycharm.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/rider.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/rubymine.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/rustrover.svg (100%) rename {jetbrains_projects => licensing/jetbrains_projects}/webstorm.svg (100%) rename {kill => licensing/kill}/__init__.py (100%) rename {locate => licensing/locate}/__init__.py (100%) rename {locate => licensing/locate}/locate.svg (100%) rename {mathematica_eval => licensing/mathematica_eval}/__init__.py (100%) rename {pacman => licensing/pacman}/__init__.py (100%) rename {pacman => licensing/pacman}/arch.svg (97%) rename {pass => licensing/pass}/__init__.py (100%) rename {pomodoro => licensing/pomodoro}/__init__.py (100%) rename {pomodoro => licensing/pomodoro}/pomodoro.svg (97%) rename {python_eval => licensing/python_eval}/__init__.py (100%) rename {python_eval => licensing/python_eval}/python.svg (100%) rename {tex_to_unicode => licensing/tex_to_unicode}/__init__.py (100%) rename {tex_to_unicode => licensing/tex_to_unicode}/tex.png (100%) rename {timer => licensing/timer}/__init__.py (100%) rename {timer => licensing/timer}/time.svg (99%) rename {translators => licensing/translators}/__init__.py (100%) rename {translators => licensing/translators}/google_translate.png (100%) rename {virtualbox => licensing/virtualbox}/__init__.py (100%) rename {vpn => licensing/vpn}/__init__.py (100%) rename {wikipedia => licensing/wikipedia}/__init__.py (100%) rename {wikipedia => licensing/wikipedia}/wikipedia.png (100%) rename {zeal => licensing/zeal}/__init__.py (100%) diff --git a/arch_wiki/__init__.py b/licensing/arch_wiki/__init__.py similarity index 100% rename from arch_wiki/__init__.py rename to licensing/arch_wiki/__init__.py diff --git a/arch_wiki/arch.svg b/licensing/arch_wiki/arch.svg similarity index 97% rename from arch_wiki/arch.svg rename to licensing/arch_wiki/arch.svg index 61f55ed9..b95bef86 100644 --- a/arch_wiki/arch.svg +++ b/licensing/arch_wiki/arch.svg @@ -1,5 +1 @@ - - \ No newline at end of file diff --git a/aur/__init__.py b/licensing/aur/__init__.py similarity index 100% rename from aur/__init__.py rename to licensing/aur/__init__.py diff --git a/aur/arch.svg b/licensing/aur/arch.svg similarity index 97% rename from aur/arch.svg rename to licensing/aur/arch.svg index 61f55ed9..b95bef86 100644 --- a/aur/arch.svg +++ b/licensing/aur/arch.svg @@ -1,5 +1 @@ - - \ No newline at end of file diff --git a/bitwarden/__init__.py b/licensing/bitwarden/__init__.py similarity index 100% rename from bitwarden/__init__.py rename to licensing/bitwarden/__init__.py diff --git a/bitwarden/bw.svg b/licensing/bitwarden/bw.svg similarity index 100% rename from bitwarden/bw.svg rename to licensing/bitwarden/bw.svg diff --git a/coingecko/__init__.py b/licensing/coingecko/__init__.py similarity index 100% rename from coingecko/__init__.py rename to licensing/coingecko/__init__.py diff --git a/coingecko/coingecko.png b/licensing/coingecko/coingecko.png similarity index 100% rename from coingecko/coingecko.png rename to licensing/coingecko/coingecko.png diff --git a/color/__init__.py b/licensing/color/__init__.py similarity index 100% rename from color/__init__.py rename to licensing/color/__init__.py diff --git a/copyq/__init__.py b/licensing/copyq/__init__.py similarity index 100% rename from copyq/__init__.py rename to licensing/copyq/__init__.py diff --git a/dice_roll/README.md b/licensing/dice_roll/README.md similarity index 100% rename from dice_roll/README.md rename to licensing/dice_roll/README.md diff --git a/dice_roll/__init__.py b/licensing/dice_roll/__init__.py similarity index 100% rename from dice_roll/__init__.py rename to licensing/dice_roll/__init__.py diff --git a/dice_roll/icons/d10.svg b/licensing/dice_roll/icons/d10.svg similarity index 100% rename from dice_roll/icons/d10.svg rename to licensing/dice_roll/icons/d10.svg diff --git a/dice_roll/icons/d100.svg b/licensing/dice_roll/icons/d100.svg similarity index 100% rename from dice_roll/icons/d100.svg rename to licensing/dice_roll/icons/d100.svg diff --git a/dice_roll/icons/d12.svg b/licensing/dice_roll/icons/d12.svg similarity index 100% rename from dice_roll/icons/d12.svg rename to licensing/dice_roll/icons/d12.svg diff --git a/dice_roll/icons/d2.svg b/licensing/dice_roll/icons/d2.svg similarity index 100% rename from dice_roll/icons/d2.svg rename to licensing/dice_roll/icons/d2.svg diff --git a/dice_roll/icons/d20.svg b/licensing/dice_roll/icons/d20.svg similarity index 100% rename from dice_roll/icons/d20.svg rename to licensing/dice_roll/icons/d20.svg diff --git a/dice_roll/icons/d4.svg b/licensing/dice_roll/icons/d4.svg similarity index 100% rename from dice_roll/icons/d4.svg rename to licensing/dice_roll/icons/d4.svg diff --git a/dice_roll/icons/d6.svg b/licensing/dice_roll/icons/d6.svg similarity index 100% rename from dice_roll/icons/d6.svg rename to licensing/dice_roll/icons/d6.svg diff --git a/dice_roll/icons/d8.svg b/licensing/dice_roll/icons/d8.svg similarity index 100% rename from dice_roll/icons/d8.svg rename to licensing/dice_roll/icons/d8.svg diff --git a/dice_roll/icons/dice.svg b/licensing/dice_roll/icons/dice.svg similarity index 100% rename from dice_roll/icons/dice.svg rename to licensing/dice_roll/icons/dice.svg diff --git a/docker/__init__.py b/licensing/docker/__init__.py similarity index 100% rename from docker/__init__.py rename to licensing/docker/__init__.py diff --git a/docker/running.png b/licensing/docker/running.png similarity index 100% rename from docker/running.png rename to licensing/docker/running.png diff --git a/docker/stopped.png b/licensing/docker/stopped.png similarity index 100% rename from docker/stopped.png rename to licensing/docker/stopped.png diff --git a/duckduckgo/__init__.py b/licensing/duckduckgo/__init__.py similarity index 100% rename from duckduckgo/__init__.py rename to licensing/duckduckgo/__init__.py diff --git a/duckduckgo/duckduckgo.svg b/licensing/duckduckgo/duckduckgo.svg similarity index 100% rename from duckduckgo/duckduckgo.svg rename to licensing/duckduckgo/duckduckgo.svg diff --git a/emoji/__init__.py b/licensing/emoji/__init__.py similarity index 100% rename from emoji/__init__.py rename to licensing/emoji/__init__.py diff --git a/goldendict/__init__.py b/licensing/goldendict/__init__.py similarity index 100% rename from goldendict/__init__.py rename to licensing/goldendict/__init__.py diff --git a/jetbrains_projects/LICENSE b/licensing/jetbrains_projects/LICENSE similarity index 100% rename from jetbrains_projects/LICENSE rename to licensing/jetbrains_projects/LICENSE diff --git a/jetbrains_projects/README.md b/licensing/jetbrains_projects/README.md similarity index 100% rename from jetbrains_projects/README.md rename to licensing/jetbrains_projects/README.md diff --git a/jetbrains_projects/__init__.py b/licensing/jetbrains_projects/__init__.py similarity index 99% rename from jetbrains_projects/__init__.py rename to licensing/jetbrains_projects/__init__.py index e461c7e6..8c66640e 100644 --- a/jetbrains_projects/__init__.py +++ b/licensing/jetbrains_projects/__init__.py @@ -22,6 +22,7 @@ md_license = "GPL-3" md_url = "https://github.com/albertlauncher/python/" md_maintainers = ["@mqus", "@tomsquest"] +md_authors = ["@mqus", "@tomsquest"] @dataclass diff --git a/jetbrains_projects/androidstudio.svg b/licensing/jetbrains_projects/androidstudio.svg similarity index 100% rename from jetbrains_projects/androidstudio.svg rename to licensing/jetbrains_projects/androidstudio.svg diff --git a/jetbrains_projects/clion.svg b/licensing/jetbrains_projects/clion.svg similarity index 100% rename from jetbrains_projects/clion.svg rename to licensing/jetbrains_projects/clion.svg diff --git a/jetbrains_projects/datagrip.svg b/licensing/jetbrains_projects/datagrip.svg similarity index 100% rename from jetbrains_projects/datagrip.svg rename to licensing/jetbrains_projects/datagrip.svg diff --git a/jetbrains_projects/dataspell.svg b/licensing/jetbrains_projects/dataspell.svg similarity index 100% rename from jetbrains_projects/dataspell.svg rename to licensing/jetbrains_projects/dataspell.svg diff --git a/jetbrains_projects/goland.svg b/licensing/jetbrains_projects/goland.svg similarity index 100% rename from jetbrains_projects/goland.svg rename to licensing/jetbrains_projects/goland.svg diff --git a/jetbrains_projects/idea.svg b/licensing/jetbrains_projects/idea.svg similarity index 100% rename from jetbrains_projects/idea.svg rename to licensing/jetbrains_projects/idea.svg diff --git a/jetbrains_projects/phpstorm.svg b/licensing/jetbrains_projects/phpstorm.svg similarity index 100% rename from jetbrains_projects/phpstorm.svg rename to licensing/jetbrains_projects/phpstorm.svg diff --git a/jetbrains_projects/pycharm.svg b/licensing/jetbrains_projects/pycharm.svg similarity index 100% rename from jetbrains_projects/pycharm.svg rename to licensing/jetbrains_projects/pycharm.svg diff --git a/jetbrains_projects/rider.svg b/licensing/jetbrains_projects/rider.svg similarity index 100% rename from jetbrains_projects/rider.svg rename to licensing/jetbrains_projects/rider.svg diff --git a/jetbrains_projects/rubymine.svg b/licensing/jetbrains_projects/rubymine.svg similarity index 100% rename from jetbrains_projects/rubymine.svg rename to licensing/jetbrains_projects/rubymine.svg diff --git a/jetbrains_projects/rustrover.svg b/licensing/jetbrains_projects/rustrover.svg similarity index 100% rename from jetbrains_projects/rustrover.svg rename to licensing/jetbrains_projects/rustrover.svg diff --git a/jetbrains_projects/webstorm.svg b/licensing/jetbrains_projects/webstorm.svg similarity index 100% rename from jetbrains_projects/webstorm.svg rename to licensing/jetbrains_projects/webstorm.svg diff --git a/kill/__init__.py b/licensing/kill/__init__.py similarity index 100% rename from kill/__init__.py rename to licensing/kill/__init__.py diff --git a/locate/__init__.py b/licensing/locate/__init__.py similarity index 100% rename from locate/__init__.py rename to licensing/locate/__init__.py diff --git a/locate/locate.svg b/licensing/locate/locate.svg similarity index 100% rename from locate/locate.svg rename to licensing/locate/locate.svg diff --git a/mathematica_eval/__init__.py b/licensing/mathematica_eval/__init__.py similarity index 100% rename from mathematica_eval/__init__.py rename to licensing/mathematica_eval/__init__.py diff --git a/pacman/__init__.py b/licensing/pacman/__init__.py similarity index 100% rename from pacman/__init__.py rename to licensing/pacman/__init__.py diff --git a/pacman/arch.svg b/licensing/pacman/arch.svg similarity index 97% rename from pacman/arch.svg rename to licensing/pacman/arch.svg index 61f55ed9..b95bef86 100644 --- a/pacman/arch.svg +++ b/licensing/pacman/arch.svg @@ -1,5 +1 @@ - - \ No newline at end of file diff --git a/pass/__init__.py b/licensing/pass/__init__.py similarity index 100% rename from pass/__init__.py rename to licensing/pass/__init__.py diff --git a/pomodoro/__init__.py b/licensing/pomodoro/__init__.py similarity index 100% rename from pomodoro/__init__.py rename to licensing/pomodoro/__init__.py diff --git a/pomodoro/pomodoro.svg b/licensing/pomodoro/pomodoro.svg similarity index 97% rename from pomodoro/pomodoro.svg rename to licensing/pomodoro/pomodoro.svg index 29cc53cf..fea164ad 100644 --- a/pomodoro/pomodoro.svg +++ b/licensing/pomodoro/pomodoro.svg @@ -1,5 +1 @@ - - \ No newline at end of file diff --git a/python_eval/__init__.py b/licensing/python_eval/__init__.py similarity index 100% rename from python_eval/__init__.py rename to licensing/python_eval/__init__.py diff --git a/python_eval/python.svg b/licensing/python_eval/python.svg similarity index 100% rename from python_eval/python.svg rename to licensing/python_eval/python.svg diff --git a/tex_to_unicode/__init__.py b/licensing/tex_to_unicode/__init__.py similarity index 100% rename from tex_to_unicode/__init__.py rename to licensing/tex_to_unicode/__init__.py diff --git a/tex_to_unicode/tex.png b/licensing/tex_to_unicode/tex.png similarity index 100% rename from tex_to_unicode/tex.png rename to licensing/tex_to_unicode/tex.png diff --git a/timer/__init__.py b/licensing/timer/__init__.py similarity index 100% rename from timer/__init__.py rename to licensing/timer/__init__.py diff --git a/timer/time.svg b/licensing/timer/time.svg similarity index 99% rename from timer/time.svg rename to licensing/timer/time.svg index 0f675ad2..f6cc4d0b 100644 --- a/timer/time.svg +++ b/licensing/timer/time.svg @@ -1,5 +1 @@ - - \ No newline at end of file diff --git a/translators/__init__.py b/licensing/translators/__init__.py similarity index 100% rename from translators/__init__.py rename to licensing/translators/__init__.py diff --git a/translators/google_translate.png b/licensing/translators/google_translate.png similarity index 100% rename from translators/google_translate.png rename to licensing/translators/google_translate.png diff --git a/virtualbox/__init__.py b/licensing/virtualbox/__init__.py similarity index 100% rename from virtualbox/__init__.py rename to licensing/virtualbox/__init__.py diff --git a/vpn/__init__.py b/licensing/vpn/__init__.py similarity index 100% rename from vpn/__init__.py rename to licensing/vpn/__init__.py diff --git a/wikipedia/__init__.py b/licensing/wikipedia/__init__.py similarity index 100% rename from wikipedia/__init__.py rename to licensing/wikipedia/__init__.py diff --git a/wikipedia/wikipedia.png b/licensing/wikipedia/wikipedia.png similarity index 100% rename from wikipedia/wikipedia.png rename to licensing/wikipedia/wikipedia.png diff --git a/zeal/__init__.py b/licensing/zeal/__init__.py similarity index 100% rename from zeal/__init__.py rename to licensing/zeal/__init__.py From d31cdd3edf2c92d45a91308debbdacab48d84733 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:20:47 +0100 Subject: [PATCH 102/243] [archwiki] Reinstate proper licensing --- {licensing/arch_wiki => arch_wiki}/__init__.py | 10 ++++++---- {licensing/arch_wiki => arch_wiki}/arch.svg | 0 2 files changed, 6 insertions(+), 4 deletions(-) rename {licensing/arch_wiki => arch_wiki}/__init__.py (94%) rename {licensing/arch_wiki => arch_wiki}/arch.svg (100%) diff --git a/licensing/arch_wiki/__init__.py b/arch_wiki/__init__.py similarity index 94% rename from licensing/arch_wiki/__init__.py rename to arch_wiki/__init__.py index a1b243be..2dbf9456 100644 --- a/licensing/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider import json from pathlib import Path @@ -9,10 +10,11 @@ md_iid = '2.0' md_version = "1.5" -md_name = "ArchLinux Wiki" -md_description = "Search ArchLinux Wiki articles" -md_license = "BSD-3" -md_url = "https://github.com/albertlauncher/python/tree/master/awiki" +md_name = "Arch Linux Wiki" +md_description = "Search Arch Linux Wiki articles" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/master/arch_wiki" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): diff --git a/licensing/arch_wiki/arch.svg b/arch_wiki/arch.svg similarity index 100% rename from licensing/arch_wiki/arch.svg rename to arch_wiki/arch.svg From 0ecdab89e4b6eb43b12e45d4217a2ecdd50469f2 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:21:34 +0100 Subject: [PATCH 103/243] [coingecko] Reinstate proper licensing --- {licensing/coingecko => coingecko}/__init__.py | 6 +++--- {licensing/coingecko => coingecko}/coingecko.png | Bin 2 files changed, 3 insertions(+), 3 deletions(-) rename {licensing/coingecko => coingecko}/__init__.py (98%) rename {licensing/coingecko => coingecko}/coingecko.png (100%) diff --git a/licensing/coingecko/__init__.py b/coingecko/__init__.py similarity index 98% rename from licensing/coingecko/__init__.py rename to coingecko/__init__.py index 602d0684..bf05ae9f 100644 --- a/licensing/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - -"""Show and access crypto currencies on CoinGecko.com.""" +# Copyright (c) 2024 Manuel Schneider from albert import * from time import time @@ -13,8 +12,9 @@ md_version = "1.1" md_name = "CoinGecko" md_description = "Access CoinGecko" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/coingecko" +md_authors = "@manuelschneid3r" class CoinFetcherThread(Thread): diff --git a/licensing/coingecko/coingecko.png b/coingecko/coingecko.png similarity index 100% rename from licensing/coingecko/coingecko.png rename to coingecko/coingecko.png From fb26263dbf70ba5d5391c1f3de21eac8d69d43ff Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:22:17 +0100 Subject: [PATCH 104/243] [color] Reinstate proper licensing --- {licensing/color => color}/__init__.py | 2 ++ 1 file changed, 2 insertions(+) rename {licensing/color => color}/__init__.py (96%) diff --git a/licensing/color/__init__.py b/color/__init__.py similarity index 96% rename from licensing/color/__init__.py rename to color/__init__.py index 10bfa2f9..6287ac3a 100644 --- a/licensing/color/__init__.py +++ b/color/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider """ Displays a color parsed from name, which may be in one of these formats: @@ -27,6 +28,7 @@ md_description = 'Display color for color codes' md_license = 'MIT' md_url = 'https://github.com/albertlauncher/python/color' +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, GlobalQueryHandler): From a47648a1508b9ac158110462e814940fc5029c0a Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:26:06 +0100 Subject: [PATCH 105/243] [color:1.1] Adopt iid:3.0 --- color/__init__.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/color/__init__.py b/color/__init__.py index 6287ac3a..6a840120 100644 --- a/color/__init__.py +++ b/color/__init__.py @@ -2,20 +2,16 @@ # Copyright (c) 2024 Manuel Schneider """ -Displays a color parsed from name, which may be in one of these formats: +Displays a color parsed from a code, which may be in one of these formats: * #RGB (each of R, G, and B is a single hex digit) * #RRGGBB * #AARRGGBB * #RRRGGGBBB * #RRRRGGGGBBBB -* A name from the list of colors defined in the list of SVG color keyword -names provided by the World Wide Web Consortium; for example, "steelblue" -or "gainsboro". -Note: This extension started as a prototype to test the internal color pixmap -generator. However it may serve as a starting point for people having a real -need for color workflows. PR's welcome. +Note: This extension started as a prototype to test the internal color pixmap generator. However it may serve as a \ +starting point for people having a real need for color workflows. PR's welcome. """ from albert import * @@ -23,7 +19,7 @@ from string import hexdigits md_iid = '2.0' -md_version = '1.0' +md_version = '1.1' md_name = 'Color' md_description = 'Display color for color codes' md_license = 'MIT' @@ -63,3 +59,11 @@ def handleGlobalQuery(self, query): ) return rank_items + + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip() + } + ] \ No newline at end of file From d1f25fa0cdfe084fc09203766813b3201c9886a9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:28:51 +0100 Subject: [PATCH 106/243] [duckduckgo] Reinstate proper licensing --- {licensing/duckduckgo => duckduckgo}/__init__.py | 7 ++++++- {licensing/duckduckgo => duckduckgo}/duckduckgo.svg | 0 2 files changed, 6 insertions(+), 1 deletion(-) rename {licensing/duckduckgo => duckduckgo}/__init__.py (91%) rename {licensing/duckduckgo => duckduckgo}/duckduckgo.svg (100%) diff --git a/licensing/duckduckgo/__init__.py b/duckduckgo/__init__.py similarity index 91% rename from licensing/duckduckgo/__init__.py rename to duckduckgo/__init__.py index 2f9174e9..f8865961 100644 --- a/licensing/duckduckgo/__init__.py +++ b/duckduckgo/__init__.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider + """ Inline DuckDuckGo web search using the 'duckduckgo-search' library. """ @@ -12,8 +15,10 @@ md_version = '1.0' md_name = 'DuckDuckGo' md_description = 'Inline DuckDuckGo web search' +md_license = "MIT" md_url = 'https://github.com/albertlauncher/python/duckduckgo' md_lib_dependencies = "duckduckgo-search" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): @@ -35,7 +40,7 @@ def handleTriggerQuery(self, query): if stripped: # dont flood - for number in range(25): + for _ in range(25): sleep(0.01) if not query.isValid: return diff --git a/licensing/duckduckgo/duckduckgo.svg b/duckduckgo/duckduckgo.svg similarity index 100% rename from licensing/duckduckgo/duckduckgo.svg rename to duckduckgo/duckduckgo.svg From 22db77c51107950927d4713d70aca97f9fb99e6f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:29:43 +0100 Subject: [PATCH 107/243] [inhibit_sleep] Reinstate proper licensing --- inhibit_sleep/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inhibit_sleep/__init__.py b/inhibit_sleep/__init__.py index c5f56427..b27c65df 100644 --- a/inhibit_sleep/__init__.py +++ b/inhibit_sleep/__init__.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider + """ Provides an item 'Inhibit sleep' which can be used to temporarily disable system suspension. @@ -12,7 +15,9 @@ md_version = '1.0' md_name = 'Inhibit sleep' md_description = 'Inhibit system sleep mode.' +md_license = "MIT" md_url = 'https://github.com/albertlauncher/python/inhibit_sleep' +md_authors = "@manuelschneid3r" md_bin_dependencies = ['systemd-inhibit', "sleep"] From 74ab65c0737e91ca0eed5fc2c7e1365cb25efc83 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:45:37 +0100 Subject: [PATCH 108/243] [killd] Reinstate proper licensing --- {licensing/kill => kill}/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) rename {licensing/kill => kill}/__init__.py (92%) diff --git a/licensing/kill/__init__.py b/kill/__init__.py similarity index 92% rename from licensing/kill/__init__.py rename to kill/__init__.py index 0b259a23..73e3ca41 100644 --- a/licensing/kill/__init__.py +++ b/kill/__init__.py @@ -1,4 +1,8 @@ -"""Unix 'kill' wrapper extension.""" +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Manuel Schneider +# Copyright (c) 2022 Benedict Dudel +# Copyright (c) 2022 Pete Hamlin + import os from signal import SIGKILL, SIGTERM @@ -9,10 +13,9 @@ md_version = "1.3" md_name = "Kill Process" md_description = "Kill processes" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/kill" -md_maintainers = "@Pete-Hamlin" -md_credits = "Original idea by Benedict Dudel & Manuel Schneider" +md_authors = ["@Pete-Hamlin", "@BenedictDwudel", "@ManuelSchneid3r"] class Plugin(PluginInstance, TriggerQueryHandler): From e28c3afd320d5431512c615859cbf90dfa92dbcf Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:51:22 +0100 Subject: [PATCH 109/243] [pacman] Reinstate proper licensing --- {licensing/pacman => pacman}/__init__.py | 11 ++++------- {licensing/pacman => pacman}/arch.svg | 0 2 files changed, 4 insertions(+), 7 deletions(-) rename {licensing/pacman => pacman}/__init__.py (96%) rename {licensing/pacman => pacman}/arch.svg (100%) diff --git a/licensing/pacman/__init__.py b/pacman/__init__.py similarity index 96% rename from licensing/pacman/__init__.py rename to pacman/__init__.py index 856b7c77..a47f39fd 100644 --- a/licensing/pacman/__init__.py +++ b/pacman/__init__.py @@ -1,9 +1,5 @@ # -*- coding: utf-8 -*- - -""" -This plugin is a `pacman` (Arch Linux Package Manager) wrapper. You can update, search, install and remove \ -packages. -""" +# Copyright (c) 2024 Manuel Schneider import subprocess from time import sleep @@ -15,8 +11,9 @@ md_version = "1.9" md_name = "PacMan" md_description = "Search, install and remove packages" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/pacman" +md_authors = "@ManuelSchneid3r" md_bin_dependencies = ["pacman", "expac"] @@ -58,7 +55,7 @@ def handleTriggerQuery(self, query): return # avoid rate limiting - for number in range(50): + for _ in range(50): sleep(0.01) if not query.isValid: return diff --git a/licensing/pacman/arch.svg b/pacman/arch.svg similarity index 100% rename from licensing/pacman/arch.svg rename to pacman/arch.svg From 0f5a14116ae46624ba2878e692f6f3e9e0154a80 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:53:24 +0100 Subject: [PATCH 110/243] [pomodoro] Reinstate proper licensing --- {licensing/pomodoro => pomodoro}/__init__.py | 6 +++--- {licensing/pomodoro => pomodoro}/pomodoro.svg | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename {licensing/pomodoro => pomodoro}/__init__.py (97%) rename {licensing/pomodoro => pomodoro}/pomodoro.svg (100%) diff --git a/licensing/pomodoro/__init__.py b/pomodoro/__init__.py similarity index 97% rename from licensing/pomodoro/__init__.py rename to pomodoro/__init__.py index a18b4455..55ac7180 100644 --- a/licensing/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2022 Manuel Schneider +# Copyright (c) 2024 Manuel Schneider """ https://en.wikipedia.org/wiki/Pomodoro_Technique @@ -16,9 +16,9 @@ md_version = "1.3" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/pomodoro" -md_maintainers = "@manuelschneid3r" +md_author = "@manuelschneid3r" class PomodoroTimer: diff --git a/licensing/pomodoro/pomodoro.svg b/pomodoro/pomodoro.svg similarity index 100% rename from licensing/pomodoro/pomodoro.svg rename to pomodoro/pomodoro.svg From 9f384037ce67074750932f6491ae5a1f2c63f593 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:54:51 +0100 Subject: [PATCH 111/243] [translators] Reinstate proper licensing --- {licensing/translators => translators}/__init__.py | 4 +++- .../google_translate.png | Bin 2 files changed, 3 insertions(+), 1 deletion(-) rename {licensing/translators => translators}/__init__.py (98%) rename {licensing/translators => translators}/google_translate.png (100%) diff --git a/licensing/translators/__init__.py b/translators/__init__.py similarity index 98% rename from licensing/translators/__init__.py rename to translators/__init__.py index dbcac7b5..7ed4fc59 100644 --- a/licensing/translators/__init__.py +++ b/translators/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider """ Translates text using the python package translators. See https://pypi.org/project/translators/ @@ -15,8 +16,9 @@ md_version = "1.4" md_name = "Translator" md_description = "Translate sentences using 'translators' package" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/translators" +md_authors = "@manuelschneid3r" md_lib_dependencies = "translators" diff --git a/licensing/translators/google_translate.png b/translators/google_translate.png similarity index 100% rename from licensing/translators/google_translate.png rename to translators/google_translate.png From 5e43239b7e3b824ba9539acc3548e7e83d4fd279 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:55:09 +0100 Subject: [PATCH 112/243] [virtualbox] Reinstate proper licensing --- {licensing/virtualbox => virtualbox}/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename {licensing/virtualbox => virtualbox}/__init__.py (97%) diff --git a/licensing/virtualbox/__init__.py b/virtualbox/__init__.py similarity index 97% rename from licensing/virtualbox/__init__.py rename to virtualbox/__init__.py index 94b21015..4b628a27 100644 --- a/licensing/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider import virtualbox from virtualbox.library import LockType, MachineState @@ -9,9 +10,9 @@ md_version = "1.5" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/virtualbox" -md_maintainers = "@manuelschneid3r" +md_authors = "@manuelschneid3r" md_lib_dependencies = ['virtualbox'] From 62c27ea56e3223d95df9342ced8782c463a942c9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:56:34 +0100 Subject: [PATCH 113/243] [wikipedia] Reinstate proper licensing --- {licensing/wikipedia => wikipedia}/__init__.py | 5 +++-- {licensing/wikipedia => wikipedia}/wikipedia.png | Bin 2 files changed, 3 insertions(+), 2 deletions(-) rename {licensing/wikipedia => wikipedia}/__init__.py (98%) rename {licensing/wikipedia => wikipedia}/wikipedia.png (100%) diff --git a/licensing/wikipedia/__init__.py b/wikipedia/__init__.py similarity index 98% rename from licensing/wikipedia/__init__.py rename to wikipedia/__init__.py index 24809292..433f85c2 100644 --- a/licensing/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider -# Copyright (c) 2022-2023 Manuel Schneider from albert import * from locale import getdefaultlocale @@ -14,8 +14,9 @@ md_version = "1.10" md_name = "Wikipedia" md_description = "Search Wikipedia articles" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/wikipedia" +md_authors = "@manuelschneid3r" class WikiFallbackHandler(FallbackHandler): diff --git a/licensing/wikipedia/wikipedia.png b/wikipedia/wikipedia.png similarity index 100% rename from licensing/wikipedia/wikipedia.png rename to wikipedia/wikipedia.png From 3955f7aafb981632ba6c552343379e12e0afd70f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 00:56:55 +0100 Subject: [PATCH 114/243] [zeal] Reinstate proper licensing --- {licensing/zeal => zeal}/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) rename {licensing/zeal => zeal}/__init__.py (90%) diff --git a/licensing/zeal/__init__.py b/zeal/__init__.py similarity index 90% rename from licensing/zeal/__init__.py rename to zeal/__init__.py index 843eee45..7ebae3c2 100644 --- a/licensing/zeal/__init__.py +++ b/zeal/__init__.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider + """Search in Zeal offline docs.""" from albert import * @@ -6,7 +9,9 @@ md_version = '1.2' md_name = 'Zeal' md_description = 'Search in Zeal docs' +md_license = "MIT" md_url = 'https://github.com/albertlauncher/python/zeal' +md_authors = "@manuelschneid3r" md_bin_dependencies = ['zeal'] From b29b96d7cf24bb896154990661ab389df1e18678 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:09:39 +0100 Subject: [PATCH 115/243] [aur] Reinstate proper licensing There were two minor additions not worth a copyright - 43f3b93 Gaganpreet [aur:1.6] Fix install action - fce8b8b Manuel Parati [aur] added paru AUR helper (#128) --- {licensing/aur => aur}/__init__.py | 5 +++-- {licensing/aur => aur}/arch.svg | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename {licensing/aur => aur}/__init__.py (98%) rename {licensing/aur => aur}/arch.svg (100%) diff --git a/licensing/aur/__init__.py b/aur/__init__.py similarity index 98% rename from licensing/aur/__init__.py rename to aur/__init__.py index a2bcb1e2..3989eb95 100644 --- a/licensing/aur/__init__.py +++ b/aur/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2022-2023 Manuel Schneider +# Copyright (c) 2024 Manuel Schneider """ Search for packages and open their URLs. This extension is also intended to be used to \ @@ -19,9 +19,10 @@ md_version = "1.8" md_name = "AUR" md_description = "Query and install AUR packages" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/aur" # md_platforms = ["Linux"] +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): diff --git a/licensing/aur/arch.svg b/aur/arch.svg similarity index 100% rename from licensing/aur/arch.svg rename to aur/arch.svg From f28d799e44fff247739485a05c4d31d2e488e833 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:12:34 +0100 Subject: [PATCH 116/243] Add contribution guide --- CONTRIBUTING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4742ff5a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +## How to contribute to this repository + +### Do you have an issue? + +* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/albertlauncher/albert/issues). +* Create a new issue using the templates provided. +* Ping the authors of the related plugin. + +### Do you want to contribute code? + +* Add a copyright notice, otherwise the code is in public domain. +* You agree to publish your contribution under the MIT license. +* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. +* Changes that do not add anything substantial to the stability, functionality, or testability will generally not be accepted. + +Thanks! :heart: From aad5a83789df99cfff7cecbbc4ca9c708b808c38 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:14:19 +0100 Subject: [PATCH 117/243] [pyi] v2.2 --- albert.pyi | 103 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/albert.pyi b/albert.pyi index 787ec318..43eebdf8 100644 --- a/albert.pyi +++ b/albert.pyi @@ -1,6 +1,5 @@ """ - -# Albert Python interface v2.1 +# Albert Python interface v2.2 The Python interface is a subset of the internal C++ interface exposed to Python with some minor adjustments. A Python @@ -21,10 +20,9 @@ md_description: str | A brief, imperative description. (Like "Launch apps" or "O md_id | Identifier overwrite. [a-zA-Z0-9_]. Note: This variable is attached at runtime | if it is unset and defaults to the module name. -__doc__ | The docstring of the module is used as long description/readme of the extension. -md_license: str | Short form e.g. BSD-2-Clause or GPL-3.0 +md_license: str | Short form e.g. MIT or BSD-2 md_url: str | Browsable source, issues etc -md_maintainers: [str|List(str)] | Active maintainer(s). Preferrably using mentionable Github usernames. +md_authors: [str|List(str)] | The authors. Preferably using mentionable Github usernames. md_bin_dependencies: [str|List(str)] | Required executable(s). Have to match the name of the executable in $PATH. md_lib_dependencies: [str|List(str)] | Required Python package(s). Have to match the PyPI package name. md_credits: [str|List(str)] | Third party credit(s) and license notes @@ -37,8 +35,20 @@ PluginInstance. Implement extensions by subclassing _one_ extension class (Trigg built-in `albert` module and pass the list of your extensions to the PluginInstance init function. Due to the differences in type systems multiple inheritance of extensions is not supported. (Python does not support virtual inheritance, which is used in the C++ space to inherit from 'Extension'). -""" +Changes in 2.1 + + - Add PluginInstance.readConfig + - Add PluginInstance.writeConfig + - Add PluginInstance.configWidget + +Changes in 2.2: + + - PluginInstance.configWidget supports 'label' + - __doc__ is not used anymore, since 0.23 drops long_description metadata + - md_maintainers not used anymore + - md_authors new optional field +""" from abc import abstractmethod, ABC from enum import Enum @@ -105,26 +115,67 @@ class PluginInstance(ABC): def configWidget(self) -> List[dict]: """ - Descriptive config widget factory. - - Define a static config widget using a list of dicts, each defining a row in the resulting form layout. - Supported keys are: - - - 'property' The name of the property that will be set upon editing the forms. - - 'label' The text displayed in front of the the editor widget. - - 'type' The type of editor widget used. See the supported types below. - - 'items' The list of strings used for 'type': 'combobox'. - - 'widget_properties' Dict setting the widget properties of the editor widget. - See the links along the editor types below (but also the base classes) to find available properties. - Note that due to the restricted type conversion only properties of type str|int|float|bool are settable. - - The supported editor widget types are: - - * 'checkbox' for boolean properties (See https://doc.qt.io/qt-6/qcheckbox.html) - * 'spinbox' for integer properties. (See https://doc.qt.io/qt-6/qspinbox.html) - * 'doublespinbox' for float properties. (See https://doc.qt.io/qt-6/qdoublespinbox.html) - * 'lineedit' if you want the user to input any string. (See https://doc.qt.io/qt-6/qlineedit.html) - * 'combobox' if you want the user to choose a string. (See https://doc.qt.io/qt-6/qcombobox.html) + **Descriptive config widget factory.** + + Define a static config widget using a list of dicts, each defining a row in the resulting form layout. Each dict + must contain key 'type' having one of the supported types specified below. Each type may define further + keys. + + **A note on 'widget_properties'** + + This is a dict setting the widget properties of a QWidget or one of its derived classes. See Qt documentation + for a particular class. Note that due to the restricted type conversion only properties of type + str|int|float|bool are settable. + + **Supported row 'type's** + + * 'label' (since 2.2) + + Display text spanning both columns. Additional keys: + + - 'text': The text to display + - 'widget_properties': https://doc.qt.io/qt-6/qlabel.html. + + * 'checkbox' + + A form layout item to edit boolean properties. Additional keys: + + - 'label': The text displayed in front of the the editor widget. + - 'property': The name of the property that will be set on changes. + - 'widget_properties': https://doc.qt.io/qt-6/qcheckbox.html + + * 'lineedit' + + A form layout item to edit string properties. Additional keys: + + - 'label': The text displayed in front of the the editor widget. + - 'property': The name of the property that will be set on changes. + - 'widget_properties': https://doc.qt.io/qt-6/qlineedit.html + + * 'combobox' + + A form layout item to set string properties using a list of options. Additional keys: + + - 'label': The text displayed in front of the the editor widget. + - 'property': The name of the property that will be set on changes. + - 'items': The list of strings used to populate the combobox. + - 'widget_properties': https://doc.qt.io/qt-6/qcombobox.html + + * 'spinbox' + + A form layout item to edit integer properties. Additional keys: + + - 'label': The text displayed in front of the the editor widget. + - 'property': The name of the property that will be set on changes. + - 'widget_properties': https://doc.qt.io/qt-6/qspinbox.html + + * 'doublespinbox' + + A form layout item to edit float properties. Additional keys: + + - 'label': The text displayed in front of the the editor widget. + - 'property': The name of the property that will be set on changes. + - 'widget_properties': https://doc.qt.io/qt-6/qdoublespinbox.html Returns: A list of dicts, describing a form layout as defined above. From e73f30e5c57e5e062785723967f79c475a9bdc3b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:21:44 +0100 Subject: [PATCH 118/243] [aur] Reinstate proper licensing There was one minor additions not worth a copyright - 45b6de3 Lorenz Henk [docker] Fix typo (#121) --- {licensing/docker => docker}/__init__.py | 10 ++++------ {licensing/docker => docker}/running.png | Bin {licensing/docker => docker}/stopped.png | Bin 3 files changed, 4 insertions(+), 6 deletions(-) rename {licensing/docker => docker}/__init__.py (97%) rename {licensing/docker => docker}/running.png (100%) rename {licensing/docker => docker}/stopped.png (100%) diff --git a/licensing/docker/__init__.py b/docker/__init__.py similarity index 97% rename from licensing/docker/__init__.py rename to docker/__init__.py index f925e913..f47905f0 100644 --- a/licensing/docker/__init__.py +++ b/docker/__init__.py @@ -1,8 +1,5 @@ -""" -Docker wrapper (prototype) -""" - -from pathlib import Path +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider import docker from albert import * @@ -11,8 +8,9 @@ md_version = "1.6" md_name = "Docker" md_description = "Manage docker images and containers" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/docker" +md_authors = "@manuelschneid3r" md_bin_dependencies = "docker" md_lib_dependencies = "docker" diff --git a/licensing/docker/running.png b/docker/running.png similarity index 100% rename from licensing/docker/running.png rename to docker/running.png diff --git a/licensing/docker/stopped.png b/docker/stopped.png similarity index 100% rename from licensing/docker/stopped.png rename to docker/stopped.png From 7c12ad41c9a7b714b9bb14cd398e20cee484b367 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:27:58 +0100 Subject: [PATCH 119/243] [dice_roll] Reinstate proper licensing --- {licensing/dice_roll => dice_roll}/__init__.py | 3 ++- {licensing/dice_roll => dice_roll}/icons/d10.svg | 0 {licensing/dice_roll => dice_roll}/icons/d100.svg | 0 {licensing/dice_roll => dice_roll}/icons/d12.svg | 0 {licensing/dice_roll => dice_roll}/icons/d2.svg | 0 {licensing/dice_roll => dice_roll}/icons/d20.svg | 0 {licensing/dice_roll => dice_roll}/icons/d4.svg | 0 {licensing/dice_roll => dice_roll}/icons/d6.svg | 0 {licensing/dice_roll => dice_roll}/icons/d8.svg | 0 {licensing/dice_roll => dice_roll}/icons/dice.svg | 0 licensing/dice_roll/README.md | 13 ------------- 11 files changed, 2 insertions(+), 14 deletions(-) rename {licensing/dice_roll => dice_roll}/__init__.py (98%) rename {licensing/dice_roll => dice_roll}/icons/d10.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d100.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d12.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d2.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d20.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d4.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d6.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/d8.svg (100%) rename {licensing/dice_roll => dice_roll}/icons/dice.svg (100%) delete mode 100644 licensing/dice_roll/README.md diff --git a/licensing/dice_roll/__init__.py b/dice_roll/__init__.py similarity index 98% rename from licensing/dice_roll/__init__.py rename to dice_roll/__init__.py index 95f95031..dc2e3c88 100644 --- a/licensing/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Jonah Lawrence from __future__ import annotations @@ -20,7 +21,7 @@ md_description = "Roll any number of dice" md_license = "MIT" md_url = "https://github.com/albertlauncher/python" -md_maintainers = "@DenverCoder1" +md_authors = "@DenverCoder1" def get_icon_path(num_sides: int | None) -> str: diff --git a/licensing/dice_roll/icons/d10.svg b/dice_roll/icons/d10.svg similarity index 100% rename from licensing/dice_roll/icons/d10.svg rename to dice_roll/icons/d10.svg diff --git a/licensing/dice_roll/icons/d100.svg b/dice_roll/icons/d100.svg similarity index 100% rename from licensing/dice_roll/icons/d100.svg rename to dice_roll/icons/d100.svg diff --git a/licensing/dice_roll/icons/d12.svg b/dice_roll/icons/d12.svg similarity index 100% rename from licensing/dice_roll/icons/d12.svg rename to dice_roll/icons/d12.svg diff --git a/licensing/dice_roll/icons/d2.svg b/dice_roll/icons/d2.svg similarity index 100% rename from licensing/dice_roll/icons/d2.svg rename to dice_roll/icons/d2.svg diff --git a/licensing/dice_roll/icons/d20.svg b/dice_roll/icons/d20.svg similarity index 100% rename from licensing/dice_roll/icons/d20.svg rename to dice_roll/icons/d20.svg diff --git a/licensing/dice_roll/icons/d4.svg b/dice_roll/icons/d4.svg similarity index 100% rename from licensing/dice_roll/icons/d4.svg rename to dice_roll/icons/d4.svg diff --git a/licensing/dice_roll/icons/d6.svg b/dice_roll/icons/d6.svg similarity index 100% rename from licensing/dice_roll/icons/d6.svg rename to dice_roll/icons/d6.svg diff --git a/licensing/dice_roll/icons/d8.svg b/dice_roll/icons/d8.svg similarity index 100% rename from licensing/dice_roll/icons/d8.svg rename to dice_roll/icons/d8.svg diff --git a/licensing/dice_roll/icons/dice.svg b/dice_roll/icons/dice.svg similarity index 100% rename from licensing/dice_roll/icons/dice.svg rename to dice_roll/icons/dice.svg diff --git a/licensing/dice_roll/README.md b/licensing/dice_roll/README.md deleted file mode 100644 index 955d6c3c..00000000 --- a/licensing/dice_roll/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Dice Roll - -Extension for rolling dice - -![image](https://user-images.githubusercontent.com/20955511/211666706-eae9c9f2-849f-4b9c-9a59-39d71940b83d.png) - -## Usage - -Roll any number of dice using the format `_d_`. - -Synopsis: ` d [d ...]` - -Example: `"roll 2d6 3d8 1d20"` From ea91d50c11b1a0933496f3e0180183a7dba57913 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:40:15 +0100 Subject: [PATCH 120/243] [copyq] Reinstate proper licensing --- {licensing/copyq => copyq}/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename {licensing/copyq => copyq}/__init__.py (94%) diff --git a/licensing/copyq/__init__.py b/copyq/__init__.py similarity index 94% rename from licensing/copyq/__init__.py rename to copyq/__init__.py index ae418eb5..18a77185 100644 --- a/licensing/copyq/__init__.py +++ b/copyq/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2017-2024 Manuel Schneider +# Copyright (c) 2023 Oskar Haarklou Veileborg (@BarrensZeppelin) import json import subprocess @@ -11,8 +13,8 @@ md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" md_url = "https://github.com/albertlauncher/python" +md_authors = ["@ManuelSchneid3r", "@BarrensZeppelin"] md_bin_dependencies = ["copyq"] -md_maintainers = "@BarrensZeppelin" copyq_script_getAll = r""" From 286114c24561660cdccaa24ad24968bd33d2b6e2 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:44:02 +0100 Subject: [PATCH 121/243] [emoji] Reinstate proper licensing Has been entirely rewritten in 970f98b --- {licensing/emoji => emoji}/__init__.py | 2 ++ 1 file changed, 2 insertions(+) rename {licensing/emoji => emoji}/__init__.py (99%) diff --git a/licensing/emoji/__init__.py b/emoji/__init__.py similarity index 99% rename from licensing/emoji/__init__.py rename to emoji/__init__.py index fe918e6d..bdf6db4e 100644 --- a/licensing/emoji/__init__.py +++ b/emoji/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider import json import re @@ -16,6 +17,7 @@ md_description = "Find and copy emojis by name" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/emoji" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, IndexQueryHandler): From 9ee2599be5e35533293a42b1fd7424fdff2e8c6e Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 01:50:33 +0100 Subject: [PATCH 122/243] [locate] Reinstate proper licensing - 68e11eb is just a minor fix --- {licensing/locate => locate}/__init__.py | 5 +++-- {licensing/locate => locate}/locate.svg | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename {licensing/locate => locate}/__init__.py (96%) rename {licensing/locate => locate}/locate.svg (100%) diff --git a/licensing/locate/__init__.py b/locate/__init__.py similarity index 96% rename from licensing/locate/__init__.py rename to locate/__init__.py index 29ed4c4c..494e20ac 100644 --- a/licensing/locate/__init__.py +++ b/locate/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2022 Manuel Schneider +# Copyright (c) 2022-2024 Manuel Schneider """ `locate` wrapper. Note that it is up to you to ensure that the locate database is \ @@ -17,9 +17,10 @@ md_version = "1.9" md_name = "Locate" md_description = "Find and open files using locate" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/locate" md_bin_dependencies = "locate" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): diff --git a/licensing/locate/locate.svg b/locate/locate.svg similarity index 100% rename from licensing/locate/locate.svg rename to locate/locate.svg From 57f8a6bf190e628a400b880e3600423712de133d Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 02:15:31 +0100 Subject: [PATCH 123/243] [goldendict] Reinstate proper licensing - f33d955 is superseded just a minor fix https://github.com/albertlauncher/python/commits/91845ed824ef7d61e572890c41f75c65c8d0a477/goldendict.py --- {licensing/goldendict => goldendict}/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename {licensing/goldendict => goldendict}/__init__.py (87%) diff --git a/licensing/goldendict/__init__.py b/goldendict/__init__.py similarity index 87% rename from licensing/goldendict/__init__.py rename to goldendict/__init__.py index 80126cf6..aebbc447 100644 --- a/licensing/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,11 +1,15 @@ -from albert import Action, StandardItem, TriggerQuery, PluginInstance, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error +# -*- coding: utf-8 -*- +# Copyright (c) 2017-2024 Manuel Schneider + +from albert import * md_iid = '2.0' md_version = '1.3' md_name = 'GoldenDict' md_description = 'Searches in GoldenDict' +md_license = 'MIT' md_url = 'https://github.com/albertlauncher/python/' -md_maintainers = '@stevenxxiu' +md_authors = '@manuelschneid3r' md_bin_dependencies = ['goldendict'] From 7823707c7fa646586eb9c4483d4470f206c3bddd Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 02:28:00 +0100 Subject: [PATCH 124/243] [pass] Reinstate proper licensing ignoring 30e6d6c, 1c868b2 due to loe LOC https://github.com/albertlauncher/python/commits/master/pass/ https://github.com/albertlauncher/python/commits/91845ed824ef7d61e572890c41f75c65c8d0a477/pass.py https://github.com/albertlauncher/python/commits/6ec2774966a524568112b27d285a4df6cb76ec2b/Pass.py?browsing_rename_history=true&new_path=pass.py&original_branch=91845ed824ef7d61e572890c41f75c65c8d0a477 --- {licensing/pass => pass}/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename {licensing/pass => pass}/__init__.py (97%) diff --git a/licensing/pass/__init__.py b/pass/__init__.py similarity index 97% rename from licensing/pass/__init__.py rename to pass/__init__.py index 5da2d5fc..9288c2af 100644 --- a/licensing/pass/__init__.py +++ b/pass/__init__.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2017 Benedict Dudel +# Copyright (c) 2023 Max +# Copyright (c) 2023 Pete-Hamlin import fnmatch import os @@ -8,9 +11,9 @@ md_version = "1.5" md_name = "Pass" md_description = "Manage passwords in pass" -md_bin_dependencies = ["pass"] -md_maintainers = ["@Pete-Hamlin"] md_license = "BSD-3" +md_authors = ["@benedictdudel", "@maxmil", "@Pete-Hamlin"] +md_bin_dependencies = ["pass"] HOME_DIR = os.environ["HOME"] PASS_DIR = os.environ.get("PASSWORD_STORE_DIR", os.path.join(HOME_DIR, ".password-store/")) From 3cbfda5a7c8456cae5d21c426da5b8ecedb063c5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 02:32:27 +0100 Subject: [PATCH 125/243] [python_eval] Reinstate proper licensing ignoring 962a832 due to low LOC https://github.com/albertlauncher/python/commits/master/python_eval/ https://github.com/albertlauncher/python/commits/10a5010aa3fb7ac93550b50772d7b6d07464389d/Python --- {licensing/python_eval => python_eval}/__init__.py | 2 ++ {licensing/python_eval => python_eval}/python.svg | 0 2 files changed, 2 insertions(+) rename {licensing/python_eval => python_eval}/__init__.py (95%) rename {licensing/python_eval => python_eval}/python.svg (100%) diff --git a/licensing/python_eval/__init__.py b/python_eval/__init__.py similarity index 95% rename from licensing/python_eval/__init__.py rename to python_eval/__init__.py index 86f86d16..9f3813a5 100644 --- a/licensing/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2017-2014 Manuel Schneider from builtins import pow from math import * @@ -12,6 +13,7 @@ md_description = "Evaluate Python code" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/master/python_eval" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): diff --git a/licensing/python_eval/python.svg b/python_eval/python.svg similarity index 100% rename from licensing/python_eval/python.svg rename to python_eval/python.svg From a0dc01acd9fea9d361f2bceffd5637a545af1ec6 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 02:38:11 +0100 Subject: [PATCH 126/243] [timer] Reinstate proper licensing https://github.com/albertlauncher/python/commits/master/timer/ https://github.com/albertlauncher/python/commits/10a5010aa3fb7ac93550b50772d7b6d07464389d/Timer --- {licensing/timer => timer}/__init__.py | 6 ++++-- {licensing/timer => timer}/time.svg | 0 2 files changed, 4 insertions(+), 2 deletions(-) rename {licensing/timer => timer}/__init__.py (96%) rename {licensing/timer => timer}/time.svg (100%) diff --git a/licensing/timer/__init__.py b/timer/__init__.py similarity index 96% rename from licensing/timer/__init__.py rename to timer/__init__.py index ab49cb5c..fc3020d7 100644 --- a/licensing/timer/__init__.py +++ b/timer/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# # Copyright (c) 2018-2024 Manuel Schneider +# # Copyright (c) 2020 Andreas Dominik Preikschat """ Takes arguments in the form of '`[[hrs:]mins:]secs [name]`'. Empty fields resolve to `0`. \ @@ -21,9 +23,9 @@ md_version = "1.7" md_name = "Timer" md_description = "Set up timers" -md_license = "BSD-2" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/timer" -md_maintainers = ["@manuelschneid3r", "@googol42", "@uztnus"] +md_authors = ["@manuelschneid3r", "@googol42"] class Timer(threading.Timer): diff --git a/licensing/timer/time.svg b/timer/time.svg similarity index 100% rename from licensing/timer/time.svg rename to timer/time.svg From a7b62710f632bee907dbfaf737c1b875ff153df4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 02:48:55 +0100 Subject: [PATCH 127/243] [vpn] Reinstate proper licensing Ignoring 7dc1514 for low LOC https://github.com/albertlauncher/python/commits/f7114926c9a5ccee0e48b9387061fd10652a1370/vpn/__init__.py?browsing_rename_history=true&new_path=licensing/vpn/__init__.py&original_branch=master https://github.com/albertlauncher/python/commits/73762b03adf98a80f19cb0039f52a8bd66d24c0e/vpn/__init_.py?browsing_rename_history=true&new_path=licensing/vpn/__init__.py&original_branch=master --- {licensing/vpn => vpn}/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename {licensing/vpn => vpn}/__init__.py (91%) diff --git a/licensing/vpn/__init__.py b/vpn/__init__.py similarity index 91% rename from licensing/vpn/__init__.py rename to vpn/__init__.py index 7c7bc02d..82177092 100644 --- a/licensing/vpn/__init__.py +++ b/vpn/__init__.py @@ -1,3 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 janeklb +# Copyright (c) 2023 Bierchermuesli +# Copyright (c) 2020-2024 Manuel Schneider + import subprocess from collections import namedtuple @@ -10,8 +15,7 @@ md_description = "Manage NetworkManager VPN connections" md_license = "MIT" md_url = "https://github.com/albertlauncher/python" -md_maintainers = ["@Bierchermuesli"] -md_credits = ["@janeklb"] +md_authors = ["@janeklb", "@Bierchermuesli", "@manuelschneid3r"] md_bin_dependencies = ["nmcli"] From 5770fb9347cc1b85f5541ad2311c58064a4e0597 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 03:11:12 +0100 Subject: [PATCH 128/243] mv mathematica_eval/ jetbrains_projects tex_to_unicode bitwarden .archive until licensing is clear --- {licensing => .archive}/bitwarden/__init__.py | 6 +++--- {licensing => .archive}/bitwarden/bw.svg | 0 {licensing => .archive}/jetbrains_projects/LICENSE | 0 .../jetbrains_projects/README.md | 0 .../jetbrains_projects/__init__.py | 8 ++++++-- .../jetbrains_projects/androidstudio.svg | 0 .../jetbrains_projects/clion.svg | 0 .../jetbrains_projects/datagrip.svg | 0 .../jetbrains_projects/dataspell.svg | 0 .../jetbrains_projects/goland.svg | 0 {licensing => .archive}/jetbrains_projects/idea.svg | 0 .../jetbrains_projects/phpstorm.svg | 0 .../jetbrains_projects/pycharm.svg | 0 .../jetbrains_projects/rider.svg | 0 .../jetbrains_projects/rubymine.svg | 0 .../jetbrains_projects/rustrover.svg | 0 .../jetbrains_projects/webstorm.svg | 0 .../mathematica_eval/__init__.py | 0 {licensing => .archive}/tex_to_unicode/__init__.py | 0 {licensing => .archive}/tex_to_unicode/tex.png | Bin 20 files changed, 9 insertions(+), 5 deletions(-) rename {licensing => .archive}/bitwarden/__init__.py (98%) rename {licensing => .archive}/bitwarden/bw.svg (100%) rename {licensing => .archive}/jetbrains_projects/LICENSE (100%) rename {licensing => .archive}/jetbrains_projects/README.md (100%) rename {licensing => .archive}/jetbrains_projects/__init__.py (97%) rename {licensing => .archive}/jetbrains_projects/androidstudio.svg (100%) rename {licensing => .archive}/jetbrains_projects/clion.svg (100%) rename {licensing => .archive}/jetbrains_projects/datagrip.svg (100%) rename {licensing => .archive}/jetbrains_projects/dataspell.svg (100%) rename {licensing => .archive}/jetbrains_projects/goland.svg (100%) rename {licensing => .archive}/jetbrains_projects/idea.svg (100%) rename {licensing => .archive}/jetbrains_projects/phpstorm.svg (100%) rename {licensing => .archive}/jetbrains_projects/pycharm.svg (100%) rename {licensing => .archive}/jetbrains_projects/rider.svg (100%) rename {licensing => .archive}/jetbrains_projects/rubymine.svg (100%) rename {licensing => .archive}/jetbrains_projects/rustrover.svg (100%) rename {licensing => .archive}/jetbrains_projects/webstorm.svg (100%) rename {licensing => .archive}/mathematica_eval/__init__.py (100%) rename {licensing => .archive}/tex_to_unicode/__init__.py (100%) rename {licensing => .archive}/tex_to_unicode/tex.png (100%) diff --git a/licensing/bitwarden/__init__.py b/.archive/bitwarden/__init__.py similarity index 98% rename from licensing/bitwarden/__init__.py rename to .archive/bitwarden/__init__.py index 23b8bcdc..87e7b4b9 100644 --- a/licensing/bitwarden/__init__.py +++ b/.archive/bitwarden/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider from pathlib import Path from subprocess import run, CalledProcessError @@ -9,10 +10,9 @@ md_version = "2.0" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python" -md_maintainers = "@ovitor" -md_credits = "Original author: @tylio" +md_authors = ["@ovitor", "@tylio", ] md_bin_dependencies = ["rbw"] diff --git a/licensing/bitwarden/bw.svg b/.archive/bitwarden/bw.svg similarity index 100% rename from licensing/bitwarden/bw.svg rename to .archive/bitwarden/bw.svg diff --git a/licensing/jetbrains_projects/LICENSE b/.archive/jetbrains_projects/LICENSE similarity index 100% rename from licensing/jetbrains_projects/LICENSE rename to .archive/jetbrains_projects/LICENSE diff --git a/licensing/jetbrains_projects/README.md b/.archive/jetbrains_projects/README.md similarity index 100% rename from licensing/jetbrains_projects/README.md rename to .archive/jetbrains_projects/README.md diff --git a/licensing/jetbrains_projects/__init__.py b/.archive/jetbrains_projects/__init__.py similarity index 97% rename from licensing/jetbrains_projects/__init__.py rename to .archive/jetbrains_projects/__init__.py index 8c66640e..82c766ad 100644 --- a/licensing/jetbrains_projects/__init__.py +++ b/.archive/jetbrains_projects/__init__.py @@ -1,3 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018-2020 Markus Richter +# Copyright (c) 2018-2023 Thomas Queste +# Copyright (c) 2023 Valentin Maerten + """ Supported IDEs: @@ -21,8 +26,7 @@ md_description = "Open your JetBrains projects" md_license = "GPL-3" md_url = "https://github.com/albertlauncher/python/" -md_maintainers = ["@mqus", "@tomsquest"] -md_authors = ["@mqus", "@tomsquest"] +md_authors = ["@mqus", "@tomsquest", "@vmaerten"] @dataclass diff --git a/licensing/jetbrains_projects/androidstudio.svg b/.archive/jetbrains_projects/androidstudio.svg similarity index 100% rename from licensing/jetbrains_projects/androidstudio.svg rename to .archive/jetbrains_projects/androidstudio.svg diff --git a/licensing/jetbrains_projects/clion.svg b/.archive/jetbrains_projects/clion.svg similarity index 100% rename from licensing/jetbrains_projects/clion.svg rename to .archive/jetbrains_projects/clion.svg diff --git a/licensing/jetbrains_projects/datagrip.svg b/.archive/jetbrains_projects/datagrip.svg similarity index 100% rename from licensing/jetbrains_projects/datagrip.svg rename to .archive/jetbrains_projects/datagrip.svg diff --git a/licensing/jetbrains_projects/dataspell.svg b/.archive/jetbrains_projects/dataspell.svg similarity index 100% rename from licensing/jetbrains_projects/dataspell.svg rename to .archive/jetbrains_projects/dataspell.svg diff --git a/licensing/jetbrains_projects/goland.svg b/.archive/jetbrains_projects/goland.svg similarity index 100% rename from licensing/jetbrains_projects/goland.svg rename to .archive/jetbrains_projects/goland.svg diff --git a/licensing/jetbrains_projects/idea.svg b/.archive/jetbrains_projects/idea.svg similarity index 100% rename from licensing/jetbrains_projects/idea.svg rename to .archive/jetbrains_projects/idea.svg diff --git a/licensing/jetbrains_projects/phpstorm.svg b/.archive/jetbrains_projects/phpstorm.svg similarity index 100% rename from licensing/jetbrains_projects/phpstorm.svg rename to .archive/jetbrains_projects/phpstorm.svg diff --git a/licensing/jetbrains_projects/pycharm.svg b/.archive/jetbrains_projects/pycharm.svg similarity index 100% rename from licensing/jetbrains_projects/pycharm.svg rename to .archive/jetbrains_projects/pycharm.svg diff --git a/licensing/jetbrains_projects/rider.svg b/.archive/jetbrains_projects/rider.svg similarity index 100% rename from licensing/jetbrains_projects/rider.svg rename to .archive/jetbrains_projects/rider.svg diff --git a/licensing/jetbrains_projects/rubymine.svg b/.archive/jetbrains_projects/rubymine.svg similarity index 100% rename from licensing/jetbrains_projects/rubymine.svg rename to .archive/jetbrains_projects/rubymine.svg diff --git a/licensing/jetbrains_projects/rustrover.svg b/.archive/jetbrains_projects/rustrover.svg similarity index 100% rename from licensing/jetbrains_projects/rustrover.svg rename to .archive/jetbrains_projects/rustrover.svg diff --git a/licensing/jetbrains_projects/webstorm.svg b/.archive/jetbrains_projects/webstorm.svg similarity index 100% rename from licensing/jetbrains_projects/webstorm.svg rename to .archive/jetbrains_projects/webstorm.svg diff --git a/licensing/mathematica_eval/__init__.py b/.archive/mathematica_eval/__init__.py similarity index 100% rename from licensing/mathematica_eval/__init__.py rename to .archive/mathematica_eval/__init__.py diff --git a/licensing/tex_to_unicode/__init__.py b/.archive/tex_to_unicode/__init__.py similarity index 100% rename from licensing/tex_to_unicode/__init__.py rename to .archive/tex_to_unicode/__init__.py diff --git a/licensing/tex_to_unicode/tex.png b/.archive/tex_to_unicode/tex.png similarity index 100% rename from licensing/tex_to_unicode/tex.png rename to .archive/tex_to_unicode/tex.png From 98b135ff262cf8986981cd9cc9687169f25191d9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 11:35:50 +0100 Subject: [PATCH 129/243] [jetbrains_projects] Reinstate proper licensing Closing https://github.com/albertlauncher/albert/issues/1351 Ignoring 860d190, 746456a, f5f5c50, 700b3be, 328dcf5, a7cda53 https://github.com/albertlauncher/python/commits/master/jetbrains_projects/ https://github.com/albertlauncher/python/commits/6ec2774966a524568112b27d285a4df6cb76ec2b/JetbrainsProjects/__init__.py?browsing_rename_history=true&new_path=jetbrains_projects/__init__.py&original_branch=6ec2774966a524568112b27d285a4df6cb76ec2b https://github.com/albertlauncher/python/commits/db49d114191feedf6fae0b268986074ef41373be/jetbrains-projects.py --- .archive/jetbrains_projects/LICENSE | 674 ------------------ .archive/jetbrains_projects/README.md | 24 - .../__init__.py | 42 +- .../androidstudio.svg | 0 .../clion.svg | 0 .../datagrip.svg | 0 .../dataspell.svg | 0 .../goland.svg | 0 .../idea.svg | 0 .../phpstorm.svg | 0 .../pycharm.svg | 0 .../rider.svg | 0 .../rubymine.svg | 0 .../rustrover.svg | 0 .../webstorm.svg | 0 15 files changed, 32 insertions(+), 708 deletions(-) delete mode 100644 .archive/jetbrains_projects/LICENSE delete mode 100644 .archive/jetbrains_projects/README.md rename {.archive/jetbrains_projects => jetbrains_projects}/__init__.py (89%) rename {.archive/jetbrains_projects => jetbrains_projects}/androidstudio.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/clion.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/datagrip.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/dataspell.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/goland.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/idea.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/phpstorm.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/pycharm.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/rider.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/rubymine.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/rustrover.svg (100%) rename {.archive/jetbrains_projects => jetbrains_projects}/webstorm.svg (100%) diff --git a/.archive/jetbrains_projects/LICENSE b/.archive/jetbrains_projects/LICENSE deleted file mode 100644 index f288702d..00000000 --- a/.archive/jetbrains_projects/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/.archive/jetbrains_projects/README.md b/.archive/jetbrains_projects/README.md deleted file mode 100644 index 08f9b880..00000000 --- a/.archive/jetbrains_projects/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Jetbrains-albert-plugin - -DISCLAIMER: This plugin has no affiliation with JetBrains s.r.o.. The icons are used under the terms specified [here](https://www.jetbrains.com/company/brand/#brand-guidelines). - -The plugin itself (the python file(s)) is licensed under the GPLv3 license. - -## How to use: - -Type `jb ` in the prompt, followed by your search term. Albert should show you a list of matching projects, scraped from your "recently edited" list of your Jetbrains IDEs. It tries to match the path of your project directory (not e.g. any files or contents). - -## Creating the project launcher: - -For this plugin to find the editors, you need to create a command line launcher for them. This is done by opening the IDE (for example PyCharm) and then going to `Tools -> Create Command-line Launcher...`. This will create a launcher (for example, `charm`) that this plugin will be able to find. - -## Authors: - -- Thomas Queste (@tomsquest) -- @mqus - -## Contributors: - -- @iyzana -- @Sharsie -- @dsager diff --git a/.archive/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py similarity index 89% rename from .archive/jetbrains_projects/__init__.py rename to jetbrains_projects/__init__.py index 82c766ad..dc598c5f 100644 --- a/.archive/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -1,15 +1,26 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018-2020 Markus Richter # Copyright (c) 2018-2023 Thomas Queste # Copyright (c) 2023 Valentin Maerten """ -Supported IDEs: - -Android Studio, CLion, DataGrip, DataSpell, GoLand, IntelliJ IDEA, PhpStorm, PyCharm, Rider, RubyMine, WebStorm. - -Note: To open projects the command-line launcher is required. If your IDE has no \ -command-line launcher in $PATH, use `Tools` > `Create Command-line Launcher`. +This plugin allows you to quickly open projects of the Jetbrains IDEs + +* Android Studio +* CLion +* DataGrip +* DataSpell +* GoLand +* IntelliJ IDEA +* PhpStorm +* PyCharm +* Rider +* RubyMine +* WebStorm. + +Note that for this plugin to find the IDEs, a commandline launcher in $PATH is required. +Open the IDE and click Tools -> Create Command-line Launcher to add one. + +Disclaimer: This plugin has no affiliation with JetBrains s.r.o.. The icons are used under the terms specified here. """ from dataclasses import dataclass @@ -24,9 +35,9 @@ md_version = "1.6" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" -md_license = "GPL-3" -md_url = "https://github.com/albertlauncher/python/" -md_authors = ["@mqus", "@tomsquest", "@vmaerten"] +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/master/jetbrains_projects" +md_authors = ["@tomsquest", "@vmaerten", "@manuelschneid3r"] @dataclass @@ -198,3 +209,14 @@ def _make_item(self, editor: Editor, project: Project, query: TriggerQuery) -> I ) ], ) + + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + 'widget_properties': { + 'textFormat': 'Qt::MarkdownText' + } + } + ] diff --git a/.archive/jetbrains_projects/androidstudio.svg b/jetbrains_projects/androidstudio.svg similarity index 100% rename from .archive/jetbrains_projects/androidstudio.svg rename to jetbrains_projects/androidstudio.svg diff --git a/.archive/jetbrains_projects/clion.svg b/jetbrains_projects/clion.svg similarity index 100% rename from .archive/jetbrains_projects/clion.svg rename to jetbrains_projects/clion.svg diff --git a/.archive/jetbrains_projects/datagrip.svg b/jetbrains_projects/datagrip.svg similarity index 100% rename from .archive/jetbrains_projects/datagrip.svg rename to jetbrains_projects/datagrip.svg diff --git a/.archive/jetbrains_projects/dataspell.svg b/jetbrains_projects/dataspell.svg similarity index 100% rename from .archive/jetbrains_projects/dataspell.svg rename to jetbrains_projects/dataspell.svg diff --git a/.archive/jetbrains_projects/goland.svg b/jetbrains_projects/goland.svg similarity index 100% rename from .archive/jetbrains_projects/goland.svg rename to jetbrains_projects/goland.svg diff --git a/.archive/jetbrains_projects/idea.svg b/jetbrains_projects/idea.svg similarity index 100% rename from .archive/jetbrains_projects/idea.svg rename to jetbrains_projects/idea.svg diff --git a/.archive/jetbrains_projects/phpstorm.svg b/jetbrains_projects/phpstorm.svg similarity index 100% rename from .archive/jetbrains_projects/phpstorm.svg rename to jetbrains_projects/phpstorm.svg diff --git a/.archive/jetbrains_projects/pycharm.svg b/jetbrains_projects/pycharm.svg similarity index 100% rename from .archive/jetbrains_projects/pycharm.svg rename to jetbrains_projects/pycharm.svg diff --git a/.archive/jetbrains_projects/rider.svg b/jetbrains_projects/rider.svg similarity index 100% rename from .archive/jetbrains_projects/rider.svg rename to jetbrains_projects/rider.svg diff --git a/.archive/jetbrains_projects/rubymine.svg b/jetbrains_projects/rubymine.svg similarity index 100% rename from .archive/jetbrains_projects/rubymine.svg rename to jetbrains_projects/rubymine.svg diff --git a/.archive/jetbrains_projects/rustrover.svg b/jetbrains_projects/rustrover.svg similarity index 100% rename from .archive/jetbrains_projects/rustrover.svg rename to jetbrains_projects/rustrover.svg diff --git a/.archive/jetbrains_projects/webstorm.svg b/jetbrains_projects/webstorm.svg similarity index 100% rename from .archive/jetbrains_projects/webstorm.svg rename to jetbrains_projects/webstorm.svg From e667d005dbd86cf36efab53b11d07ff974d48970 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:15:37 +0100 Subject: [PATCH 130/243] [aur:1.9] Show docstring in config widget --- aur/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/aur/__init__.py b/aur/__init__.py index 3989eb95..7dfb0c97 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -15,13 +15,12 @@ from albert import * -md_iid = '2.0' -md_version = "1.8" +md_iid = '2.2' +md_version = "1.9" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/aur" -# md_platforms = ["Linux"] md_authors = "@manuelschneid3r" @@ -52,6 +51,14 @@ def __init__(self): info("No supported AUR helper found.") self.install_cmdline = None + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip() + } + ] + def handleTriggerQuery(self, query): for _ in range(50): sleep(0.01) From ea06843bb796093f2618430be807db6eee4bddef Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:15:57 +0100 Subject: [PATCH 131/243] [color:1.2] Show docstring in config widget --- color/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/color/__init__.py b/color/__init__.py index 6a840120..beda8f9b 100644 --- a/color/__init__.py +++ b/color/__init__.py @@ -18,8 +18,8 @@ from urllib.parse import quote_plus from string import hexdigits -md_iid = '2.0' -md_version = '1.1' +md_iid = '2.2' +md_version = '1.2' md_name = 'Color' md_description = 'Display color for color codes' md_license = 'MIT' @@ -66,4 +66,5 @@ def configWidget(self): 'type': 'label', 'text': __doc__.strip() } - ] \ No newline at end of file + ] + \ No newline at end of file From 88abbdc1c0e3458b2c0d0317c9e60cdd296df0bf Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:17:37 +0100 Subject: [PATCH 132/243] [dice_roll:1.4] Show docstring in config widget --- dice_roll/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index dc2e3c88..b65aa82b 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -15,8 +15,8 @@ Example: "roll 2d6 3d8 1d20" """ -md_iid = '2.0' -md_version = "1.3" +md_iid = '2.2' +md_version = "1.4" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -141,6 +141,14 @@ def __init__(self): ) albert.PluginInstance.__init__(self, extensions=[self]) + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + } + ] + def handleTriggerQuery(self, query: albert.Query) -> None: query_string = query.string.strip() try: From b3b38e8528c0f522d77a9f3f4655b1c4206d01c9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:19:09 +0100 Subject: [PATCH 133/243] [inhibit_sleep] Show docstring in config widget --- inhibit_sleep/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/inhibit_sleep/__init__.py b/inhibit_sleep/__init__.py index b27c65df..968cab1d 100644 --- a/inhibit_sleep/__init__.py +++ b/inhibit_sleep/__init__.py @@ -4,14 +4,14 @@ """ Provides an item 'Inhibit sleep' which can be used to temporarily disable system suspension. -This is a prototype using `systemd-inhibit`. A sophisticated implementation would probably use the systemd D-Bus +This is a prototype using `systemd-inhibit`. A sophisticated implementation would probably use the systemd D-Bus \ interface documented [here](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html). """ from albert import * from subprocess import Popen, TimeoutExpired -md_iid = '2.0' +md_iid = '2.2' md_version = '1.0' md_name = 'Inhibit sleep' md_description = 'Inhibit system sleep mode.' @@ -50,6 +50,17 @@ def toggle(self): "sleep", "infinity"]) info(str(self.proc)) + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + 'widget_properties': { + 'textFormat': 'Qt::MarkdownText' + } + } + ] + def handleGlobalQuery(self, query: GlobalQuery): stripped = query.string.strip().lower() if stripped in "inhibit sleep": From 704fbd641b9ea990d2aa1a6a1cea72dc86be3f8c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:24:30 +0100 Subject: [PATCH 134/243] [jetbrains_projects:1.7] Move icons in a dir, correct iid --- jetbrains_projects/__init__.py | 28 +++++++++---------- .../{ => icons}/androidstudio.svg | 0 jetbrains_projects/{ => icons}/clion.svg | 0 jetbrains_projects/{ => icons}/datagrip.svg | 0 jetbrains_projects/{ => icons}/dataspell.svg | 0 jetbrains_projects/{ => icons}/goland.svg | 0 jetbrains_projects/{ => icons}/idea.svg | 0 jetbrains_projects/{ => icons}/phpstorm.svg | 0 jetbrains_projects/{ => icons}/pycharm.svg | 0 jetbrains_projects/{ => icons}/rider.svg | 0 jetbrains_projects/{ => icons}/rubymine.svg | 0 jetbrains_projects/{ => icons}/rustrover.svg | 0 jetbrains_projects/{ => icons}/webstorm.svg | 0 13 files changed, 14 insertions(+), 14 deletions(-) rename jetbrains_projects/{ => icons}/androidstudio.svg (100%) rename jetbrains_projects/{ => icons}/clion.svg (100%) rename jetbrains_projects/{ => icons}/datagrip.svg (100%) rename jetbrains_projects/{ => icons}/dataspell.svg (100%) rename jetbrains_projects/{ => icons}/goland.svg (100%) rename jetbrains_projects/{ => icons}/idea.svg (100%) rename jetbrains_projects/{ => icons}/phpstorm.svg (100%) rename jetbrains_projects/{ => icons}/pycharm.svg (100%) rename jetbrains_projects/{ => icons}/rider.svg (100%) rename jetbrains_projects/{ => icons}/rubymine.svg (100%) rename jetbrains_projects/{ => icons}/rustrover.svg (100%) rename jetbrains_projects/{ => icons}/webstorm.svg (100%) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index dc598c5f..7409e8b0 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -31,8 +31,8 @@ from xml.etree import ElementTree from albert import * -md_iid = '2.0' -md_version = "1.6" +md_iid = '2.2' +md_version = "1.7" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -115,65 +115,65 @@ def __init__(self): editors = [ Editor( name="Android Studio", - icon=plugin_dir / "androidstudio.svg", + icon=plugin_dir / "icons" / "androidstudio.svg", config_dir_prefix="Google/AndroidStudio", binaries=["studio", "androidstudio", "android-studio", "android-studio-canary", "jdk-android-studio", "android-studio-system-jdk"]), Editor( name="CLion", - icon=plugin_dir / "clion.svg", + icon=plugin_dir / "icons" / "clion.svg", config_dir_prefix="JetBrains/CLion", binaries=["clion", "clion-eap"]), Editor( name="DataGrip", - icon=plugin_dir / "datagrip.svg", + icon=plugin_dir / "icons" / "datagrip.svg", config_dir_prefix="JetBrains/DataGrip", binaries=["datagrip", "datagrip-eap"]), Editor( name="DataSpell", - icon=plugin_dir / "dataspell.svg", + icon=plugin_dir / "icons" / "dataspell.svg", config_dir_prefix="JetBrains/DataSpell", binaries=["dataspell", "dataspell-eap"]), Editor( name="GoLand", - icon=plugin_dir / "goland.svg", + icon=plugin_dir / "icons" / "goland.svg", config_dir_prefix="JetBrains/GoLand", binaries=["goland", "goland-eap"]), Editor( name="IntelliJ IDEA", - icon=plugin_dir / "idea.svg", + icon=plugin_dir / "icons" / "idea.svg", config_dir_prefix="JetBrains/IntelliJIdea", binaries=["idea", "idea.sh", "idea-ultimate", "idea-ce-eap", "idea-ue-eap", "intellij-idea-ce", "intellij-idea-ce-eap", "intellij-idea-ue-bundled-jre", "intellij-idea-ultimate-edition", "intellij-idea-community-edition-jre", "intellij-idea-community-edition-no-jre"]), Editor( name="PhpStorm", - icon=plugin_dir / "phpstorm.svg", + icon=plugin_dir / "icons" / "phpstorm.svg", config_dir_prefix="JetBrains/PhpStorm", binaries=["phpstorm", "phpstorm-eap"]), Editor( name="PyCharm", - icon=plugin_dir / "pycharm.svg", + icon=plugin_dir / "icons" / "pycharm.svg", config_dir_prefix="JetBrains/PyCharm", binaries=["charm", "pycharm", "pycharm-eap"]), Editor( name="Rider", - icon=plugin_dir / "rider.svg", + icon=plugin_dir / "icons" / "rider.svg", config_dir_prefix="JetBrains/Rider", binaries=["rider", "rider-eap"]), Editor( name="RubyMine", - icon=plugin_dir / "rubymine.svg", + icon=plugin_dir / "icons" / "rubymine.svg", config_dir_prefix="JetBrains/RubyMine", binaries=["rubymine", "rubymine-eap", "jetbrains-rubymine", "jetbrains-rubymine-eap"]), Editor( name="WebStorm", - icon=plugin_dir / "webstorm.svg", + icon=plugin_dir / "icons" / "webstorm.svg", config_dir_prefix="JetBrains/WebStorm", binaries=["webstorm", "webstorm-eap"]), Editor( name="RustRover", - icon=plugin_dir / "rustrover.svg", + icon=plugin_dir / "icons" / "rustrover.svg", config_dir_prefix="JetBrains/RustRover", binaries=["rustrover", "rustrover-eap"]), ] diff --git a/jetbrains_projects/androidstudio.svg b/jetbrains_projects/icons/androidstudio.svg similarity index 100% rename from jetbrains_projects/androidstudio.svg rename to jetbrains_projects/icons/androidstudio.svg diff --git a/jetbrains_projects/clion.svg b/jetbrains_projects/icons/clion.svg similarity index 100% rename from jetbrains_projects/clion.svg rename to jetbrains_projects/icons/clion.svg diff --git a/jetbrains_projects/datagrip.svg b/jetbrains_projects/icons/datagrip.svg similarity index 100% rename from jetbrains_projects/datagrip.svg rename to jetbrains_projects/icons/datagrip.svg diff --git a/jetbrains_projects/dataspell.svg b/jetbrains_projects/icons/dataspell.svg similarity index 100% rename from jetbrains_projects/dataspell.svg rename to jetbrains_projects/icons/dataspell.svg diff --git a/jetbrains_projects/goland.svg b/jetbrains_projects/icons/goland.svg similarity index 100% rename from jetbrains_projects/goland.svg rename to jetbrains_projects/icons/goland.svg diff --git a/jetbrains_projects/idea.svg b/jetbrains_projects/icons/idea.svg similarity index 100% rename from jetbrains_projects/idea.svg rename to jetbrains_projects/icons/idea.svg diff --git a/jetbrains_projects/phpstorm.svg b/jetbrains_projects/icons/phpstorm.svg similarity index 100% rename from jetbrains_projects/phpstorm.svg rename to jetbrains_projects/icons/phpstorm.svg diff --git a/jetbrains_projects/pycharm.svg b/jetbrains_projects/icons/pycharm.svg similarity index 100% rename from jetbrains_projects/pycharm.svg rename to jetbrains_projects/icons/pycharm.svg diff --git a/jetbrains_projects/rider.svg b/jetbrains_projects/icons/rider.svg similarity index 100% rename from jetbrains_projects/rider.svg rename to jetbrains_projects/icons/rider.svg diff --git a/jetbrains_projects/rubymine.svg b/jetbrains_projects/icons/rubymine.svg similarity index 100% rename from jetbrains_projects/rubymine.svg rename to jetbrains_projects/icons/rubymine.svg diff --git a/jetbrains_projects/rustrover.svg b/jetbrains_projects/icons/rustrover.svg similarity index 100% rename from jetbrains_projects/rustrover.svg rename to jetbrains_projects/icons/rustrover.svg diff --git a/jetbrains_projects/webstorm.svg b/jetbrains_projects/icons/webstorm.svg similarity index 100% rename from jetbrains_projects/webstorm.svg rename to jetbrains_projects/icons/webstorm.svg From 3299f14f316c6bac80029a12fcb14ab4956155ec Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:25:28 +0100 Subject: [PATCH 135/243] [pomodora:1.4] Show docstring in config widget --- pomodoro/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 55ac7180..3e75decb 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -12,8 +12,8 @@ from albert import * -md_iid = '2.0' -md_version = "1.3" +md_iid = '2.2' +md_version = "1.4" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "MIT" @@ -85,6 +85,14 @@ def __init__(self): self.pomodoro = PomodoroTimer() self.iconUrls = [f"file:{Path(__file__).parent}/pomodoro.svg"] + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + } + ] + def handleTriggerQuery(self, query): item = StandardItem( id=md_id, From 685734155c9dc08152684857174bb11300621be7 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:25:50 +0100 Subject: [PATCH 136/243] [timer:1.8] Show docstring in config widget --- timer/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/timer/__init__.py b/timer/__init__.py index fc3020d7..0a577485 100644 --- a/timer/__init__.py +++ b/timer/__init__.py @@ -19,8 +19,8 @@ from albert import * -md_iid = '2.0' -md_version = "1.7" +md_iid = '2.2' +md_version = "1.8" md_name = "Timer" md_description = "Set up timers" md_license = "MIT" @@ -73,6 +73,15 @@ def onTimerTimeout(self, timer): ) self.deleteTimer(timer) + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + 'widget_properties': { 'textFormat': 'Qt::MarkdownText' } + } + ] + def handleTriggerQuery(self, query): if not query.isValid: return From 5a34d94a8939ce4ad4edf9a842826f9138c4fbf5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:26:03 +0100 Subject: [PATCH 137/243] [translators:1.5] Show docstring in config widget --- translators/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 7ed4fc59..36b563eb 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -12,8 +12,8 @@ from albert import * import translators as ts -md_iid = '2.0' -md_version = "1.4" +md_iid = '2.2' +md_version = "1.5" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "MIT" @@ -72,6 +72,10 @@ def lang(self, value): def configWidget(self): return [ + { + 'type': 'label', + 'text': __doc__.strip(), + }, { 'type': 'combobox', 'property': 'translator', From c00750406987cff65e1df23ea408332c81bbd53c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 14 Jan 2024 18:26:28 +0100 Subject: [PATCH 138/243] [zeal] Remove unused docstring --- zeal/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/zeal/__init__.py b/zeal/__init__.py index 7ebae3c2..1201d130 100644 --- a/zeal/__init__.py +++ b/zeal/__init__.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 Manuel Schneider -"""Search in Zeal offline docs.""" - from albert import * md_iid = '2.0' From 8c96cd374f16d267e6952a218c45c9d8017999e4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 19 Jan 2024 00:33:32 +0100 Subject: [PATCH 139/243] [pomodoro:1.5] Fix * notifications * property synopsis * Ux. Dislpay action --- pomodoro/__init__.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 3e75decb..2849131d 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -2,7 +2,7 @@ # Copyright (c) 2024 Manuel Schneider """ -https://en.wikipedia.org/wiki/Pomodoro_Technique +Wiki: [Pomodoro_Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique). """ import subprocess @@ -13,7 +13,7 @@ from albert import * md_iid = '2.2' -md_version = "1.4" +md_version = "1.5" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "MIT" @@ -26,22 +26,23 @@ class PomodoroTimer: def __init__(self): self.isBreak = True self.timer = None + self.notification = None def timeout(self): if self.isBreak: duration = self.pomodoroDuration * 60 self.timer = threading.Timer(duration, self.timeout) self.endTime = time.time() + duration - sendTrayNotification("PomodoroTimer", "Let's go to work!") + self.notification = Notification("PomodoroTimer", "Let's go to work!") self.timer.start() else: self.remainingTillLongBreak -= 1 if self.remainingTillLongBreak == 0: self.remainingTillLongBreak = self.count - sendTrayNotification("PomodoroTimer", "Take a long break (%s min)" % self.longBreakDuration) + self.notification = Notification("PomodoroTimer", "Take a long break (%s min)" % self.longBreakDuration) duration = self.longBreakDuration * 60 else: - sendTrayNotification("PomodoroTimer", "Take a short break (%s min)" % self.breakDuration) + self.notification = Notification("PomodoroTimer", "Take a short break (%s min)" % self.breakDuration) duration = self.breakDuration * 60 self.endTime = time.time() + duration self.timer = threading.Timer(duration, self.timeout) @@ -90,6 +91,7 @@ def configWidget(self): { 'type': 'label', 'text': __doc__.strip(), + 'widget_properties': {'textFormat': 'Qt::MarkdownText'} } ] @@ -97,30 +99,32 @@ def handleTriggerQuery(self, query): item = StandardItem( id=md_id, iconUrls=self.iconUrls, - text=md_name ) if self.pomodoro.isActive(): + item.text = "Stop Pomodoro" item.actions = [Action("stop", "Stop", lambda p=self.pomodoro: p.stop())] if self.pomodoro.isBreak: whatsNext = "Pomodoro" else: whatsNext = "Long break" if self.pomodoro.remainingTillLongBreak == 1 else "Short break" - item.subtext = "Stop pomodoro (Next: %s at %s)" \ - % (whatsNext, time.strftime("%X", time.localtime(self.pomodoro.endTime))) + item.subtext = "%s at %s" % (whatsNext, time.strftime("%X", time.localtime(self.pomodoro.endTime))) query.add(item) else: tokens = query.string.split() if len(tokens) > 4 or not all([t.isdigit() for t in tokens]): - item.subtext = "Invalid parameters. Use %s" % self.synopsis() + item.text = "Invalid parameters" + item.subtext = "Use %s" % self.synopsis query.add(item) else: p = int(tokens[0]) if len(tokens) > 0 else self.default_pomodoro_duration b = int(tokens[1]) if len(tokens) > 1 else self.default_break_duration lb = int(tokens[2]) if len(tokens) > 2 else self.default_longbreak_duration c = int(tokens[3]) if len(tokens) > 3 else self.default_pomodoro_count - item.subtext = "Start new pomodoro timer (%s min, break %s min, long break %s min, count %s)"\ - % (p, b, lb, c) - item.actions = [Action("start", "Start", lambda p=p, b=b, lb=lb, c=c: self.pomodoro.start(p, b, lb, c))] + + item.text = "Start Pomodoro" + item.subtext = f"{p} min, break {b} min, long break {lb} min, count {c}" + item.actions = [Action("start", "Start", + lambda p=p, b=b, lb=lb, c=c: self.pomodoro.start(p, b, lb, c))] query.add(item) From 581bff3e5b7218513f0394fe03b846a3e62a2238 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 26 Jan 2024 13:33:39 +0100 Subject: [PATCH 140/243] [bitwarden:2.1] Proper licensing See https://github.com/albertlauncher/albert/issues/1350 --- {.archive/bitwarden => bitwarden}/__init__.py | 5 ++--- {.archive/bitwarden => bitwarden}/bw.svg | 0 2 files changed, 2 insertions(+), 3 deletions(-) rename {.archive/bitwarden => bitwarden}/__init__.py (98%) rename {.archive/bitwarden => bitwarden}/bw.svg (100%) diff --git a/.archive/bitwarden/__init__.py b/bitwarden/__init__.py similarity index 98% rename from .archive/bitwarden/__init__.py rename to bitwarden/__init__.py index 87e7b4b9..72421d52 100644 --- a/.archive/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2024 Manuel Schneider from pathlib import Path from subprocess import run, CalledProcessError @@ -7,12 +6,12 @@ from albert import * md_iid = '2.1' -md_version = "2.0" +md_version = "2.1" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "MIT" md_url = "https://github.com/albertlauncher/python" -md_authors = ["@ovitor", "@tylio", ] +md_authors = ["@ovitor", "@daviddeadly", "@manuelschneid3r"] md_bin_dependencies = ["rbw"] diff --git a/.archive/bitwarden/bw.svg b/bitwarden/bw.svg similarity index 100% rename from .archive/bitwarden/bw.svg rename to bitwarden/bw.svg From 14848376d5e7c345c80094dd2f098561bbb4334f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 6 Feb 2024 22:10:57 +0100 Subject: [PATCH 141/243] [docker:2.0] * Show error on conn failure. * Fix pathlib import --- docker/__init__.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index f47905f0..3a2c8a21 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 Manuel Schneider +from pathlib import Path + import docker from albert import * md_iid = "2.0" -md_version = "1.6" +md_version = "2.0" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "MIT" @@ -31,10 +33,23 @@ def __init__(self): def handleGlobalQuery(self, query): rank_items = [] - try: - if not self.client: + + if not self.client: + try: self.client = docker.from_env() + except Exception as e: + rank_items.append(RankItem( + item=StandardItem( + id='except', + text="Failed starting docker client", + subtext=str(e), + iconUrls=self.icon_urls_running, + ), + score=1.0 + )) + return rank_items + try: for container in self.client.containers.list(all=True): if query.string in container.name: # Create dynamic actions @@ -79,7 +94,7 @@ def handleGlobalQuery(self, query): score=len(query.string)/len(tag) )) except Exception as e: - warning(e) + warning(str(e)) self.client = None return rank_items From f0831a4a2f0b3fcfe9ed06265131a587496cd892 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 10 Feb 2024 23:29:26 +0100 Subject: [PATCH 142/243] [jetbrains:1.8] * Bullets in config --- jetbrains_projects/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 7409e8b0..cde5006b 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -5,17 +5,17 @@ """ This plugin allows you to quickly open projects of the Jetbrains IDEs -* Android Studio -* CLion -* DataGrip -* DataSpell -* GoLand -* IntelliJ IDEA -* PhpStorm -* PyCharm -* Rider -* RubyMine -* WebStorm. +- Android Studio +- CLion +- DataGrip +- DataSpell +- GoLand +- IntelliJ IDEA +- PhpStorm +- PyCharm +- Rider +- RubyMine +- WebStorm. Note that for this plugin to find the IDEs, a commandline launcher in $PATH is required. Open the IDE and click Tools -> Create Command-line Launcher to add one. @@ -32,7 +32,7 @@ from albert import * md_iid = '2.2' -md_version = "1.7" +md_version = "1.8" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" From 72a8863accb81f484f5d348e11c91e706eede9e9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 27 Feb 2024 20:51:16 +0100 Subject: [PATCH 143/243] [pomodoro:1.6] * Fix authors metadata field --- pomodoro/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 2849131d..7e8ab084 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -13,12 +13,12 @@ from albert import * md_iid = '2.2' -md_version = "1.5" +md_version = "1.6" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/master/pomodoro" -md_author = "@manuelschneid3r" +md_authors = "@manuelschneid3r" class PomodoroTimer: From cd790246d42fdf6ee8fe5cddc2c8adc13f101d5c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 27 Feb 2024 20:51:36 +0100 Subject: [PATCH 144/243] [pass:1.6] * Add url metadata --- pass/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pass/__init__.py b/pass/__init__.py index 9288c2af..d0c378f7 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -8,10 +8,11 @@ from albert import * md_iid = "2.1" -md_version = "1.5" +md_version = "1.6" md_name = "Pass" md_description = "Manage passwords in pass" md_license = "BSD-3" +md_url = "https://github.com/albertlauncher/python/tree/master/pass" md_authors = ["@benedictdudel", "@maxmil", "@Pete-Hamlin"] md_bin_dependencies = ["pass"] From faeddafbb1cb6781549ac789adf338c97491dc6c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 1 Mar 2024 11:47:24 +0100 Subject: [PATCH 145/243] [virtualbox:1.6] Add info on vboxapi requirement --- virtualbox/__init__.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/virtualbox/__init__.py b/virtualbox/__init__.py index 4b628a27..3b908840 100644 --- a/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -1,13 +1,18 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 Manuel Schneider +""" +This plugin is based on [virtualbox-python](https://pypi.org/project/virtualbox/) and needs the 'vboxapi' module which +is part of the VirtualBox SDK. Some distributions package the SDK, e.g. Arch has +[virtualbox-sdk](https://archlinux.org/packages/extra/x86_64/virtualbox-sdk/). +""" import virtualbox from virtualbox.library import LockType, MachineState from albert import * -md_iid = '2.0' -md_version = "1.5" +md_iid = '2.2' +md_version = "1.6" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" md_license = "MIT" @@ -67,6 +72,17 @@ def __init__(self): PluginInstance.__init__(self, extensions=[self]) self.iconUrls = ["xdg:virtualbox", ":unknown"] + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + 'widget_properties': { + 'textFormat': 'Qt::MarkdownText' + } + } + ] + def handleTriggerQuery(self, query): items = [] pattern = query.string.strip().lower() From 96dc437e4dcbcc2fb7d8729fbe27ddca631c73ba Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 12:34:29 +0200 Subject: [PATCH 146/243] Remove albert.pyi Ship, install and update with plugin. Add python stubfile to ignore files --- .gitignore | 3 +- albert.pyi | 469 ----------------------------------------------------- 2 files changed, 2 insertions(+), 470 deletions(-) delete mode 100644 albert.pyi diff --git a/.gitignore b/.gitignore index 1d4436e8..bef49833 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__ /.idea /.vscode -/.venv \ No newline at end of file +/.venv +albert.pyi \ No newline at end of file diff --git a/albert.pyi b/albert.pyi deleted file mode 100644 index 43eebdf8..00000000 --- a/albert.pyi +++ /dev/null @@ -1,469 +0,0 @@ -""" -# Albert Python interface v2.2 - - -The Python interface is a subset of the internal C++ interface exposed to Python with some minor adjustments. A Python -plugin is required to contain the mandatory metadata and a plugin class, both described below. To get started read the -top level classes and function names in this file. Most of them are self explanatory. In case of questions see the C++ -documentation at https://albertlauncher.github.io/reference/namespacealbert.html - - -## Mandatory metadata variables - -md_iid: str | Interface version (.) -md_version: str | Plugin version (.) -md_name: str | Human readable name -md_description: str | A brief, imperative description. (Like "Launch apps" or "Open files") - - -## Optional metadata variables: - -md_id | Identifier overwrite. [a-zA-Z0-9_]. Note: This variable is attached at runtime - | if it is unset and defaults to the module name. -md_license: str | Short form e.g. MIT or BSD-2 -md_url: str | Browsable source, issues etc -md_authors: [str|List(str)] | The authors. Preferably using mentionable Github usernames. -md_bin_dependencies: [str|List(str)] | Required executable(s). Have to match the name of the executable in $PATH. -md_lib_dependencies: [str|List(str)] | Required Python package(s). Have to match the PyPI package name. -md_credits: [str|List(str)] | Third party credit(s) and license notes - - -## The Plugin class - -The plugin class is the entry point for a Python plugin. It is instantiated on plugin initialization and has to subclass -PluginInstance. Implement extensions by subclassing _one_ extension class (TriggerQueryHandler etc…) provided by the -built-in `albert` module and pass the list of your extensions to the PluginInstance init function. Due to the -differences in type systems multiple inheritance of extensions is not supported. (Python does not support virtual -inheritance, which is used in the C++ space to inherit from 'Extension'). - -Changes in 2.1 - - - Add PluginInstance.readConfig - - Add PluginInstance.writeConfig - - Add PluginInstance.configWidget - -Changes in 2.2: - - - PluginInstance.configWidget supports 'label' - - __doc__ is not used anymore, since 0.23 drops long_description metadata - - md_maintainers not used anymore - - md_authors new optional field -""" - -from abc import abstractmethod, ABC -from enum import Enum -from typing import Any -from typing import Callable -from typing import List -from typing import Optional -from typing import Union -from typing import overload - - -class PluginInstance(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1PluginInstance.html""" - - def __init__(self, extensions: List[Extension] = []): - ... - - @property - def id(self) -> str: - ... - - @property - def name(self) -> str: - ... - - @property - def description(self) -> str: - ... - - @property - def cacheLocation(self) -> pathlib.Path: - ... - - @property - def configLocation(self) -> pathlib.Path: - ... - - @property - def dataLocation(self) -> pathlib.Path: - ... - - @property - def extensions(self) -> List[Extension]: - ... - - def initialize(self): - ... - - def finalize(self): - ... - - def readConfig(self, key: str, type: type[str|int|float|bool]) -> str|int|float|bool|None: - """ - Read a config value from the Albert settings. - Note: Due to limitations of QSettings on some platforms the type may be lost, therefore the type expected has to - be passed. - - Returns: - The requested value or None if the value does not exist or errors occurred. - """ - - def writeConfig(self, key: str, value: str|int|float|bool): - """Write a config value to the Albert settings.""" - - def configWidget(self) -> List[dict]: - """ - **Descriptive config widget factory.** - - Define a static config widget using a list of dicts, each defining a row in the resulting form layout. Each dict - must contain key 'type' having one of the supported types specified below. Each type may define further - keys. - - **A note on 'widget_properties'** - - This is a dict setting the widget properties of a QWidget or one of its derived classes. See Qt documentation - for a particular class. Note that due to the restricted type conversion only properties of type - str|int|float|bool are settable. - - **Supported row 'type's** - - * 'label' (since 2.2) - - Display text spanning both columns. Additional keys: - - - 'text': The text to display - - 'widget_properties': https://doc.qt.io/qt-6/qlabel.html. - - * 'checkbox' - - A form layout item to edit boolean properties. Additional keys: - - - 'label': The text displayed in front of the the editor widget. - - 'property': The name of the property that will be set on changes. - - 'widget_properties': https://doc.qt.io/qt-6/qcheckbox.html - - * 'lineedit' - - A form layout item to edit string properties. Additional keys: - - - 'label': The text displayed in front of the the editor widget. - - 'property': The name of the property that will be set on changes. - - 'widget_properties': https://doc.qt.io/qt-6/qlineedit.html - - * 'combobox' - - A form layout item to set string properties using a list of options. Additional keys: - - - 'label': The text displayed in front of the the editor widget. - - 'property': The name of the property that will be set on changes. - - 'items': The list of strings used to populate the combobox. - - 'widget_properties': https://doc.qt.io/qt-6/qcombobox.html - - * 'spinbox' - - A form layout item to edit integer properties. Additional keys: - - - 'label': The text displayed in front of the the editor widget. - - 'property': The name of the property that will be set on changes. - - 'widget_properties': https://doc.qt.io/qt-6/qspinbox.html - - * 'doublespinbox' - - A form layout item to edit float properties. Additional keys: - - - 'label': The text displayed in front of the the editor widget. - - 'property': The name of the property that will be set on changes. - - 'widget_properties': https://doc.qt.io/qt-6/qdoublespinbox.html - - Returns: - A list of dicts, describing a form layout as defined above. - """ - -class Action: - """https://albertlauncher.github.io/reference/classalbert_1_1Action.html""" - - def __init__(self, - id: str, - text: str, - callable: Callable): - ... - - -class Item(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1Item.html""" - - @abstractmethod - def id(self) -> str: - ... - - @abstractmethod - def text(self) -> str: - ... - - @abstractmethod - def subtext(self) -> str: - ... - - @abstractmethod - def inputActionText(self) -> str: - ... - - @abstractmethod - def iconUrls(self) -> List[str]: - """See https://albertlauncher.github.io/reference/classalbert_1_1IconProvider.html""" - - @abstractmethod - def actions(self) -> List[Action]: - ... - - -class StandardItem(Item): - """https://albertlauncher.github.io/reference/structalbert_1_1StandardItem.html""" - - def __init__(self, - id: str = '', - text: str = '', - subtext: str = '', - iconUrls: List[str] = [], - actions: List[Action] = [], - inputActionText: Optional[str] = ''): - ... - - id: str - text: str - subtext: str - iconUrls: List[str] - actions: List[Action] - inputActionText: str - - -class Extension(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1Extension.html""" - - @property - def id(self) -> str: - ... - - @property - def name(self) -> str: - ... - - @property - def description(self) -> str: - ... - - -class FallbackHandler(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1FallbackHandler.html""" - - @abstractmethod - def fallbacks(self, query: str ) ->List[Item]: - ... - - -class TriggerQuery(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1TriggerQueryHandler_1_1TriggerQuery.html""" - - @property - def trigger(self) -> str: - ... - - @property - def string(self) -> str: - ... - - @property - def isValid(self) -> bool: - ... - - @overload - def add(self, item: Item): - ... - - @overload - def add(self, item: List[Item]): - ... - - -class TriggerQueryHandler(Extension): - """https://albertlauncher.github.io/reference/classalbert_1_1TriggerQueryHandler.html""" - - def __init__(self, - id: str, - name: str, - description: str, - synopsis: str = '', - defaultTrigger: str = f'{id} ', - allowTriggerRemap: str = true, - supportsFuzzyMatching: bool = False): - ... - - @property - def synopsis(self) -> str: - ... - - @property - def trigger(self) -> str: - ... - - @property - def defaultTrigger(self) -> str: - ... - - @property - def allowTriggerRemap(self) -> bool: - ... - - @property - def supportsFuzzyMatching(self) -> bool: - ... - - @property - def fuzzyMatching(self) -> bool: - ... - - @fuzzyMatching.setter - def setFuzzyMatching(self, enabled: bool): - ... - - @abstractmethod - def handleTriggerQuery(self, query: TriggerQuery): - ... - - -class RankItem: - """https://albertlauncher.github.io/reference/classalbert_1_1RankItem.html""" - - def __init__(self, item: Item, score: float): - ... - - item: Item - score: float - - -class GlobalQuery(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1GlobalQueryHandler_1_1GlobalQuery.html""" - - @property - def string(self) -> str: - ... - - @property - def isValid(self) -> bool: - ... - - -class GlobalQueryHandler(TriggerQueryHandler): - """https://albertlauncher.github.io/reference/classalbert_1_1GlobalQueryHandler.html""" - - def __init__(self, - id: str, - name: str, - description: str, - synopsis: str = '', - defaultTrigger: str = f'{id} ', - allowTriggerRemap: str = true, - supportsFuzzyMatching: bool = False): - ... - - @abstractmethod - def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: - ... - - def applyUsageScore(self, rank_items: List[RankItem]): - ... - - def handleTriggerQuery(self, query: TriggerQuery): - ... - - -class IndexItem: - """https://albertlauncher.github.io/reference/classalbert_1_1IndexItem.html""" - - def __init__(self, item: AbstractItem, string: str): - ... - - item: AbstractItem - string: str - - -class IndexQueryHandler(GlobalQueryHandler): - """https://albertlauncher.github.io/reference/classalbert_1_1IndexQueryHandler.html""" - - @abstractmethod - def updateIndexItems(self): - ... - - def setIndexItems(self, indexItems: List[RankItem]): - ... - - def handleGlobalQuery(self, query: GlobalQuery) -> List[RankItem]: - ... - - -class Notification: - - def __init__(self, title: str, subtitle: str = '', text: str = ''): - ... - - -def debug(arg: Any): - """Module attached attribute""" - - -def info(arg: Any): - """Module attached attribute""" - - -def warning(arg: Any): - """Module attached attribute""" - - -def critical(arg: Any): - """Module attached attribute""" - - -def setClipboardText(text: str=''): - """ - Set the system clipboard text. - Args: - text: The text used to set the clipboard - """ - - -def setClipboardTextAndPaste(text: str=''): - """ - Set the system clipboard text and paste to the front-most window - Args: - text: The text used to set the clipboard - """ - - -def openUrl(url: str = ''): - """ - Open an URL using QDesktopServices::openUrl. - Args: - url: The URL to open - """ - - -def runDetachedProcess(cmdln: List[str] = [], workdir: str = ''): - """ - Run a detached process. - Args: - cmdln: The commandline to run in the terminal (argv) - workdir: The working directory used to run the terminal - """ - - -def runTerminal(script: str = '', workdir: str = '', close_on_exit: bool = False): - """ - Run a script in the users shell and terminal. - Args: - script: The script to be executed. - workdir: The working directory used to run the process - close_on_exit: Close the terminal on exit. Otherwise exec $SHELL. - """ - From a7bb92d061f9ad041e01825929b1adef72bb72b1 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 12:23:29 +0200 Subject: [PATCH 147/243] [arch_wiki:1.6] - Min api 2.3 --- arch_wiki/__init__.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/arch_wiki/__init__.py b/arch_wiki/__init__.py index 2dbf9456..2b8eb1db 100644 --- a/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -8,12 +8,12 @@ from albert import * -md_iid = '2.0' -md_version = "1.5" -md_name = "Arch Linux Wiki" -md_description = "Search Arch Linux Wiki articles" -md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/arch_wiki" +md_iid = '2.3' +md_version = '1.6' +md_name = 'Arch Linux Wiki' +md_description = 'Search Arch Linux Wiki articles' +md_license = 'MIT' +md_url = 'https://github.com/albertlauncher/python/tree/main/arch_wiki' md_authors = "@manuelschneid3r" @@ -24,12 +24,8 @@ class Plugin(PluginInstance, TriggerQueryHandler): user_agent = "org.albert.extension.python.archwiki" def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='awiki ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger='awiki ') self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] def handleTriggerQuery(self, query): @@ -62,7 +58,7 @@ def handleTriggerQuery(self, query): summary = data[2][i] url = data[3][i] - results.append(StandardItem(id=md_id, + results.append(StandardItem(id=self.id, text=title, subtext=summary if summary else url, iconUrls=self.iconUrls, @@ -73,7 +69,7 @@ def handleTriggerQuery(self, query): if results: query.add(results) else: - query.add(StandardItem(id=md_id, + query.add(StandardItem(id=self.id, text="Search '%s'" % query.string, subtext="No results. Start online search on Arch Wiki", iconUrls=self.iconUrls, @@ -81,7 +77,7 @@ def handleTriggerQuery(self, query): lambda s=query.string: openUrl(self.search_url % s))])) else: - query.add(StandardItem(id=md_id, + query.add(StandardItem(id=self.id, text=md_name, iconUrls=self.iconUrls, subtext="Enter a query to search on the Arch Wiki")) From f8c63dbdb6cee7cff761fd3a6b43e10662610c7b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 12:21:02 +0200 Subject: [PATCH 148/243] [aur:1.10] - Min api 2.3 --- aur/__init__.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/aur/__init__.py b/aur/__init__.py index 7dfb0c97..7fa763a1 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -15,12 +15,12 @@ from albert import * -md_iid = '2.2' -md_version = "1.9" +md_iid = '2.3' +md_version = "1.10" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/aur" +md_url = "https://github.com/albertlauncher/python/tree/main/aur" md_authors = "@manuelschneid3r" @@ -30,13 +30,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): baseurl = 'https://aur.archlinux.org/rpc/' def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='aur ') - PluginInstance.__init__(self, extensions=[self]) - + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='aur ' + ) self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] if which("yaourt"): @@ -80,7 +78,7 @@ def handleTriggerQuery(self, query): data = json.loads(response.read().decode()) if data['type'] == "error": query.add(StandardItem( - id=md_id, + id=self.id, text="Error", subtext=data['error'], iconUrls=self.iconUrls @@ -94,7 +92,7 @@ def handleTriggerQuery(self, query): for entry in results_json: name = entry['Name'] item = StandardItem( - id=md_id, + id=self.id, iconUrls=self.iconUrls, text=f"{entry['Name']} {entry['Version']}" ) @@ -141,7 +139,7 @@ def handleTriggerQuery(self, query): query.add(results) else: query.add(StandardItem( - id=md_id, + id=self.id, text=md_name, subtext="Enter a query to search the AUR", iconUrls=self.iconUrls, From 2bc81d40a9d072e038c0723af6d932d739801f0c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:57:24 +0200 Subject: [PATCH 149/243] [bitwarden:2.3] - Min api 2.3 --- bitwarden/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 72421d52..7cc1aec2 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -5,12 +5,12 @@ from albert import * -md_iid = '2.1' -md_version = "2.1" +md_iid = '2.3' +md_version = "2.3" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python" +md_url = "https://github.com/albertlauncher/python/tree/main/bitwarden" md_authors = ["@ovitor", "@daviddeadly", "@manuelschneid3r"] md_bin_dependencies = ["rbw"] @@ -18,12 +18,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='bw ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='bw ' + ) self.iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] def handleTriggerQuery(self, query): From bb4e4a7fc92b9eab1d86c0fd86c162962d9af4ae Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:56:30 +0200 Subject: [PATCH 150/243] [coingecko:1.2] - Min api 2.3 --- coingecko/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index bf05ae9f..d4c9de14 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -8,12 +8,12 @@ from pathlib import Path from threading import Thread, Event -md_iid = "2.0" -md_version = "1.1" +md_iid = '2.3' +md_version = "1.2" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/coingecko" +md_url = "https://github.com/albertlauncher/python/tree/main/coingecko" md_authors = "@manuelschneid3r" @@ -86,12 +86,12 @@ class Plugin(PluginInstance, IndexQueryHandler): iconUrls = [f"file:{Path(__file__).parent}/coingecko.png"] def __init__(self): + PluginInstance.__init__(self) IndexQueryHandler.__init__( - self, md_id, md_name, md_description, + self, self.id, self.name, self.description, defaultTrigger='cg ', synopsis='< symbol | name >' ) - PluginInstance.__init__(self, extensions=[self]) self.items = [] self.mtime = 0 @@ -99,7 +99,7 @@ def __init__(self): self.thread = CoinFetcherThread(self.updateIndexItems, self.coinCacheFilePath) self.thread.start() - def finalize(self): + def __del__(self): self.thread.stop() self.thread.join() From ced8305ebaf79f660386cfa60bb64db6cd9e6bf5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:55:44 +0200 Subject: [PATCH 151/243] [color:1.3] - Min api 2.3 --- color/__init__.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/color/__init__.py b/color/__init__.py index beda8f9b..4ec468ae 100644 --- a/color/__init__.py +++ b/color/__init__.py @@ -18,8 +18,8 @@ from urllib.parse import quote_plus from string import hexdigits -md_iid = '2.2' -md_version = '1.2' +md_iid = '2.3' +md_version = '1.3' md_name = 'Color' md_description = 'Display color for color codes' md_license = 'MIT' @@ -30,12 +30,11 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): - GlobalQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='#') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + GlobalQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='#' + ) def handleGlobalQuery(self, query): rank_items = [] @@ -49,7 +48,7 @@ def handleGlobalQuery(self, query): rank_items.append( RankItem( StandardItem( - id=md_id, + id=self.id, text=s, subtext="The color for this code.", iconUrls=[f"gen:?background=%23{s}"], @@ -61,10 +60,5 @@ def handleGlobalQuery(self, query): return rank_items def configWidget(self): - return [ - { - 'type': 'label', - 'text': __doc__.strip() - } - ] + return [{ 'type': 'label', 'text': __doc__.strip() }] \ No newline at end of file From 5cecc32b7c6fa05a643339e49443ea5f7510b976 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:55:21 +0200 Subject: [PATCH 152/243] [copyq:1.5] - Min api 2.3 --- copyq/__init__.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/copyq/__init__.py b/copyq/__init__.py index 18a77185..6ff3d234 100644 --- a/copyq/__init__.py +++ b/copyq/__init__.py @@ -7,8 +7,8 @@ from albert import * -md_iid = '2.0' -md_version = "1.4" +md_iid = '2.3' +md_version = "1.5" md_name = "CopyQ" md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" @@ -50,13 +50,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis="", - defaultTrigger='cq ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='cp ' + ) def handleTriggerQuery(self, query): items = [] @@ -79,7 +77,7 @@ def handleTriggerQuery(self, query): ) items.append( StandardItem( - id=md_id, + id=self.id, iconUrls=["xdg:copyq"], text=text, subtext="%s: %s" % (row, ", ".join(json_obj["mimetypes"])), From 5efb549073d995081af286f8cd483fda7ddc2754 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:54:51 +0200 Subject: [PATCH 153/243] [dice_roll:1.5] - Min api 2.3 --- dice_roll/__init__.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index b65aa82b..ae12f472 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -15,8 +15,8 @@ Example: "roll 2d6 3d8 1d20" """ -md_iid = '2.2' -md_version = "1.4" +md_iid = '2.3' +md_version = "1.5" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -132,22 +132,15 @@ class Plugin(albert.PluginInstance, albert.TriggerQueryHandler): """A plugin to roll dice""" def __init__(self): - albert.TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, + albert.PluginInstance.__init__(self) + albert.TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, synopsis="d [d ...]", - defaultTrigger="roll ", + defaultTrigger='roll ' ) - albert.PluginInstance.__init__(self, extensions=[self]) def configWidget(self): - return [ - { - 'type': 'label', - 'text': __doc__.strip(), - } - ] + return [{ 'type': 'label', 'text': __doc__.strip() }] def handleTriggerQuery(self, query: albert.Query) -> None: query_string = query.string.strip() From 60f19a772c7e65f32ffb10f2696fbec79e381f54 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:54:16 +0200 Subject: [PATCH 154/243] [docker:2.1] - Min api 2.3 --- docker/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 3a2c8a21..1e6fc8bf 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -6,12 +6,12 @@ import docker from albert import * -md_iid = "2.0" -md_version = "2.0" +md_iid = '2.3' +md_version = "2.1" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/docker" +md_url = "https://github.com/albertlauncher/python/tree/main/docker" md_authors = "@manuelschneid3r" md_bin_dependencies = "docker" md_lib_dependencies = "docker" @@ -20,13 +20,12 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): - GlobalQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='d ', - synopsis='') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + GlobalQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='d ', + synopsis='' + ) self.icon_urls_running = [f"file:{Path(__file__).parent}/running.png"] self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] self.client = None From 30b8e249fd3ef2a35e59b68f393ea4d08717fde9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:53:49 +0200 Subject: [PATCH 155/243] [duckduckgo:1.1] - Min api 2.3 --- duckduckgo/__init__.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/duckduckgo/__init__.py b/duckduckgo/__init__.py index f8865961..f04f08ea 100644 --- a/duckduckgo/__init__.py +++ b/duckduckgo/__init__.py @@ -11,8 +11,8 @@ from itertools import islice from time import sleep -md_iid = '2.0' -md_version = '1.0' +md_iid = '2.3' +md_version = '1.1' md_name = 'DuckDuckGo' md_description = 'Inline DuckDuckGo web search' md_license = "MIT" @@ -24,13 +24,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis="", - defaultTrigger='ddg ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='ddg ' + ) self.ddg = DDGS() self.iconUrls = [f"file:{Path(__file__).parent}/duckduckgo.svg"] @@ -48,7 +46,7 @@ def handleTriggerQuery(self, query): for r in islice(self.ddg.text(stripped, safesearch='off'), 10): query.add( StandardItem( - id=md_id, + id=self.id, text=r['title'], subtext=r['body'], iconUrls=self.iconUrls, From c4300ae9e09490095df422d02b25f6d6ab3ad91b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 22 Apr 2024 14:39:52 +0200 Subject: [PATCH 156/243] [emoji:2.2] - Check paste support - Min api 2.3 --- emoji/__init__.py | 56 ++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index bdf6db4e..120f1516 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -11,33 +11,28 @@ from albert import * -md_iid = '2.1' -md_version = "2.1" +md_iid = '2.3' +md_version = "2.2" md_name = "Emoji" md_description = "Find and copy emojis by name" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/emoji" +md_url = "https://github.com/albertlauncher/python/tree/main/emoji" md_authors = "@manuelschneid3r" class Plugin(PluginInstance, IndexQueryHandler): def __init__(self): - IndexQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger=':', - synopsis='') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + IndexQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger=':') self.thread = None self._use_derived = self.readConfig('use_derived', bool) if self._use_derived is None: self._use_derived = False - def finalize(self): - if self.thread.is_alive(): + def __del__(self): + if self.thread and self.thread.is_alive(): self.thread.join() @property @@ -67,7 +62,7 @@ def updateIndexItems(self): def update_index_items_task(self): - def download_file(url: str, path: str) -> bool: + def download_file(url: str, path: Path): debug(f"Downloading {url}.") headers = {'User-Agent': 'Mozilla/5.0'} # otherwise github returns html request = urllib.request.Request(url, headers=headers) @@ -79,7 +74,7 @@ def download_file(url: str, path: str) -> bool: else: raise RuntimeError(f"Failed to download {url}. Status code: {response.getcode()}") - def get_fully_qualified_emojis(cache_path: str) -> list: + def get_fully_qualified_emojis(cache_path: Path) -> list: """Returns fully qualified emoji strings""" def convert_to_unicode_char(hex_code: str): @@ -124,7 +119,7 @@ def convert_to_unicode_str(hex_codes: str): return fully_qualified - def get_annotations(cache_path: str, use_derived: bool) -> dict: + def get_annotations(cache_path: Path, use_derived: bool) -> dict: # determine locale @@ -136,7 +131,7 @@ def get_annotations(cache_path: str, use_derived: bool) -> dict: # fetch localized cldr annotations 'full' - path_full = cache_path / 'emoji_annotations_full.json' + path_full = cache_path / f'emoji_annotations_full_{lang}.json' if not path_full.is_file(): url = 'https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/' \ 'cldr-annotations-full/annotations/%s/annotations.json' % lang @@ -150,7 +145,7 @@ def get_annotations(cache_path: str, use_derived: bool) -> dict: # fetch localized cldr annotations 'derived' - path_derived = cache_path / 'emoji_annotations_derived.json' + path_derived = cache_path / f'emoji_annotations_derived_{lang}.json' if not path_derived.is_file(): url = 'https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/' \ 'cldr-annotations-derived-full/annotationsDerived/%s/annotations.json' % lang @@ -192,21 +187,28 @@ def remove_redundancy(sentences): title = ann['tts'][0] aliases = remove_redundancy([title.replace(':', '').replace(',', ''), *ann['default']]) + actions = [] + if havePasteSupport(): + actions.append( + Action( + "paste", "Copy and paste to front-most window", + lambda emj=emoji: setClipboardTextAndPaste(emj) + ) + ) + + actions.append( + Action( + "copy", "Copy to clipboard", + lambda emj=emoji: setClipboardText(emj) + ) + ) + item = StandardItem( id=emoji, text=title.capitalize(), subtext=", ".join([a.capitalize() for a in aliases]), iconUrls=[f"gen:?text={emoji}"], - actions=[ - Action( - "paste", "Copy and paste to front-most window", - lambda emj=emoji: setClipboardTextAndPaste(emj) - ), - Action( - "copy", "Copy to clipboard", - lambda emj=emoji: setClipboardText(emj) - ), - ] + actions=actions ) for alias in aliases: From 9985c076576077d4fadd1d4db2074dc6513e792b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 15 May 2024 15:30:36 +0200 Subject: [PATCH 157/243] [goldendict:1.4] - Support flatpaks and goldendict-ng - Min api 2.3 --- goldendict/__init__.py | 55 +++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index aebbc447..51eadcd6 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,41 +1,56 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017-2024 Manuel Schneider +import os +import shutil + from albert import * -md_iid = '2.0' -md_version = '1.3' +md_iid = '2.3' +md_version = '1.4' md_name = 'GoldenDict' -md_description = 'Searches in GoldenDict' +md_description = 'Quick access to GoldenDict' md_license = 'MIT' -md_url = 'https://github.com/albertlauncher/python/' +md_url = 'https://github.com/albertlauncher/python/tree/main/goldendict' md_authors = '@manuelschneid3r' -md_bin_dependencies = ['goldendict'] class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='query', - defaultTrigger='gd ') - PluginInstance.__init__(self, extensions=[self]) - self.iconUrls = ["xdg:goldendict"] - - def handleTriggerQuery(self, query: TriggerQuery) -> None: - query_str = query.string.strip() - if not query_str: - return + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='gd ' + ) + + commands = [ + '/var/lib/flatpak/exports/bin/org.goldendict.GoldenDict', # flatpak + '/var/lib/flatpak/exports/bin/io.github.xiaoyifang.goldendict_ng', # flatpak ng + 'goldendict', # native + 'goldendict-ng', # native ng + ] + + executables = [e for e in [shutil.which(c) for c in commands] if e] + + if not executables: + raise RuntimeError(f'None of the GoldenDict distributions found.') + + self.executable = executables[0] + self.iconUrls = [f'xdg:{os.path.basename(self.executable)}'] + + if len(executables) > 1: + warning(f"Multiple GoldenDict commands found: {', '.join(executables)}") + warning(f"Using {self.executable}") + def handleTriggerQuery(self, query: TriggerQuery): + q = query.string.strip() query.add( StandardItem( id=md_name, text=md_name, - subtext=f'Look up {query_str} using GoldenDict', + subtext=f"Look up '{q}' in GoldenDict", iconUrls=self.iconUrls, - actions=[Action(md_name, md_name, lambda: runDetachedProcess(['goldendict', query_str]))], + actions=[Action(md_name, md_name, lambda e=self.executable: runDetachedProcess([e, q]))], ) ) From e3ce3bebd7a9115082e53c54df03968867237b56 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 22 Apr 2024 21:50:53 +0200 Subject: [PATCH 158/243] [inhibit_sleep:1.1] - Fix type hint - Min api 2.3 --- inhibit_sleep/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/inhibit_sleep/__init__.py b/inhibit_sleep/__init__.py index 968cab1d..3d620ebb 100644 --- a/inhibit_sleep/__init__.py +++ b/inhibit_sleep/__init__.py @@ -11,8 +11,8 @@ from albert import * from subprocess import Popen, TimeoutExpired -md_iid = '2.2' -md_version = '1.0' +md_iid = '2.3' +md_version = '1.1' md_name = 'Inhibit sleep' md_description = 'Inhibit system sleep mode.' md_license = "MIT" @@ -24,12 +24,11 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): - GlobalQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='is ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + GlobalQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='is ' + ) self.proc = None def finalize(self): @@ -61,7 +60,7 @@ def configWidget(self): } ] - def handleGlobalQuery(self, query: GlobalQuery): + def handleGlobalQuery(self, query): stripped = query.string.strip().lower() if stripped in "inhibit sleep": return [ From 5514fcc65c169b3b3f5821624f25ba4a953fd69f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 22 Apr 2024 14:40:40 +0200 Subject: [PATCH 159/243] [jb:1.9] Fix type hints --- jetbrains_projects/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index cde5006b..0b62b244 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -32,7 +32,7 @@ from albert import * md_iid = '2.2' -md_version = "1.8" +md_version = "1.9" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -179,7 +179,7 @@ def __init__(self): ] self.editors = [e for e in editors if e.binary is not None] - def handleTriggerQuery(self, query: TriggerQuery): + def handleTriggerQuery(self, query: Query): editor_project_pairs = [] for editor in self.editors: projects = editor.list_projects() @@ -192,7 +192,7 @@ def handleTriggerQuery(self, query: TriggerQuery): query.add([self._make_item(editor, project, query) for editor, project in editor_project_pairs]) - def _make_item(self, editor: Editor, project: Project, query: TriggerQuery) -> Item: + def _make_item(self, editor: Editor, project: Project, query: Query) -> Item: return StandardItem( id="%s-%s-%s" % (editor.binary, project.path, project.last_opened), text=project.name, From f12b90edf31ccdaa1221637a2df42d3e1375948b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:47:54 +0200 Subject: [PATCH 160/243] [jetbrains:1.10] - Min api 2.3 --- jetbrains_projects/__init__.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 0b62b244..623f8eb1 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -31,12 +31,12 @@ from xml.etree import ElementTree from albert import * -md_iid = '2.2' -md_version = "1.9" +md_iid = '2.3' +md_version = "1.10" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/jetbrains_projects" +md_url = "https://github.com/albertlauncher/python/tree/main/jetbrains_projects" md_authors = ["@tomsquest", "@vmaerten", "@manuelschneid3r"] @@ -103,13 +103,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): executables = [] def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='project name', - defaultTrigger='jb ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='jb ' + ) plugin_dir = Path(__file__).parent editors = [ From 8a99bd52911176c2e38d8d5b27ba7b2fd8b2e5db Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:46:48 +0200 Subject: [PATCH 161/243] [kill:1.4] - Min api 2.3 --- kill/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/kill/__init__.py b/kill/__init__.py index 73e3ca41..c443d2b1 100644 --- a/kill/__init__.py +++ b/kill/__init__.py @@ -9,23 +9,22 @@ from albert import * -md_iid = '2.0' -md_version = "1.3" +md_iid = '2.3' +md_version = "1.4" md_name = "Kill Process" md_description = "Kill processes" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/kill" +md_url = "https://github.com/albertlauncher/python/tree/main/kill" md_authors = ["@Pete-Hamlin", "@BenedictDwudel", "@ManuelSchneid3r"] class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='kill ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='kill ' + ) def handleTriggerQuery(self, query): if not query.isValid: From 54e59966e8ebc12e4ac9100a0f54bcf5a2beb5c1 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:45:01 +0200 Subject: [PATCH 162/243] [locate:1.10] - Min api 2.3 --- locate/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/locate/__init__.py b/locate/__init__.py index 494e20ac..8da7db90 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -13,12 +13,12 @@ from albert import * -md_iid = '2.0' -md_version = "1.9" +md_iid = '2.3' +md_version = "1.10" md_name = "Locate" md_description = "Find and open files using locate" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/locate" +md_url = "https://github.com/albertlauncher/python/tree/main/locate" md_bin_dependencies = "locate" md_authors = "@manuelschneid3r" @@ -26,13 +26,12 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='', - defaultTrigger="'") - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger="'" + ) self.iconUrls = [ "xdg:preferences-system-search", From 37588a1a86875cfd68ea6f39ee3a491bb7c5facf Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:44:30 +0200 Subject: [PATCH 163/243] [pacman:1.10] - Min api 2.3 --- pacman/__init__.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pacman/__init__.py b/pacman/__init__.py index a47f39fd..a522c73a 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -7,12 +7,12 @@ from albert import Action, StandardItem, PluginInstance, TriggerQueryHandler, runTerminal, openUrl -md_iid = '2.0' -md_version = "1.9" +md_iid = '2.3' +md_version = "1.10" md_name = "PacMan" md_description = "Search, install and remove packages" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/pacman" +md_url = "https://github.com/albertlauncher/python/tree/main/pacman" md_authors = "@ManuelSchneid3r" md_bin_dependencies = ["pacman", "expac"] @@ -22,13 +22,12 @@ class Plugin(PluginInstance, TriggerQueryHandler): pkgs_url = "https://www.archlinux.org/packages/" def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='', - defaultTrigger='pac ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger='pac ' + ) self.iconUrls = [ "xdg:archlinux-logo", "xdg:system-software-install", @@ -41,7 +40,7 @@ def handleTriggerQuery(self, query): # Update item on empty queries if not stripped: query.add(StandardItem( - id="%s-update" % md_id, + id="%s-update" % self.id, text="Pacman package manager", subtext="Enter the package you are looking for or hit enter to update.", iconUrls=self.iconUrls, @@ -91,7 +90,7 @@ def handleTriggerQuery(self, query): actions.append(Action("proj_url", "Show project website", lambda u=pkg_purl: openUrl(u))) item = StandardItem( - id="%s_%s_%s" % (md_id, pkg_repo, pkg_name), + id="%s_%s_%s" % (self.id, pkg_repo, pkg_name), iconUrls=self.iconUrls, text="%s %s [%s]" % (pkg_name, pkg_vers, pkg_repo), subtext=f"{pkg_desc} [Installed]" if pkg_installed else f"{pkg_desc}", @@ -104,7 +103,7 @@ def handleTriggerQuery(self, query): query.add(items) else: query.add(StandardItem( - id="%s-empty" % md_id, + id="%s-empty" % self.id, text="Search on archlinux.org", subtext="No results found in the local database", iconUrls=self.iconUrls, From caede59a94f580081da5abd2aa16dd6a9ef15108 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:44:04 +0200 Subject: [PATCH 164/243] [pass:1.7] - Min api 2.3 --- pass/__init__.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pass/__init__.py b/pass/__init__.py index d0c378f7..567d1650 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -7,12 +7,12 @@ import os from albert import * -md_iid = "2.1" -md_version = "1.6" +md_iid = '2.3' +md_version = "1.7" md_name = "Pass" md_description = "Manage passwords in pass" md_license = "BSD-3" -md_url = "https://github.com/albertlauncher/python/tree/master/pass" +md_url = "https://github.com/albertlauncher/python/tree/main/pass" md_authors = ["@benedictdudel", "@maxmil", "@Pete-Hamlin"] md_bin_dependencies = ["pass"] @@ -22,15 +22,12 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): + PluginInstance.__init__(self) TriggerQueryHandler.__init__( - self, - id=md_id, - name=md_name, - description=md_description, - synopsis="", - defaultTrigger="pass ", + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger='pass ' ) - PluginInstance.__init__(self, extensions=[self]) self.iconUrls = ["xdg:dialog-password"] self._use_otp = self.readConfig("use_otp", bool) or False self._otp_glob = self.readConfig("otp_glob", str) or "*-otp.gpg" From 412f53ed943822309899aa363d6d89118aeaaeda Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:43:28 +0200 Subject: [PATCH 165/243] [pomodoro:1.7] - Min api 2.3 --- pomodoro/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 7e8ab084..4a38a8d4 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -12,12 +12,12 @@ from albert import * -md_iid = '2.2' -md_version = "1.6" +md_iid = '2.3' +md_version = "1.7" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/pomodoro" +md_url = "https://github.com/albertlauncher/python/tree/main/pomodoro" md_authors = "@manuelschneid3r" @@ -76,13 +76,12 @@ class Plugin(PluginInstance, TriggerQueryHandler): default_pomodoro_count = 4 def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='[duration [break duration [long break duration [count]]]]', - defaultTrigger='pomo ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='[duration [break duration [long break duration [count]]]]', + defaultTrigger='pomo ' + ) self.pomodoro = PomodoroTimer() self.iconUrls = [f"file:{Path(__file__).parent}/pomodoro.svg"] @@ -97,7 +96,7 @@ def configWidget(self): def handleTriggerQuery(self, query): item = StandardItem( - id=md_id, + id=self.id, iconUrls=self.iconUrls, ) From b6faf30e471c15fe3728c0afe52b1925662ae1de Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:42:46 +0200 Subject: [PATCH 166/243] [python_eval:1.6] - Min api 2.3 --- python_eval/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/python_eval/__init__.py b/python_eval/__init__.py index 9f3813a5..c054025d 100644 --- a/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -7,25 +7,24 @@ from albert import * -md_iid = '2.0' -md_version = "1.5" +md_iid = '2.3' +md_version = "1.6" md_name = "Python Eval" md_description = "Evaluate Python code" md_license = "BSD-3" -md_url = "https://github.com/albertlauncher/python/tree/master/python_eval" +md_url = "https://github.com/albertlauncher/python/tree/main/python_eval" md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='', - defaultTrigger='py ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger='py ' + ) self.iconUrls = [f"file:{Path(__file__).parent}/python.svg"] def handleTriggerQuery(self, query): @@ -39,7 +38,7 @@ def handleTriggerQuery(self, query): result_str = str(result) query.add(StandardItem( - id=md_id, + id=self.id, text=result_str, subtext=type(result).__name__, inputActionText=query.trigger + result_str, From 2b7bb22422cb96d13dd5e32cbccdd615f66b4f69 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 21 Mar 2024 18:39:57 +0100 Subject: [PATCH 167/243] [syncthing:1.0] Initial prototype --- syncthing/__init__.py | 143 ++++++++++++++++++++++++++++++++++++++++ syncthing/syncthing.svg | 26 ++++++++ 2 files changed, 169 insertions(+) create mode 100644 syncthing/__init__.py create mode 100644 syncthing/syncthing.svg diff --git a/syncthing/__init__.py b/syncthing/__init__.py new file mode 100644 index 00000000..c43fa9e2 --- /dev/null +++ b/syncthing/__init__.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Manuel Schneider + +""" +Quickly pause/resume/open/scan shares and devices. +""" + +from pathlib import Path + +from albert import * +from syncthing import Syncthing + +md_iid = '2.3' +md_version = "1.0" +md_name = "Syncthing" +md_description = "Trigger basic syncthing actions." +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/main/syncthing" +md_authors = "@manuelschneid3r" +md_lib_dependencies = "syncthing" + + +class Plugin(PluginInstance, GlobalQueryHandler): + + config_key = 'syncthing_api_key' + + def __init__(self): + PluginInstance.__init__(self) + GlobalQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger='st ') + self.iconUrls = ["xdg:syncthing", f"file:{Path(__file__).parent}/syncthing.svg"] + self._api_key = self.readConfig(self.config_key, str) + if self._api_key: + self.st = Syncthing(self._api_key) + + @property + def api_key(self) -> str: + return self._api_key + + @api_key.setter + def api_key(self, value: str): + if self._api_key != value: + self._api_key = value + self.writeConfig(self.config_key, value) + self.st = Syncthing(self._api_key) + + + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + }, + { + 'type': 'lineedit', + 'property': 'api_key', + 'label': 'API key', + 'widget_properties': {'tooltip': 'You can find the API key using the web frontend.'} + } + ] + + def handleGlobalQuery(self, query): + + results = [] + + if self.st: + + config = self.st.system.config() + + devices = dict() + for d in config['devices']: + if not d['name']: + d['name'] = d['deviceID'] + d['_shared_folders'] = {} + devices[d['deviceID']] = d + + folders = dict() + for f in config['folders']: + if not f['label']: + f['label'] = f['id'] + for d in f['devices']: + devices[d['deviceID']]['_shared_folders'][f['id']] = f + folders[f['id']] = f + + matcher = Matcher(query.string) + + # create device items + for id, d in devices.items(): + device_name = d['name'] + + if match := matcher.match(device_name): + device_folders = ", ".join([f['label'] for f in d['_shared_folders'].values()]) + + actions = [] + if d['paused']: + actions.append( + Action("resume", "Resume synchronization", + lambda did=id: self.st.system.resume(did)) + ) + else: + actions.append( + Action("pause", "Pause synchronization", + lambda did=id: self.st.system.pause(did)) + ) + + results.append( + RankItem( + StandardItem( + id=id, + text=f"{device_name}", + subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " + f"Shared: {device_folders if device_folders else 'Nothing'}.", + iconUrls=self.iconUrls, + actions=actions + ), + match.score + ) + ) + + # create folder items + for id, f in folders.items(): + folder_name = f['label'] + if match := matcher.match(folder_name): + folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) + results.append( + RankItem( + StandardItem( + id=id, + text=folder_name, + subtext=f"Syncthing folder {f['path']}. " + f"Shared with {folders_devices if folders_devices else 'nobody'}.", + iconUrls=self.iconUrls, + actions=[ + Action("scan", "Scan the folder", + lambda fid=id: self.st.database.scan(fid)), + Action("open", "Open this folder in file browser", + lambda p=f['path']: openUrl(f'file://{p}')) + ] + ), + match.score + ) + ) + + return results diff --git a/syncthing/syncthing.svg b/syncthing/syncthing.svg new file mode 100644 index 00000000..ce92210f --- /dev/null +++ b/syncthing/syncthing.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + From 8df7f609cbbb0ed1c7658f22588967660b005933 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 4 Jun 2024 12:16:08 +0200 Subject: [PATCH 168/243] [timer] Move to archive --- {timer => .archive/timer}/__init__.py | 6 +++--- timer/time.svg | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) rename {timer => .archive/timer}/__init__.py (96%) delete mode 100644 timer/time.svg diff --git a/timer/__init__.py b/.archive/timer/__init__.py similarity index 96% rename from timer/__init__.py rename to .archive/timer/__init__.py index 0a577485..5a22e160 100644 --- a/timer/__init__.py +++ b/.archive/timer/__init__.py @@ -19,12 +19,12 @@ from albert import * -md_iid = '2.2' +md_iid = '2.3' md_version = "1.8" md_name = "Timer" md_description = "Set up timers" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/timer" +md_url = "https://github.com/albertlauncher/python/tree/main/timer" md_authors = ["@manuelschneid3r", "@googol42"] @@ -48,7 +48,7 @@ def __init__(self): description=md_description, synopsis='[[hrs:]mins:]secs [name]', defaultTrigger='timer ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) self.iconUrls = [f"file:{Path(__file__).parent}/time.svg"] self.soundPath = Path(__file__).parent / "bing.wav" self.timers = [] diff --git a/timer/time.svg b/timer/time.svg deleted file mode 100644 index f6cc4d0b..00000000 --- a/timer/time.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 41f575bc672cadca75cd9c3183199500898de03d Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 22 Apr 2024 14:41:09 +0200 Subject: [PATCH 169/243] [tr:1.6] Check paste support --- translators/__init__.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 36b563eb..5ab65cc8 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -13,7 +13,7 @@ import translators as ts md_iid = '2.2' -md_version = "1.5" +md_version = "1.6" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "MIT" @@ -112,19 +112,26 @@ def handleTriggerQuery(self, query): to_language=dst, timeout=5) + actions = [] + if havePasteSupport(): + actions.append( + Action( + "paste", "Copy to clipboard and paste to front-most window", + lambda t=translation: setClipboardTextAndPaste(t) + ) + ) + + actions.append( + Action("copy", "Copy to clipboard", + lambda t=translation: setClipboardText(t)) + ) + query.add(StandardItem( id=md_id, text=translation, subtext=f"{src.upper()} > {dst.upper()}", iconUrls=self.iconUrls, - actions=[ - Action( - "paste", "Copy to clipboard and paste to front-most window", - lambda t=translation: setClipboardTextAndPaste(t) - ), - Action("copy", "Copy to clipboard", - lambda t=translation: setClipboardText(t)) - ] + actions=actions )) except Exception as e: From 190f5cf6cfec5970c8bdcbab73ba8e3d956d0e45 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:29:17 +0200 Subject: [PATCH 170/243] [translators:1.7] - Min api 2.3 --- translators/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 5ab65cc8..6fb04efc 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -12,8 +12,8 @@ from albert import * import translators as ts -md_iid = '2.2' -md_version = "1.6" +md_iid = '2.3' +md_version = "1.7" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "MIT" @@ -25,13 +25,12 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis="[[from] to] text", - defaultTrigger='tr ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis="[[from] to] text", + defaultTrigger='tr ' + ) self.iconUrls = [f"file:{Path(__file__).parent}/google_translate.png"] self._translator = self.readConfig('translator', str) @@ -127,7 +126,7 @@ def handleTriggerQuery(self, query): ) query.add(StandardItem( - id=md_id, + id=self.id, text=translation, subtext=f"{src.upper()} > {dst.upper()}", iconUrls=self.iconUrls, @@ -137,7 +136,7 @@ def handleTriggerQuery(self, query): except Exception as e: query.add(StandardItem( - id=md_id, + id=self.id, text="Error", subtext=str(e), iconUrls=self.iconUrls From 6ba92ba4837b67f3a1a908179948c1324381edc6 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:26:19 +0200 Subject: [PATCH 171/243] [virtualbox:1.7] - Min api 2.3 --- virtualbox/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/virtualbox/__init__.py b/virtualbox/__init__.py index 3b908840..8ba78272 100644 --- a/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -11,12 +11,12 @@ from albert import * -md_iid = '2.2' -md_version = "1.6" +md_iid = '2.3' +md_version = "1.7" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/virtualbox" +md_url = "https://github.com/albertlauncher/python/tree/main/virtualbox" md_authors = "@manuelschneid3r" md_lib_dependencies = ['virtualbox'] @@ -63,13 +63,12 @@ def pauseVm(vm): class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='', - defaultTrigger='vbox ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger='vbox ' + ) self.iconUrls = ["xdg:virtualbox", ":unknown"] def configWidget(self): From 1c98aec4c5fad2bb213198a08dca5049756bff46 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:24:04 +0200 Subject: [PATCH 172/243] [vpn:1.5] - Min api 2.3 --- vpn/__init__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/vpn/__init__.py b/vpn/__init__.py index 82177092..ef82febd 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -8,9 +8,8 @@ from albert import * -md_iid = '2.0' -md_version = "1.4" -md_id = "vpn" +md_iid = '2.3' +md_version = "1.5" md_name = "VPN" md_description = "Manage NetworkManager VPN connections" md_license = "MIT" @@ -24,12 +23,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): VPNConnection = namedtuple('VPNConnection', ['name', 'connected']) def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='vpn ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='vpn ' + ) def getVPNConnections(self): consStr = subprocess.check_output( From 27c69f2d2d18aebde477da7edb2f7eac49ec072f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:10:23 +0200 Subject: [PATCH 173/243] [wikipedia:2.0] - Min api 2.3 - Add fuzzy search support --- wikipedia/__init__.py | 92 +++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index 433f85c2..2305117a 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -10,27 +10,15 @@ import json from pathlib import Path -md_iid = '2.0' -md_version = "1.10" +md_iid = '2.3' +md_version = "2.0" md_name = "Wikipedia" md_description = "Search Wikipedia articles" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/tree/master/wikipedia" +md_url = "https://github.com/albertlauncher/python/tree/main/wikipedia" md_authors = "@manuelschneid3r" -class WikiFallbackHandler(FallbackHandler): - def __init__(self): - FallbackHandler.__init__(self, - id=f"{md_id}_fb", - name=f"{md_name} fallback", - description="Wikipedia fallback search") - - def fallbacks(self, query_string): - stripped = query_string.strip() - return [Plugin.createFallbackItem(query_string)] if stripped else [] - - class Plugin(PluginInstance, TriggerQueryHandler): baseurl = 'https://en.wikipedia.org/w/api.php' @@ -40,13 +28,22 @@ class Plugin(PluginInstance, TriggerQueryHandler): iconUrls = [f"file:{Path(__file__).parent}/wikipedia.png"] def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='wiki ') - self.wiki_fb = WikiFallbackHandler() - PluginInstance.__init__(self, extensions=[self, self.wiki_fb]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='wiki ', supportsFuzzyMatching=True + ) + self.fuzzy = False + + self.local_lang_code = getdefaultlocale()[0] + if self.local_lang_code: + self.local_lang_code = self.local_lang_code[0:2] + else: + self.local_lang_code = 'en' + warning("Failed getting language code. Using 'en'.") + + self.fbh = FBH(self) + self.registerExtension(self.fbh) params = { 'action': 'query', @@ -56,13 +53,6 @@ def __init__(self): 'format': 'json' } - Plugin.local_lang_code = getdefaultlocale()[0] - if Plugin.local_lang_code: - Plugin.local_lang_code = Plugin.local_lang_code[0:2] - else: - Plugin.local_lang_code = 'en' - warning("Failed getting language code. Using 'en'.") - get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) req = request.Request(get_url, headers={'User-Agent': self.user_agent}) try: @@ -76,9 +66,15 @@ def __init__(self): except Exception as error: warning('Error getting languages (%s). Defaulting to EN.' % error) + def __del__(self): + self.deregisterExtension(self.fbh) + + def setFuzzyMatching(self, enabled: bool): + self.fuzzy = enabled + def handleTriggerQuery(self, query): - stripped = query.string.strip() - if stripped: + if stripped := query.string.strip(): + # avoid rate limiting for _ in range(50): sleep(0.01) @@ -92,7 +88,8 @@ def handleTriggerQuery(self, query): 'search': stripped, 'limit': self.limit, 'utf8': 1, - 'format': 'json' + 'format': 'json', + 'profile': 'fuzzy' if self.fuzzy else 'normal' } get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) req = request.Request(get_url, headers={'User-Agent': self.user_agent}) @@ -106,7 +103,7 @@ def handleTriggerQuery(self, query): url = data[3][i] results.append( StandardItem( - id=md_id, + id=self.id, text=title, subtext=summary if summary else url, iconUrls=self.iconUrls, @@ -118,28 +115,37 @@ def handleTriggerQuery(self, query): ) if not results: - results.append(Plugin.createFallbackItem(stripped)) + results.append(self.createFallbackItem(stripped)) query.add(results) else: query.add( StandardItem( - id=md_id, - text=md_name, + id=self.id, + text=self.name, subtext="Enter a query to search on Wikipedia", iconUrls=self.iconUrls ) ) - @staticmethod - def createFallbackItem(query_string): + def createFallbackItem(self, q: str) -> Item: return StandardItem( - id=md_id, - text=md_name, - subtext="Search '%s' on Wiki" % query_string, - iconUrls=Plugin.iconUrls, + id=self.id, + text=self.name, + subtext="Search '%s' on Wikipedia" % q, + iconUrls=self.iconUrls, actions=[ Action("wiki_search", "Search on Wikipedia", - lambda url=Plugin.searchUrl % (Plugin.local_lang_code, query_string): openUrl(url)) + lambda url=self.searchUrl % (self.local_lang_code, q): openUrl(url)) ] ) + + +class FBH(FallbackHandler): + + def __init__(self, p: Plugin): + FallbackHandler.__init__(self, p.id + 'fb', p.name, p.description) + self.plugin = p + + def fallbacks(self, q :str): + return [self.plugin.createFallbackItem(q)] From 1b7fa593946f0b9c6ef02b8255323a60550a2e96 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sat, 22 Jun 2024 01:16:17 +0200 Subject: [PATCH 174/243] [zeal:2.0] - Min api 2.3 - Add fallback extension --- zeal/__init__.py | 58 +++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/zeal/__init__.py b/zeal/__init__.py index 1201d130..89e6bd63 100644 --- a/zeal/__init__.py +++ b/zeal/__init__.py @@ -3,34 +3,52 @@ from albert import * -md_iid = '2.0' -md_version = '1.2' +md_iid = '2.3' +md_version = '2.0' md_name = 'Zeal' md_description = 'Search in Zeal docs' md_license = "MIT" -md_url = 'https://github.com/albertlauncher/python/zeal' +md_url = 'https://github.com/albertlauncher/python/tree/main/zeal' md_authors = "@manuelschneid3r" md_bin_dependencies = ['zeal'] +class FBH(FallbackHandler): + def fallbacks(self, s): + return [Plugin.createItem(s)] if s else [] + + class Plugin(PluginInstance, TriggerQueryHandler): + def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - defaultTrigger='z ') - PluginInstance.__init__(self, extensions=[self]) + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + defaultTrigger='z ' + ) + self.fbh = FBH( + id=self.id + 'fb', + name=self.name, + description=self.description + ) + + self.registerExtension(self.fbh) + + def __del__(self): + self.deregisterExtension(self.fbh) def handleTriggerQuery(self, query): - stripped = query.string.strip() - if stripped: - query.add( - StandardItem( - id=md_name, - text=md_name, - subtext=f"Search '{stripped}' in Zeal", - iconUrls=["xdg:zeal"], - actions=[Action("zeal", "Search in Zeal", lambda s=stripped: runDetachedProcess(['zeal', s]))] - ) - ) + if stripped := query.string.strip(): + query.add(self.createItem(stripped)) + + @staticmethod + def createItem(query: str) -> Item: + return StandardItem( + id=md_name, + text=md_name, + subtext=f"Search '{query}' in Zeal", + iconUrls=["xdg:zeal"], + actions=[Action("zeal", "Search in Zeal", + lambda q=query: runDetachedProcess(['zeal', q]))] + ) + From 7a724d7620827e7a71d1b78892eee21b33da1a99 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 1 Jul 2024 21:54:29 +0200 Subject: [PATCH 175/243] [GoldenDict:1.5] Remove breaking type hints --- goldendict/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index 51eadcd6..d60298eb 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -7,7 +7,7 @@ from albert import * md_iid = '2.3' -md_version = '1.4' +md_version = '1.5' md_name = 'GoldenDict' md_description = 'Quick access to GoldenDict' md_license = 'MIT' @@ -43,7 +43,7 @@ def __init__(self): warning(f"Multiple GoldenDict commands found: {', '.join(executables)}") warning(f"Using {self.executable}") - def handleTriggerQuery(self, query: TriggerQuery): + def handleTriggerQuery(self, query): q = query.string.strip() query.add( StandardItem( From beb090365e4de1e51bf225ab9cdd6f4e3ec892cc Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 17 Jul 2024 15:28:20 +0200 Subject: [PATCH 176/243] [CoinGecko:1.3] Use Matcher --- coingecko/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index d4c9de14..58fdab66 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -9,7 +9,7 @@ from threading import Thread, Event md_iid = '2.3' -md_version = "1.2" +md_version = "1.3" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "MIT" @@ -128,7 +128,7 @@ def updateIndexItems(self): # override default trigger handling to sort by rank def handleTriggerQuery(self, query): - qs = query.string.strip().lower() + m = Matcher(query.string) for item in self.items: - if qs in item.name.lower() or qs in item.symbol.lower(): + if m.match(item.symbol) or m.match(item.name): query.add(item) From 54c5eebe8a37bcb6f204b7c2d34cec3736c1f1ad Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 7 Aug 2024 14:04:52 +0200 Subject: [PATCH 177/243] [tex_to_unicode:1.3] Port to v2.3 Author is okay with license change. See https://github.com/orgs/albertlauncher/discussions/1391#discussioncomment-10263863 --- .archive/tex_to_unicode/tex.png | Bin 9268 -> 0 bytes .../__init__.py | 31 ++++++++---------- tex_to_unicode/tex.svg | 4 +++ 3 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 .archive/tex_to_unicode/tex.png rename {.archive/tex_to_unicode => tex_to_unicode}/__init__.py (71%) create mode 100644 tex_to_unicode/tex.svg diff --git a/.archive/tex_to_unicode/tex.png b/.archive/tex_to_unicode/tex.png deleted file mode 100644 index 49379b81f05387b988f68864e06b4dfe3d55f841..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9268 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 W3tuFJQegG~0000 Item: + def _create_item(self, text: str, subtext: str, can_copy: bool): actions = [] if can_copy: actions.append( @@ -43,14 +38,14 @@ def _create_item(self, text: str, subtext: str, can_copy: bool) -> Item: ) ) return StandardItem( - id=md_id, + id=self.id, text=text, subtext=subtext, iconUrls=self.iconUrls, actions=actions, ) - def handleTriggerQuery(self, query: TriggerQuery) -> None: + def handleTriggerQuery(self, query): stripped = query.string.strip() if not stripped: diff --git a/tex_to_unicode/tex.svg b/tex_to_unicode/tex.svg new file mode 100644 index 00000000..86ddf19b --- /dev/null +++ b/tex_to_unicode/tex.svg @@ -0,0 +1,4 @@ + + + + From bb60bbe59920a01d0ddc7627a6c1021d4f3a18db Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 12 Aug 2024 02:02:10 +0200 Subject: [PATCH 178/243] [bitwarden:2.4] Drop deprecated parameters --- bitwarden/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 7cc1aec2..c40057c9 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = '2.3' -md_version = "2.3" +md_iid = "2.3" +md_version = "2.4" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "MIT" @@ -156,7 +156,4 @@ def _code_to_clipboard(self, item): def _edit_entry(self, item): id = item["id"] - runTerminal( - script=f"rbw edit {id}", - close_on_exit=True - ) + runTerminal(script=f"rbw edit {id}") From 2d2c9eb42d89f1b81f045d31f16097b409a636e4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 12 Aug 2024 02:13:28 +0200 Subject: [PATCH 179/243] [docker:2.2] Drop deprecated parameters --- docker/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 1e6fc8bf..99c60200 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -6,8 +6,8 @@ import docker from albert import * -md_iid = '2.3' -md_version = "2.1" +md_iid = "2.3" +md_version = "2.2" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "MIT" @@ -59,7 +59,7 @@ def handleGlobalQuery(self, query): actions = [Action("start", "Start container", lambda c=container: c.start())] actions.extend([ Action("logs", "Logs", - lambda c=container.id: runTerminal("docker logs -f %s" % c, close_on_exit=False)), + lambda c=container.id: runTerminal("docker logs -f %s ; exec $SHELL" % c)), Action("remove", "Remove (forced, with volumes)", lambda c=container: c.remove(v=True, force=True)), Action("copy-id", "Copy id to clipboard", @@ -86,9 +86,11 @@ def handleGlobalQuery(self, query): text=", ".join(image.tags), subtext="Image: %s" % image.id, iconUrls=self.icon_urls_stopped, - actions=[Action("run", "Run with command: %s" % query.string, - lambda i=image, s=query.string: client.containers.run(i, s)), - Action("rmi", "Remove image", lambda i=image: i.remove())] + actions=[ + # Action("run", "Run with command: %s" % query.string, + # lambda i=image, s=query.string: client.containers.run(i, s)), + Action("rmi", "Remove image", lambda i=image: i.remove()) + ] ), score=len(query.string)/len(tag) )) From 2e300d120e48003125061c78b9b17dba403373a2 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 12 Aug 2024 02:17:15 +0200 Subject: [PATCH 180/243] [aur:1.11] Drop deprecated parameters --- aur/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/aur/__init__.py b/aur/__init__.py index 7fa763a1..69ad787c 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -15,8 +15,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.10" +md_iid = "2.3" +md_version = "1.11" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "MIT" @@ -113,16 +113,14 @@ def handleTriggerQuery(self, query): id="inst", text="Install using %s" % pacman, callable=lambda n=name: runTerminal( - script=self.install_cmdline % n, - close_on_exit=False + script=self.install_cmdline % n + " ; exec $SHELL" ) )) actions.append(Action( id="instnc", text="Install using %s (noconfirm)" % pacman, callable=lambda n=name: runTerminal( - script=self.install_cmdline % n + " --noconfirm", - close_on_exit=False + script=self.install_cmdline % n + " --noconfirm ; exec $SHELL" ) )) From bdaf3ccb133b88f1929da73926a88392e0214845 Mon Sep 17 00:00:00 2001 From: dev2a Date: Wed, 14 Aug 2024 22:37:54 +0200 Subject: [PATCH 181/243] [jetbrains:2.0] - Add Aqua and Writerside - Look for project name to search in projects - Use albert.Matcher Co-authored-by: @manuelschneid3r --- jetbrains_projects/__init__.py | 35 ++++- jetbrains_projects/icons/aqua.svg | 171 ++++++++++++++++++++++++ jetbrains_projects/icons/writerside.svg | 9 ++ 3 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 jetbrains_projects/icons/aqua.svg create mode 100644 jetbrains_projects/icons/writerside.svg diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 623f8eb1..39d69f0c 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -5,6 +5,7 @@ """ This plugin allows you to quickly open projects of the Jetbrains IDEs +- Aqua - Android Studio - CLion - DataGrip @@ -15,7 +16,8 @@ - PyCharm - Rider - RubyMine -- WebStorm. +- WebStorm +- Writerside. Note that for this plugin to find the IDEs, a commandline launcher in $PATH is required. Open the IDE and click Tools -> Create Command-line Launcher to add one. @@ -32,12 +34,12 @@ from albert import * md_iid = '2.3' -md_version = "1.10" +md_version = "2.0" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/jetbrains_projects" -md_authors = ["@tomsquest", "@vmaerten", "@manuelschneid3r"] +md_authors = ["@tomsquest", "@vmaerten", "@manuelschneid3r", "@d3v2a"] @dataclass @@ -84,15 +86,22 @@ def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: projects = [] for entry in entries: - project_path = entry.attrib["key"].replace("$USER_HOME$", str(Path.home())) - + project_path = entry.attrib["key"] + project_path = project_path.replace("$USER_HOME$", str(Path.home())) + project_name = Path(project_path).name + files = Path(project_path + "/.idea").glob("*.iml") tag_opened = entry.find(".//option[@name='projectOpenTimestamp']") last_opened = tag_opened.attrib["value"] if tag_opened is not None and "value" in tag_opened.attrib else None if project_path and last_opened: projects.append( - Project(name=Path(project_path).name, path=project_path, last_opened=int(last_opened)) + Project(name=project_name, path=project_path, last_opened=int(last_opened)) ) + for file in files: + name = file.name.replace(".iml", "") + if name != project_name: + projects.append(Project(name=name, path=project_path, last_opened=int(last_opened))) + return projects except (ElementTree.ParseError, FileNotFoundError): return [] @@ -117,6 +126,11 @@ def __init__(self): config_dir_prefix="Google/AndroidStudio", binaries=["studio", "androidstudio", "android-studio", "android-studio-canary", "jdk-android-studio", "android-studio-system-jdk"]), + Editor( + name="Aqua", + icon=plugin_dir / "icons" / "aqua.svg", + config_dir_prefix="JetBrains/Aqua", + binaries=["aqua", "aqua-eap"]), Editor( name="CLion", icon=plugin_dir / "icons" / "clion.svg", @@ -174,15 +188,22 @@ def __init__(self): icon=plugin_dir / "icons" / "rustrover.svg", config_dir_prefix="JetBrains/RustRover", binaries=["rustrover", "rustrover-eap"]), + Editor( + name="Writerside", + icon=plugin_dir / "icons" / "writerside.svg", + config_dir_prefix="JetBrains/Writerside", + binaries=["writerside", "writerside-eap"]), ] self.editors = [e for e in editors if e.binary is not None] def handleTriggerQuery(self, query: Query): editor_project_pairs = [] + + m = Matcher(query.string) for editor in self.editors: projects = editor.list_projects() projects = [p for p in projects if Path(p.path).exists()] - projects = [p for p in projects if query.string.lower() in p.name.lower()] + projects = [p for p in projects if m.match(p.name) or m.match(p.path)] editor_project_pairs.extend([(editor, p) for p in projects]) # sort by last opened diff --git a/jetbrains_projects/icons/aqua.svg b/jetbrains_projects/icons/aqua.svg new file mode 100644 index 00000000..a2d7e161 --- /dev/null +++ b/jetbrains_projects/icons/aqua.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetbrains_projects/icons/writerside.svg b/jetbrains_projects/icons/writerside.svg new file mode 100644 index 00000000..087eed26 --- /dev/null +++ b/jetbrains_projects/icons/writerside.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 109a5cb34a31e47828223b987ac13ab1a8d3c216 Mon Sep 17 00:00:00 2001 From: Pete Hamlin Date: Wed, 14 Aug 2024 21:00:04 +0000 Subject: [PATCH 182/243] [unit_converter:1.6] Port to API v2 --- .../__init__.py | 103 +++++++++--------- .../icons/currency.svg | 0 .../icons/current.svg | 0 .../icons/length.svg | 0 .../icons/lengthtime.svg | 0 .../icons/luminosity.svg | 0 .../icons/mass.svg | 0 .../icons/printing_unit.svg | 0 .../icons/substance.svg | 0 .../icons/temperature.svg | 0 .../icons/time.svg | 0 .../icons/unit_converter.svg | 0 12 files changed, 49 insertions(+), 54 deletions(-) rename {.archive/unit_converter => unit_converter}/__init__.py (86%) rename {.archive/unit_converter => unit_converter}/icons/currency.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/current.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/length.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/lengthtime.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/luminosity.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/mass.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/printing_unit.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/substance.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/temperature.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/time.svg (100%) rename {.archive/unit_converter => unit_converter}/icons/unit_converter.svg (100%) diff --git a/.archive/unit_converter/__init__.py b/unit_converter/__init__.py similarity index 86% rename from .archive/unit_converter/__init__.py rename to unit_converter/__init__.py index 462dad4f..66b1c05a 100644 --- a/.archive/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -24,19 +24,18 @@ from urllib.error import URLError from urllib.request import urlopen -import albert import inflect import pint +from albert import * - -md_iid = '2.0' -md_version = "1.4" +md_iid = "2.3" +md_version = "1.6" md_name = "Unit Converter" md_description = "Convert between units" md_license = "MIT" md_url = "https://github.com/albertlauncher/python" md_lib_dependencies = ["pint", "inflect"] -md_maintainers = "@DenverCoder1" +md_authors = ["@DenverCoder1", "@Pete-Hamlin"] class ConversionResult: @@ -250,12 +249,12 @@ def _get_currencies(self) -> dict[str, float]: with urlopen(self.API_URL) as response: data = json.loads(response.read().decode("utf-8")) if not data or "rates" not in data: - albert.info("No currencies found") + info("No currencies found") return {} - albert.info(f'Currencies updated') + info(f"Currencies updated") return data["rates"] except URLError as error: - albert.warning(f"Error getting currencies: {error}") + warning(f"Error getting currencies: {error}") return {} def get_currency(self, currency: str) -> str | None: @@ -309,14 +308,9 @@ def convert(self, amount: float, from_unit: str, to_unit: str) -> ConversionResu ) -class Plugin(albert.TriggerQueryHandler): +class Plugin(PluginInstance, GlobalQueryHandler): """The plugin class""" - unit_convert_regex = re.compile( - r"(?P-?\d+\.?\d*)\s?(?P.*)\s(?:to|in)\s(?P.*)", - re.I, - ) - config: dict[str, Any] = { # Maximum number of decimal places for precision "precision": 12, @@ -351,47 +345,50 @@ class Plugin(albert.TriggerQueryHandler): }, } - def initialize(self): + def __init__(self): + PluginInstance.__init__(self) + GlobalQueryHandler.__init__( + self, + id=self.id, + name=self.name, + description=self.description, + synopsis=" to ", + defaultTrigger="convert ", + ) + + self.unit_convert_regex = re.compile( + r"(?P-?\d+\.?\d*)\s?(?P.*)\s(?:to|in)\s(?P.*)", + re.I, + ) self.unit_converter = StandardUnitConverter() self.currency_converter = CurrencyConverter() - def id(self) -> str: - return md_id + def handleTriggerQuery(self, query: Query) -> None: + if query_string := query.string.strip(): + items = self.match_query(query_string) + query.add(items) - def name(self) -> str: - return md_name + def handleGlobalQuery(self, query): + return [RankItem(item=item, score=1) for item in self.match_query(query.string.strip())] - def description(self) -> str: - return md_description - - def synopsis(self) -> str: - return " to " - - def defaultTrigger(self) -> str: - return "convert " - - def handleTriggerQuery(self, query: albert.TriggerQuery) -> None: - query_string = query.string.strip() + def match_query(self, query_string: str): match = self.unit_convert_regex.fullmatch(query_string) if match: - albert.info(f"Matched {query_string}") try: - items = self._get_items( + return self._get_items( float(match.group("from_amount")), match.group("from_unit").strip(), match.group("to_unit").strip(), ) - query.add(items) except Exception as error: - albert.warning(f"Error: {error}") - tb = "".join( - traceback.format_exception(error.__class__, error, error.__traceback__) - ) - albert.warning(tb) - albert.info("Something went wrong. Make sure you're using the correct format.") + warning(f"Error: {error}") + tb = "".join(traceback.format_exception(error.__class__, error, error.__traceback__)) + warning(tb) + info("Something went wrong. Make sure you're using the correct format.") + return [] - def _create_item(self, text: str, subtext: str, icon: str = "") -> albert.Item: - """Create an albert.Item from a text and subtext + def _create_item(self, text: str, subtext: str, icon: str = "") -> Item: + """Create an Item from a text and subtext Args: text (str): The text to display @@ -399,22 +396,22 @@ def _create_item(self, text: str, subtext: str, icon: str = "") -> albert.Item: icon (Optional[str]): The icon to display. If not specified, the default icon will be used Returns: - albert.Item: The item to be added to the list of results + Item: The item to be added to the list of results """ icon_path = Path(__file__).parent / "icons" / icon if not icon or not icon_path.exists(): - albert.warning(f"Icon {icon} does not exist") + warning(f"Icon {icon} does not exist") icon_path = Path(__file__).parent / "icons" / "unit_converter.svg" - return albert.StandardItem( + return StandardItem( id=str(icon_path), iconUrls=["file:" + str(icon_path)], text=text, subtext=subtext, actions=[ - albert.Action( + Action( id="copy", text="Copy result to clipboard", - callable=lambda: albert.setClipboardText(text=text), + callable=lambda: setClipboardText(text=text), ) ], ) @@ -436,7 +433,7 @@ def _get_converter(self, from_unit: str, to_unit: str) -> UnitConverter: return self.currency_converter return self.unit_converter - def _get_items(self, amount: float, from_unit: str, to_unit: str) -> list[albert.Item]: + def _get_items(self, amount: float, from_unit: str, to_unit: str) -> list[Item]: """Generate the Albert items to display for the query Args: @@ -445,7 +442,7 @@ def _get_items(self, amount: float, from_unit: str, to_unit: str) -> list[albert to_unit (str): The unit to convert to Returns: - List[albert.Item]: The list of items to display + List[Item]: The list of items to display """ try: converter = self._get_converter(from_unit, to_unit) @@ -459,13 +456,11 @@ def _get_items(self, amount: float, from_unit: str, to_unit: str) -> list[albert ) ] except pint.errors.DimensionalityError as e: - albert.warning(f"DimensionalityError: {e}") - return [ - self._create_item(f"Unable to convert {amount} {from_unit} to {to_unit}", str(e)) - ] + warning(f"DimensionalityError: {e}") + return [self._create_item(f"Unable to convert {amount} {from_unit} to {to_unit}", str(e))] except pint.errors.UndefinedUnitError as e: - albert.warning(f"UndefinedUnitError: {e}") + warning(f"UndefinedUnitError: {e}") return [] except UnknownCurrencyError as e: - albert.warning(f"UnknownCurrencyError: {e}") + warning(f"UnknownCurrencyError: {e}") return [] diff --git a/.archive/unit_converter/icons/currency.svg b/unit_converter/icons/currency.svg similarity index 100% rename from .archive/unit_converter/icons/currency.svg rename to unit_converter/icons/currency.svg diff --git a/.archive/unit_converter/icons/current.svg b/unit_converter/icons/current.svg similarity index 100% rename from .archive/unit_converter/icons/current.svg rename to unit_converter/icons/current.svg diff --git a/.archive/unit_converter/icons/length.svg b/unit_converter/icons/length.svg similarity index 100% rename from .archive/unit_converter/icons/length.svg rename to unit_converter/icons/length.svg diff --git a/.archive/unit_converter/icons/lengthtime.svg b/unit_converter/icons/lengthtime.svg similarity index 100% rename from .archive/unit_converter/icons/lengthtime.svg rename to unit_converter/icons/lengthtime.svg diff --git a/.archive/unit_converter/icons/luminosity.svg b/unit_converter/icons/luminosity.svg similarity index 100% rename from .archive/unit_converter/icons/luminosity.svg rename to unit_converter/icons/luminosity.svg diff --git a/.archive/unit_converter/icons/mass.svg b/unit_converter/icons/mass.svg similarity index 100% rename from .archive/unit_converter/icons/mass.svg rename to unit_converter/icons/mass.svg diff --git a/.archive/unit_converter/icons/printing_unit.svg b/unit_converter/icons/printing_unit.svg similarity index 100% rename from .archive/unit_converter/icons/printing_unit.svg rename to unit_converter/icons/printing_unit.svg diff --git a/.archive/unit_converter/icons/substance.svg b/unit_converter/icons/substance.svg similarity index 100% rename from .archive/unit_converter/icons/substance.svg rename to unit_converter/icons/substance.svg diff --git a/.archive/unit_converter/icons/temperature.svg b/unit_converter/icons/temperature.svg similarity index 100% rename from .archive/unit_converter/icons/temperature.svg rename to unit_converter/icons/temperature.svg diff --git a/.archive/unit_converter/icons/time.svg b/unit_converter/icons/time.svg similarity index 100% rename from .archive/unit_converter/icons/time.svg rename to unit_converter/icons/time.svg diff --git a/.archive/unit_converter/icons/unit_converter.svg b/unit_converter/icons/unit_converter.svg similarity index 100% rename from .archive/unit_converter/icons/unit_converter.svg rename to unit_converter/icons/unit_converter.svg From 3d68043a559515e1fe84e15952cc98e2b41d25dd Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 08:42:22 +0200 Subject: [PATCH 183/243] [docker:3.0] Revert to trigger query handling Global query handler not applicable, queries take seconds sometimes --- docker/__init__.py | 66 ++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 99c60200..9835e46f 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -7,7 +7,7 @@ from albert import * md_iid = "2.3" -md_version = "2.2" +md_version = "3.0" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "MIT" @@ -17,11 +17,12 @@ md_lib_dependencies = "docker" -class Plugin(PluginInstance, GlobalQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): + # Global query handler not applicable, queries take seconds sometimes def __init__(self): PluginInstance.__init__(self) - GlobalQueryHandler.__init__( + TriggerQueryHandler.__init__( self, self.id, self.name, self.description, defaultTrigger='d ', synopsis='' @@ -30,23 +31,20 @@ def __init__(self): self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] self.client = None - def handleGlobalQuery(self, query): - rank_items = [] + def handleTriggerQuery(self, query): + items = [] if not self.client: try: self.client = docker.from_env() except Exception as e: - rank_items.append(RankItem( - item=StandardItem( - id='except', - text="Failed starting docker client", - subtext=str(e), - iconUrls=self.icon_urls_running, - ), - score=1.0 + items.append(StandardItem( + id='except', + text="Failed starting docker client", + subtext=str(e), + iconUrls=self.icon_urls_running, )) - return rank_items + return items try: for container in self.client.containers.list(all=True): @@ -66,36 +64,30 @@ def handleGlobalQuery(self, query): lambda id=container.id: setClipboardText(id)) ]) - rank_items.append(RankItem( - item=StandardItem( - id=container.id, - text="%s (%s)" % (container.name, ", ".join(container.image.tags)), - subtext="Container: %s" % container.id, - iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, - actions=actions - ), - score=len(query.string)/len(container.name) + items.append(StandardItem( + id=container.id, + text="%s (%s)" % (container.name, ", ".join(container.image.tags)), + subtext="Container: %s" % container.id, + iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, + actions=actions )) for image in reversed(self.client.images.list()): for tag in sorted(image.tags, key=len): # order by resulting score if query.string in tag: - rank_items.append(RankItem( - item=StandardItem( - id=image.short_id, - text=", ".join(image.tags), - subtext="Image: %s" % image.id, - iconUrls=self.icon_urls_stopped, - actions=[ - # Action("run", "Run with command: %s" % query.string, - # lambda i=image, s=query.string: client.containers.run(i, s)), - Action("rmi", "Remove image", lambda i=image: i.remove()) - ] - ), - score=len(query.string)/len(tag) + items.append(StandardItem( + id=image.short_id, + text=", ".join(image.tags), + subtext="Image: %s" % image.id, + iconUrls=self.icon_urls_stopped, + actions=[ + # Action("run", "Run with command: %s" % query.string, + # lambda i=image, s=query.string: client.containers.run(i, s)), + Action("rmi", "Remove image", lambda i=image: i.remove()) + ] )) except Exception as e: warning(str(e)) self.client = None - return rank_items + query.add(items) From a93035d1dbb93e4ae28dd6f72ca51e69c4b541c6 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:43:36 +0200 Subject: [PATCH 184/243] [color:1.4] Fix upstream url --- color/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/color/__init__.py b/color/__init__.py index 4ec468ae..ab5d8f41 100644 --- a/color/__init__.py +++ b/color/__init__.py @@ -19,11 +19,11 @@ from string import hexdigits md_iid = '2.3' -md_version = '1.3' +md_version = '1.4' md_name = 'Color' md_description = 'Display color for color codes' md_license = 'MIT' -md_url = 'https://github.com/albertlauncher/python/color' +md_url = 'https://github.com/albertlauncher/python/tree/main/color' md_authors = "@manuelschneid3r" From 89a380bc4286be3e96175744efc91957b427cbed Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:43:56 +0200 Subject: [PATCH 185/243] [copyq:1.6] Fix upstream url --- copyq/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/copyq/__init__.py b/copyq/__init__.py index 6ff3d234..bcf3ea72 100644 --- a/copyq/__init__.py +++ b/copyq/__init__.py @@ -8,11 +8,11 @@ from albert import * md_iid = '2.3' -md_version = "1.5" +md_version = "1.6" md_name = "CopyQ" md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" -md_url = "https://github.com/albertlauncher/python" +md_url = "https://github.com/albertlauncher/python/tree/main/copyq" md_authors = ["@ManuelSchneid3r", "@BarrensZeppelin"] md_bin_dependencies = ["copyq"] From dc8e2d97a1fd2d709cbfbd13d0f883439b2d3b25 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:44:11 +0200 Subject: [PATCH 186/243] [dice_roll:1.6] Fix upstream url --- dice_roll/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index ae12f472..10101814 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -16,11 +16,11 @@ """ md_iid = '2.3' -md_version = "1.5" +md_version = "1.6" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python" +md_url = "https://github.com/albertlauncher/python/tree/main/dice_roll" md_authors = "@DenverCoder1" From 9729f80b24008199ad98eea2e6f4baaa723af4b5 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:44:26 +0200 Subject: [PATCH 187/243] [duckduckgo:1.2] Fix upstream url --- duckduckgo/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/duckduckgo/__init__.py b/duckduckgo/__init__.py index f04f08ea..e5b5a92b 100644 --- a/duckduckgo/__init__.py +++ b/duckduckgo/__init__.py @@ -12,11 +12,11 @@ from time import sleep md_iid = '2.3' -md_version = '1.1' +md_version = '1.2' md_name = 'DuckDuckGo' md_description = 'Inline DuckDuckGo web search' md_license = "MIT" -md_url = 'https://github.com/albertlauncher/python/duckduckgo' +md_url = 'https://github.com/albertlauncher/python/tree/main/duckduckgo' md_lib_dependencies = "duckduckgo-search" md_authors = "@manuelschneid3r" From e00e0accc7ea067203736f62af6dffd824fe7b0b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:44:43 +0200 Subject: [PATCH 188/243] [inhibit_sleep:1.2] Fix upstream url --- inhibit_sleep/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inhibit_sleep/__init__.py b/inhibit_sleep/__init__.py index 3d620ebb..76829305 100644 --- a/inhibit_sleep/__init__.py +++ b/inhibit_sleep/__init__.py @@ -12,11 +12,11 @@ from subprocess import Popen, TimeoutExpired md_iid = '2.3' -md_version = '1.1' +md_version = '1.2' md_name = 'Inhibit sleep' md_description = 'Inhibit system sleep mode.' md_license = "MIT" -md_url = 'https://github.com/albertlauncher/python/inhibit_sleep' +md_url = 'https://github.com/albertlauncher/python/tree/main/inhibit_sleep' md_authors = "@manuelschneid3r" md_bin_dependencies = ['systemd-inhibit', "sleep"] From 3f2d4d93f378371177db2843d8ebef0d6cbd3799 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:45:03 +0200 Subject: [PATCH 189/243] [translators:1.8] Fix upstream url --- translators/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 6fb04efc..85f7b098 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -13,11 +13,11 @@ import translators as ts md_iid = '2.3' -md_version = "1.7" +md_version = "1.8" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python/translators" +md_url = "https://github.com/albertlauncher/python/tree/main/translators" md_authors = "@manuelschneid3r" md_lib_dependencies = "translators" From 82b15e5dddd9e715409034f7546769ed0888a512 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:45:18 +0200 Subject: [PATCH 190/243] [unit_converter:1.7] Fix upstream url --- unit_converter/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit_converter/__init__.py b/unit_converter/__init__.py index 66b1c05a..927629b2 100644 --- a/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -29,11 +29,11 @@ from albert import * md_iid = "2.3" -md_version = "1.6" +md_version = "1.7" md_name = "Unit Converter" md_description = "Convert between units" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python" +md_url = "https://github.com/albertlauncher/python/tree/main/unit_converter" md_lib_dependencies = ["pint", "inflect"] md_authors = ["@DenverCoder1", "@Pete-Hamlin"] From b1183a63a645abd644be0a5a163b3acd5878dd98 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 15 Aug 2024 23:45:30 +0200 Subject: [PATCH 191/243] [vpn:1.6] Fix upstream url --- vpn/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vpn/__init__.py b/vpn/__init__.py index ef82febd..db122112 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -9,11 +9,11 @@ from albert import * md_iid = '2.3' -md_version = "1.5" +md_version = "1.6" md_name = "VPN" md_description = "Manage NetworkManager VPN connections" md_license = "MIT" -md_url = "https://github.com/albertlauncher/python" +md_url = "https://github.com/albertlauncher/python/tree/main/vpn" md_authors = ["@janeklb", "@Bierchermuesli", "@manuelschneid3r"] md_bin_dependencies = ["nmcli"] From 522196fc33acda8df8d12ca5db56e2293dffbc4d Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 16 Aug 2024 18:42:57 +0200 Subject: [PATCH 192/243] [inhibit_sleep] Archive Moved to system plugin --- {inhibit_sleep => .archive/inhibit_sleep}/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {inhibit_sleep => .archive/inhibit_sleep}/__init__.py (100%) diff --git a/inhibit_sleep/__init__.py b/.archive/inhibit_sleep/__init__.py similarity index 100% rename from inhibit_sleep/__init__.py rename to .archive/inhibit_sleep/__init__.py From d39c04c04204784bf7e8a671131da876806090fb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 21 Aug 2024 16:38:34 +0200 Subject: [PATCH 193/243] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6802f24..9a5cd63a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,6 @@ This repository is shipped with Albert. Credits go to our contributors 👍 git clone https://github.com//python.git ~/Library/Application\ Support/albert/python/plugins ``` * Open the directory in your favorite IDE (PyCharmCE is a good choice). -* Write your plugin (Make sure it is upstream-polished-enough though). - This repository ships a [python stub file](https://github.com/albertlauncher/python/blob/master/albert.pyi) which gives you coding assistance. +* The Python plugins plugin installs a python stub file in your user plugin directory. This file serves as API documentation and gives you coding assistance if you are using a decent IDE. +* Write your plugin. Make sure it is polished (No bugs, few to no linting warnings, efficient, readable, maintainable, …). * Commit, push, send a PR. From cda9f21426daa10e8e75fcdc3c86f38f0d73007c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Tue, 10 Sep 2024 12:55:38 +0200 Subject: [PATCH 194/243] [unit_converter] Remove future typehints --- unit_converter/__init__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/unit_converter/__init__.py b/unit_converter/__init__.py index 927629b2..6fcf779f 100644 --- a/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -12,15 +12,12 @@ - `convert 100 USD to EUR` """ - -from __future__ import annotations - import json import re import traceback from datetime import datetime from pathlib import Path -from typing import Any +from typing import Any, Optional from urllib.error import URLError from urllib.request import urlopen @@ -29,7 +26,7 @@ from albert import * md_iid = "2.3" -md_version = "1.7" +md_version = "1.8" md_name = "Unit Converter" md_description = "Convert between units" md_license = "MIT" @@ -96,7 +93,8 @@ def __display_unit_name(self, amount: float, unit: str) -> str: unit = self.__pluralize_unit(unit) if amount != 1 else unit return self.display_names.get(unit, unit) - def __format_float(self, num: float) -> str: + @staticmethod + def __format_float(num: float) -> str: """Format a float to remove trailing zeros and avoid scientific notation Args: @@ -257,7 +255,7 @@ def _get_currencies(self) -> dict[str, float]: warning(f"Error getting currencies: {error}") return {} - def get_currency(self, currency: str) -> str | None: + def get_currency(self, currency: str) -> Optional[str]: """Get the currency name normalized using aliases and capitalization Args: @@ -387,7 +385,8 @@ def match_query(self, query_string: str): info("Something went wrong. Make sure you're using the correct format.") return [] - def _create_item(self, text: str, subtext: str, icon: str = "") -> Item: + @staticmethod + def _create_item(text: str, subtext: str, icon: str = "") -> Item: """Create an Item from a text and subtext Args: From e4af117f9adeee0a935c663f2be082debd7e9399 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 12 Sep 2024 22:12:42 +0200 Subject: [PATCH 195/243] [syncthing] Use syncthing2 Syncthing is dead --- syncthing/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncthing/__init__.py b/syncthing/__init__.py index c43fa9e2..1ddb423c 100644 --- a/syncthing/__init__.py +++ b/syncthing/__init__.py @@ -11,13 +11,13 @@ from syncthing import Syncthing md_iid = '2.3' -md_version = "1.0" +md_version = "1.1" md_name = "Syncthing" md_description = "Trigger basic syncthing actions." md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/syncthing" md_authors = "@manuelschneid3r" -md_lib_dependencies = "syncthing" +md_lib_dependencies = "syncthing2" class Plugin(PluginInstance, GlobalQueryHandler): From 2d07e54a3cada14cfa42226c053c8ac5306d92f3 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 18 Nov 2024 12:44:15 +0100 Subject: [PATCH 196/243] [coingecko] Use variadic matcher --- coingecko/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 58fdab66..a1d3f2b5 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -8,8 +8,8 @@ from pathlib import Path from threading import Thread, Event -md_iid = '2.3' -md_version = "1.3" +md_iid = '2.5' +md_version = "1.4" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "MIT" @@ -130,5 +130,5 @@ def updateIndexItems(self): def handleTriggerQuery(self, query): m = Matcher(query.string) for item in self.items: - if m.match(item.symbol) or m.match(item.name): + if m.match(item.symbol, item.name): query.add(item) From 50a2d4e0252b392f78fcd44b03dbcb7f9722cf2f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 18 Nov 2024 12:44:24 +0100 Subject: [PATCH 197/243] [jetbrains] Use variadic matcher --- jetbrains_projects/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 39d69f0c..36f7625e 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -33,8 +33,8 @@ from xml.etree import ElementTree from albert import * -md_iid = '2.3' -md_version = "2.0" +md_iid = '2.5' +md_version = "2.1" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -203,7 +203,7 @@ def handleTriggerQuery(self, query: Query): for editor in self.editors: projects = editor.list_projects() projects = [p for p in projects if Path(p.path).exists()] - projects = [p for p in projects if m.match(p.name) or m.match(p.path)] + projects = [p for p in projects if m.match(p.name, p.path)] editor_project_pairs.extend([(editor, p) for p in projects]) # sort by last opened From dcdbb0521fc7be25a03898ac5a5025ca82b728ae Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 198/243] [arch_wiki] v3 --- arch_wiki/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/arch_wiki/__init__.py b/arch_wiki/__init__.py index 2b8eb1db..c24c2476 100644 --- a/arch_wiki/__init__.py +++ b/arch_wiki/__init__.py @@ -8,12 +8,12 @@ from albert import * -md_iid = '2.3' -md_version = '1.6' -md_name = 'Arch Linux Wiki' -md_description = 'Search Arch Linux Wiki articles' -md_license = 'MIT' -md_url = 'https://github.com/albertlauncher/python/tree/main/arch_wiki' +md_iid = "3.0" +md_version = '2.0' +md_name = "Arch Linux Wiki" +md_description = "Search Arch Linux Wiki articles" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/main/arch_wiki" md_authors = "@manuelschneid3r" @@ -22,11 +22,14 @@ class Plugin(PluginInstance, TriggerQueryHandler): baseurl = 'https://wiki.archlinux.org/api.php' search_url = "https://wiki.archlinux.org/index.php?search=%s" user_agent = "org.albert.extension.python.archwiki" + iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger='awiki ') - self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] + TriggerQueryHandler.__init__(self) + + def defaultTrigger(self): + return 'awiki ' def handleTriggerQuery(self, query): stripped = query.string.strip() @@ -58,7 +61,7 @@ def handleTriggerQuery(self, query): summary = data[2][i] url = data[3][i] - results.append(StandardItem(id=self.id, + results.append(StandardItem(id=self.id(), text=title, subtext=summary if summary else url, iconUrls=self.iconUrls, @@ -69,7 +72,7 @@ def handleTriggerQuery(self, query): if results: query.add(results) else: - query.add(StandardItem(id=self.id, + query.add(StandardItem(id=self.id(), text="Search '%s'" % query.string, subtext="No results. Start online search on Arch Wiki", iconUrls=self.iconUrls, @@ -77,7 +80,7 @@ def handleTriggerQuery(self, query): lambda s=query.string: openUrl(self.search_url % s))])) else: - query.add(StandardItem(id=self.id, + query.add(StandardItem(id=self.id(), text=md_name, iconUrls=self.iconUrls, subtext="Enter a query to search on the Arch Wiki")) From d9b1d092342cd4fe98824228b94325413746866a Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 199/243] [aur] v3 --- aur/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/aur/__init__.py b/aur/__init__.py index 69ad787c..0ca8917a 100644 --- a/aur/__init__.py +++ b/aur/__init__.py @@ -15,8 +15,8 @@ from albert import * -md_iid = "2.3" -md_version = "1.11" +md_iid = "3.0" +md_version = "2.0" md_name = "AUR" md_description = "Query and install AUR packages" md_license = "MIT" @@ -28,14 +28,11 @@ class Plugin(PluginInstance, TriggerQueryHandler): aur_url = "https://aur.archlinux.org/packages/" baseurl = 'https://aur.archlinux.org/rpc/' + iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='aur ' - ) - self.iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] + TriggerQueryHandler.__init__(self) if which("yaourt"): self.install_cmdline = "yaourt -S aur/%s" @@ -49,6 +46,9 @@ def __init__(self): info("No supported AUR helper found.") self.install_cmdline = None + def defaultTrigger(self): + return 'aur ' + def configWidget(self): return [ { @@ -78,7 +78,7 @@ def handleTriggerQuery(self, query): data = json.loads(response.read().decode()) if data['type'] == "error": query.add(StandardItem( - id=self.id, + id=self.id(), text="Error", subtext=data['error'], iconUrls=self.iconUrls @@ -92,7 +92,7 @@ def handleTriggerQuery(self, query): for entry in results_json: name = entry['Name'] item = StandardItem( - id=self.id, + id=self.id(), iconUrls=self.iconUrls, text=f"{entry['Name']} {entry['Version']}" ) @@ -137,7 +137,7 @@ def handleTriggerQuery(self, query): query.add(results) else: query.add(StandardItem( - id=self.id, + id=self.id(), text=md_name, subtext="Enter a query to search the AUR", iconUrls=self.iconUrls, From 3a6f4159bba37fb1e72f757f9633067286d8afe9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 200/243] [bitwarden] v3 --- bitwarden/__init__.py | 45 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index c40057c9..6e9e8031 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -5,8 +5,8 @@ from albert import * -md_iid = "2.3" -md_version = "2.4" +md_iid = "3.0" +md_version = "3.0" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "MIT" @@ -17,13 +17,14 @@ class Plugin(PluginInstance, TriggerQueryHandler): + iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] + def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='bw ' - ) - self.iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] + TriggerQueryHandler.__init__(self) + + def defaultTrigger(self): + return 'bw ' def handleTriggerQuery(self, query): if query.string.strip().lower() == "sync": @@ -44,9 +45,7 @@ def handleTriggerQuery(self, query): ) ) - filtered_items = self._filter_items(query) - - for p in filtered_items: + for p in self._filter_items(query): query.add( StandardItem( id=p["id"], @@ -68,7 +67,7 @@ def handleTriggerQuery(self, query): id="copy-username", text="Copy username to clipboard", callable=lambda username=p["user"]: - setClipboardText(text=username) + setClipboardText(text=username) ), Action( id="edit", @@ -79,7 +78,8 @@ def handleTriggerQuery(self, query): ) ) - def _get_items(self): + @staticmethod + def _get_items(): field_names = ["id", "name", "user", "folder"] raw_items = run( ["rbw", "list", "--fields", ",".join(field_names)], @@ -121,11 +121,12 @@ def _filter_items(self, query): return filtered_passwords - def _password_to_clipboard(self, item): - id = item["id"] + @staticmethod + def _password_to_clipboard(item): + rbw_id = item["id"] password = run( - ["rbw", "get", id], + ["rbw", "get", rbw_id], capture_output=True, encoding="utf-8", check=True @@ -133,12 +134,13 @@ def _password_to_clipboard(self, item): setClipboardText(text=password) - def _code_to_clipboard(self, item): - id = item["id"] + @staticmethod + def _code_to_clipboard(item): + rbw_id = item["id"] try: code = run( - ["rbw", "code", id], + ["rbw", "code", rbw_id], capture_output=True, encoding="utf-8", check=True @@ -153,7 +155,8 @@ def _code_to_clipboard(self, item): setClipboardText(text=code) - def _edit_entry(self, item): - id = item["id"] + @staticmethod + def _edit_entry(item): + rbw_id = item["id"] - runTerminal(script=f"rbw edit {id}") + runTerminal(script=f"rbw edit {rbw_id}") From 86c33d2d0b303ae68c4072b4120b28b57ec3cdba Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 201/243] [coingecko] v3 --- coingecko/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index a1d3f2b5..76f9f089 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -8,8 +8,8 @@ from pathlib import Path from threading import Thread, Event -md_iid = '2.5' -md_version = "1.4" +md_iid = "3.0" +md_version = "2.0" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "MIT" @@ -71,9 +71,9 @@ def __init__(self, iconUrls=Plugin.iconUrls, actions=[ Action("show", f"Show {name} on CoinGecko", - lambda id=identifier: openUrl(Plugin.coinsUrl + id)), + lambda coin_id=identifier: openUrl(Plugin.coinsUrl + coin_id)), Action("url", "Copy URL to clipboard", - lambda id=identifier: setClipboardText(Plugin.coinsUrl + id)) + lambda coin_id=identifier: setClipboardText(Plugin.coinsUrl + coin_id)) ] ) self.name = name @@ -87,15 +87,11 @@ class Plugin(PluginInstance, IndexQueryHandler): def __init__(self): PluginInstance.__init__(self) - IndexQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='cg ', - synopsis='< symbol | name >' - ) + IndexQueryHandler.__init__(self) self.items = [] self.mtime = 0 - self.coinCacheFilePath = self.cacheLocation / "coins.json" + self.coinCacheFilePath = self.cacheLocation() / "coins.json" self.thread = CoinFetcherThread(self.updateIndexItems, self.coinCacheFilePath) self.thread.start() @@ -103,6 +99,12 @@ def __del__(self): self.thread.stop() self.thread.join() + def defaultTrigger(self): + return 'cg ' + + def synopsis(self, query): + return "< symbol | name >" + def updateIndexItems(self): if self.coinCacheFilePath.is_file() and (mtime := self.coinCacheFilePath.lstat().st_mtime) > self.mtime: self.mtime = mtime From 220b112605816ff4a61d89716bf9753ea0f44c55 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 202/243] [color] v3 --- color/__init__.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/color/__init__.py b/color/__init__.py index ab5d8f41..482f709f 100644 --- a/color/__init__.py +++ b/color/__init__.py @@ -15,15 +15,14 @@ """ from albert import * -from urllib.parse import quote_plus from string import hexdigits -md_iid = '2.3' -md_version = '1.4' -md_name = 'Color' -md_description = 'Display color for color codes' -md_license = 'MIT' -md_url = 'https://github.com/albertlauncher/python/tree/main/color' +md_iid = "3.0" +md_version = "2.0" +md_name = "Color" +md_description = "Display color for color codes" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/main/color" md_authors = "@manuelschneid3r" @@ -31,10 +30,10 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): PluginInstance.__init__(self) - GlobalQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='#' - ) + GlobalQueryHandler.__init__(self) + + def defaultTrigger(self): + return '#' def handleGlobalQuery(self, query): rank_items = [] @@ -48,7 +47,7 @@ def handleGlobalQuery(self, query): rank_items.append( RankItem( StandardItem( - id=self.id, + id=self.id(), text=s, subtext="The color for this code.", iconUrls=[f"gen:?background=%23{s}"], From eb52b10775c1b05b3e3e7559a41e805b7be1f511 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 203/243] [copyq] v3 --- copyq/__init__.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/copyq/__init__.py b/copyq/__init__.py index bcf3ea72..cd21adb7 100644 --- a/copyq/__init__.py +++ b/copyq/__init__.py @@ -7,8 +7,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.6" +md_iid = "3.0" +md_version = "2.0" md_name = "CopyQ" md_description = "Access CopyQ clipboard" md_license = "BSD-2-Clause" @@ -51,16 +51,14 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='cp ' - ) + TriggerQueryHandler.__init__(self) + + def defaultTrigger(self): + return "cp " def handleTriggerQuery(self, query): items = [] - q_string = query.string - - script = copyq_script_getMatches % q_string if q_string else copyq_script_getAll + script = copyq_script_getMatches % query.string if query.string else copyq_script_getAll proc = subprocess.run(["copyq", "-"], input=script.encode(), stdout=subprocess.PIPE) json_arr = json.loads(proc.stdout.decode()) @@ -72,12 +70,12 @@ def handleTriggerQuery(self, query): else: text = " ".join(filter(None, text.replace("\n", " ").split(" "))) - act = lambda script, row=row: ( - lambda: runDetachedProcess(["copyq", script % row]) + act = lambda s=script, r=row: ( + lambda: runDetachedProcess(["copyq", s % r]) ) items.append( StandardItem( - id=self.id, + id=self.id(), iconUrls=["xdg:copyq"], text=text, subtext="%s: %s" % (row, ", ".join(json_obj["mimetypes"])), From ba086e893c1ce1ffc2db5c230b5cc8fd76268646 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 204/243] [dice_roll] v3 --- dice_roll/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dice_roll/__init__.py b/dice_roll/__init__.py index 10101814..deb9bc2c 100644 --- a/dice_roll/__init__.py +++ b/dice_roll/__init__.py @@ -15,8 +15,8 @@ Example: "roll 2d6 3d8 1d20" """ -md_iid = '2.3' -md_version = "1.6" +md_iid = "3.0" +md_version = "2.0" md_name = "Dice Roll" md_description = "Roll any number of dice" md_license = "MIT" @@ -133,11 +133,13 @@ class Plugin(albert.PluginInstance, albert.TriggerQueryHandler): def __init__(self): albert.PluginInstance.__init__(self) - albert.TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis="d [d ...]", - defaultTrigger='roll ' - ) + albert.TriggerQueryHandler.__init__(self) + + def synopsis(self, query): + return "d [d ...]" + + def defaultTrigger(self): + return "roll " def configWidget(self): return [{ 'type': 'label', 'text': __doc__.strip() }] @@ -147,7 +149,7 @@ def handleTriggerQuery(self, query: albert.Query) -> None: try: items = get_items(query_string) query.add(items) - except Exception as e: + except Exception: query.add([albert.StandardItem( id="error", iconUrls=[get_icon_path(None)], From 5104a11fbdd43634f3063a9e57f16a0ed29f553c Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 205/243] [docker] v3 --- docker/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docker/__init__.py b/docker/__init__.py index 9835e46f..f6639c03 100644 --- a/docker/__init__.py +++ b/docker/__init__.py @@ -6,8 +6,8 @@ import docker from albert import * -md_iid = "2.3" -md_version = "3.0" +md_iid = "3.0" +md_version = "4.0" md_name = "Docker" md_description = "Manage docker images and containers" md_license = "MIT" @@ -22,15 +22,17 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='d ', - synopsis='' - ) + TriggerQueryHandler.__init__(self) self.icon_urls_running = [f"file:{Path(__file__).parent}/running.png"] self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] self.client = None + def synopsis(self, query): + return "" + + def defaultTrigger(self): + return "d " + def handleTriggerQuery(self, query): items = [] @@ -61,7 +63,7 @@ def handleTriggerQuery(self, query): Action("remove", "Remove (forced, with volumes)", lambda c=container: c.remove(v=True, force=True)), Action("copy-id", "Copy id to clipboard", - lambda id=container.id: setClipboardText(id)) + lambda cid=container.id: setClipboardText(cid)) ]) items.append(StandardItem( From 28d94a0d027dac3be79d6aae986ccc058b9240e8 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:11:59 +0100 Subject: [PATCH 206/243] [duckduckgo] v3 --- duckduckgo/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/duckduckgo/__init__.py b/duckduckgo/__init__.py index e5b5a92b..3666115e 100644 --- a/duckduckgo/__init__.py +++ b/duckduckgo/__init__.py @@ -11,8 +11,8 @@ from itertools import islice from time import sleep -md_iid = '2.3' -md_version = '1.2' +md_iid = "3.0" +md_version = "2.0" md_name = 'DuckDuckGo' md_description = 'Inline DuckDuckGo web search' md_license = "MIT" @@ -25,13 +25,13 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='ddg ' - ) + TriggerQueryHandler.__init__(self) self.ddg = DDGS() self.iconUrls = [f"file:{Path(__file__).parent}/duckduckgo.svg"] + def defaultTrigger(self): + return "ddg " + def handleTriggerQuery(self, query): stripped = query.string.strip() @@ -46,7 +46,7 @@ def handleTriggerQuery(self, query): for r in islice(self.ddg.text(stripped, safesearch='off'), 10): query.add( StandardItem( - id=self.id, + id=self.id(), text=r['title'], subtext=r['body'], iconUrls=self.iconUrls, From 1a09f389ace86e62380d13a3caf1f972c86648eb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 207/243] [emoji] v3 --- emoji/__init__.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 120f1516..361198f0 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -5,14 +5,14 @@ import re import threading import urllib.request -from itertools import product +import builtins from locale import getdefaultlocale from pathlib import Path from albert import * -md_iid = '2.3' -md_version = "2.2" +md_iid = "3.0" +md_version = "3.0" md_name = "Emoji" md_description = "Find and copy emojis by name" md_license = "MIT" @@ -24,7 +24,7 @@ class Plugin(PluginInstance, IndexQueryHandler): def __init__(self): PluginInstance.__init__(self) - IndexQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger=':') + IndexQueryHandler.__init__(self) self.thread = None self._use_derived = self.readConfig('use_derived', bool) @@ -35,6 +35,9 @@ def __del__(self): if self.thread and self.thread.is_alive(): self.thread.join() + def defaultTrigger(self): + return ':' + @property def use_derived(self): return self._use_derived @@ -69,7 +72,7 @@ def download_file(url: str, path: Path): with urllib.request.urlopen(request, timeout=3) as response: if response.getcode() == 200: debug(f"Success. Storing to {path}.") - with open(path, 'wb') as file: + with builtins.open(path, 'wb') as file: file.write(response.read()) else: raise RuntimeError(f"Failed to download {url}. Status code: {response.getcode()}") @@ -157,8 +160,8 @@ def get_annotations(cache_path: Path, use_derived: bool) -> dict: json_derived = json.load(file_derived)['annotationsDerived']['annotations'] return json_full | json_derived - emojis = get_fully_qualified_emojis(self.cacheLocation) - annotations = get_annotations(self.cacheLocation, self.use_derived) + emojis = get_fully_qualified_emojis(self.cacheLocation()) + annotations = get_annotations(self.cacheLocation(), self.use_derived) def remove_redundancy(sentences): sets_of_words = [set(sentence.lower().split()) for sentence in sentences] @@ -181,7 +184,7 @@ def remove_redundancy(sentences): non_rgi_emoji = emoji.replace('\uFE0F', '') ann = annotations[non_rgi_emoji] except KeyError as e: - # debug(f"Found no translation for {e}. Emoji will not be available.") + debug(f"Found no translation for {e}. Emoji will not be available.") continue title = ann['tts'][0] From 8e3635bec1c8aaec2a130a82c64d7d7b511b6770 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 208/243] [goldendict] v3 --- goldendict/__init__.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index d60298eb..c25c72bb 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -6,23 +6,20 @@ from albert import * -md_iid = '2.3' -md_version = '1.5' -md_name = 'GoldenDict' -md_description = 'Quick access to GoldenDict' -md_license = 'MIT' -md_url = 'https://github.com/albertlauncher/python/tree/main/goldendict' -md_authors = '@manuelschneid3r' +md_iid = "3.0" +md_version = "2.0" +md_name = "GoldenDict" +md_description = "Quick access to GoldenDict" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/main/goldendict" +md_authors = "@manuelschneid3r" class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='gd ' - ) + TriggerQueryHandler.__init__(self) commands = [ '/var/lib/flatpak/exports/bin/org.goldendict.GoldenDict', # flatpak @@ -43,6 +40,9 @@ def __init__(self): warning(f"Multiple GoldenDict commands found: {', '.join(executables)}") warning(f"Using {self.executable}") + def defaultTrigger(self): + return "gd " + def handleTriggerQuery(self, query): q = query.string.strip() query.add( From 4aac6ca4a18c1a0e7e6bb14fda58c1fca97e9064 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 209/243] [jetbrains_projects] v3 --- jetbrains_projects/__init__.py | 43 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 36f7625e..3dbfcf2b 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -27,14 +27,14 @@ from dataclasses import dataclass from pathlib import Path -from typing import Union +from typing import Union, List from shutil import which from sys import platform from xml.etree import ElementTree from albert import * -md_iid = '2.5' -md_version = "2.1" +md_iid = "3.0" +md_version = "3.0" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -62,13 +62,14 @@ def __init__(self, name: str, icon: Path, config_dir_prefix: str, binaries: list self.config_dir_prefix = config_dir_prefix self.binary = self._find_binary(binaries) - def _find_binary(self, binaries: list[str]) -> Union[str, None]: + @staticmethod + def _find_binary(binaries: list[str]) -> Union[str, None]: for binary in binaries: if which(binary): return binary return None - def list_projects(self) -> list[Project]: + def list_projects(self) -> List[Project]: config_dir = Path.home() / ".config" if platform == "darwin": config_dir = Path.home() / "Library" / "Application Support" @@ -79,7 +80,8 @@ def list_projects(self) -> list[Project]: latest = sorted(dirs)[-1] return self._parse_recent_projects(Path(latest) / "options" / "recentProjects.xml") - def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: + @staticmethod + def _parse_recent_projects(recent_projects_file: Path) -> list[Project]: try: root = ElementTree.parse(recent_projects_file).getroot() entries = root.findall(".//component[@name='RecentProjectsManager']//entry[@key]") @@ -113,10 +115,9 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='jb ' - ) + TriggerQueryHandler.__init__(self) + + self.fuzzy = False plugin_dir = Path(__file__).parent editors = [ @@ -196,22 +197,32 @@ def __init__(self): ] self.editors = [e for e in editors if e.binary is not None] + def supportsFuzzyMatching(self): + return True + + def setFuzzyMatching(self, enabled): + self.fuzzy = enabled + + def defaultTrigger(self): + return "jb " + def handleTriggerQuery(self, query: Query): editor_project_pairs = [] - m = Matcher(query.string) + m = Matcher(query.string, MatchConfig(fuzzy=self.fuzzy)) + for editor in self.editors: - projects = editor.list_projects() - projects = [p for p in projects if Path(p.path).exists()] - projects = [p for p in projects if m.match(p.name, p.path)] - editor_project_pairs.extend([(editor, p) for p in projects]) + for project in editor.list_projects(): + if Path(project.path).exists() and m.match(project.name, project.path): + editor_project_pairs.append((editor, project)) # sort by last opened editor_project_pairs.sort(key=lambda pair: pair[1].last_opened, reverse=True) query.add([self._make_item(editor, project, query) for editor, project in editor_project_pairs]) - def _make_item(self, editor: Editor, project: Project, query: Query) -> Item: + @staticmethod + def _make_item(editor: Editor, project: Project, query: Query) -> Item: return StandardItem( id="%s-%s-%s" % (editor.binary, project.path, project.last_opened), text=project.name, From a3225989dee2a2a2deebd0938bdf151918097aba Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 210/243] [kill] v3 --- kill/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kill/__init__.py b/kill/__init__.py index c443d2b1..c9c69231 100644 --- a/kill/__init__.py +++ b/kill/__init__.py @@ -9,8 +9,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.4" +md_iid = "3.0" +md_version = "2.0" md_name = "Kill Process" md_description = "Kill processes" md_license = "MIT" @@ -21,10 +21,10 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='kill ' - ) + TriggerQueryHandler.__init__(self) + + def defaultTrigger(self): + return "kill " def handleTriggerQuery(self, query): if not query.isValid: From 2a503e63b4048a28e15e5d4b33bab4bd9ff93748 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 211/243] [locate] v3 --- locate/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/locate/__init__.py b/locate/__init__.py index 8da7db90..55015ba8 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -13,8 +13,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.10" +md_iid = "3.0" +md_version = "2.0" md_name = "Locate" md_description = "Find and open files using locate" md_license = "MIT" @@ -27,11 +27,7 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger="'" - ) + TriggerQueryHandler.__init__(self) self.iconUrls = [ "xdg:preferences-system-search", @@ -41,6 +37,12 @@ def __init__(self): f"file:{Path(__file__).parent}/locate.svg" ] + def synopsis(self, query): + return "" + + def defaultTrigger(self): + return "'" + def handleTriggerQuery(self, query): if len(query.string) > 2: From a12c9b03c4659a6bf90caf6659daaced58ba6b2b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 212/243] [pacman] v3 --- pacman/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pacman/__init__.py b/pacman/__init__.py index a522c73a..eb0e3e14 100644 --- a/pacman/__init__.py +++ b/pacman/__init__.py @@ -7,8 +7,8 @@ from albert import Action, StandardItem, PluginInstance, TriggerQueryHandler, runTerminal, openUrl -md_iid = '2.3' -md_version = "1.10" +md_iid = "3.0" +md_version = "2.0" md_name = "PacMan" md_description = "Search, install and remove packages" md_license = "MIT" @@ -23,17 +23,19 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger='pac ' - ) + TriggerQueryHandler.__init__(self) self.iconUrls = [ "xdg:archlinux-logo", "xdg:system-software-install", f"file:{pathlib.Path(__file__).parent}/arch.svg" ] + def synopsis(self, query): + return "" + + def defaultTrigger(self): + return "pac " + def handleTriggerQuery(self, query): stripped = query.string.strip() From 5808276dc70f85d543216504c5d54a1327010f69 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 213/243] [pass] v3 --- pass/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pass/__init__.py b/pass/__init__.py index 567d1650..2a0ce20a 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -7,8 +7,8 @@ import os from albert import * -md_iid = '2.3' -md_version = "1.7" +md_iid = "3.0" +md_version = "2.0" md_name = "Pass" md_description = "Manage passwords in pass" md_license = "BSD-3" @@ -23,11 +23,7 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger='pass ' - ) + TriggerQueryHandler.__init__(self) self.iconUrls = ["xdg:dialog-password"] self._use_otp = self.readConfig("use_otp", bool) or False self._otp_glob = self.readConfig("otp_glob", str) or "*-otp.gpg" @@ -52,6 +48,12 @@ def otp_glob(self, value): self._otp_glob = value self.writeConfig("otp_glob", value) + def defaultTrigger(self): + return "pass " + + def synopsis(self, query): + return "" + def configWidget(self): return [ {"type": "checkbox", "property": "use_otp", "label": "Enable pass OTP extension"}, From b7cd108cfdd12e46b62b1a52d762cd949f11d777 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 214/243] [pomodoro] v3 --- pomodoro/__init__.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pomodoro/__init__.py b/pomodoro/__init__.py index 4a38a8d4..39dc9b35 100644 --- a/pomodoro/__init__.py +++ b/pomodoro/__init__.py @@ -5,15 +5,14 @@ Wiki: [Pomodoro_Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique). """ -import subprocess import threading import time from pathlib import Path from albert import * -md_iid = '2.3' -md_version = "1.7" +md_iid = "3.0" +md_version = "2.0" md_name = "Pomodoro" md_description = "Set up a Pomodoro timer" md_license = "MIT" @@ -27,6 +26,12 @@ def __init__(self): self.isBreak = True self.timer = None self.notification = None + self.remainingTillLongBreak = 0 + self.endTime = 0 + self.pomodoroDuration = 0 + self.breakDuration = 0 + self.longBreakDuration = 0 + self.count = 0 def timeout(self): if self.isBreak: @@ -77,14 +82,16 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='[duration [break duration [long break duration [count]]]]', - defaultTrigger='pomo ' - ) + TriggerQueryHandler.__init__(self) self.pomodoro = PomodoroTimer() self.iconUrls = [f"file:{Path(__file__).parent}/pomodoro.svg"] + def defaultTrigger(self): + return 'pomo ' + + def synopsis(self, query): + return '[duration [break duration [long break duration [count]]]]' + def configWidget(self): return [ { @@ -96,13 +103,13 @@ def configWidget(self): def handleTriggerQuery(self, query): item = StandardItem( - id=self.id, + id=self.id(), iconUrls=self.iconUrls, ) if self.pomodoro.isActive(): item.text = "Stop Pomodoro" - item.actions = [Action("stop", "Stop", lambda p=self.pomodoro: p.stop())] + item.actions = [Action("stop", "Stop", lambda pomo=self.pomodoro: pomo.stop())] if self.pomodoro.isBreak: whatsNext = "Pomodoro" else: @@ -125,5 +132,5 @@ def handleTriggerQuery(self, query): item.text = "Start Pomodoro" item.subtext = f"{p} min, break {b} min, long break {lb} min, count {c}" item.actions = [Action("start", "Start", - lambda p=p, b=b, lb=lb, c=c: self.pomodoro.start(p, b, lb, c))] + lambda _p=p, _b=b, _lb=lb, _c=c: self.pomodoro.start(_p, _b, _lb, _c))] query.add(item) From 2aca84dfd488ea54610286fd3db9e186afd7e455 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 215/243] [python_eval] v3 --- python_eval/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python_eval/__init__.py b/python_eval/__init__.py index c054025d..1acce6c4 100644 --- a/python_eval/__init__.py +++ b/python_eval/__init__.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017-2014 Manuel Schneider -from builtins import pow -from math import * from pathlib import Path from albert import * -md_iid = '2.3' -md_version = "1.6" +md_iid = "3.0" +md_version = "2.0" md_name = "Python Eval" md_description = "Evaluate Python code" md_license = "BSD-3" @@ -20,13 +18,15 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger='py ' - ) + TriggerQueryHandler.__init__(self) self.iconUrls = [f"file:{Path(__file__).parent}/python.svg"] + def synopsis(self, query): + return "" + + def defaultTrigger(self): + return "py " + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: @@ -38,7 +38,7 @@ def handleTriggerQuery(self, query): result_str = str(result) query.add(StandardItem( - id=self.id, + id=self.id(), text=result_str, subtext=type(result).__name__, inputActionText=query.trigger + result_str, From 6267a9177bc4ac3efd0b8185e5564ffbf6560579 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 216/243] [syncthing] v3 --- syncthing/__init__.py | 69 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/syncthing/__init__.py b/syncthing/__init__.py index 1ddb423c..ce4cb3e7 100644 --- a/syncthing/__init__.py +++ b/syncthing/__init__.py @@ -10,8 +10,8 @@ from albert import * from syncthing import Syncthing -md_iid = '2.3' -md_version = "1.1" +md_iid = "3.0" +md_version = "2.0" md_name = "Syncthing" md_description = "Trigger basic syncthing actions." md_license = "MIT" @@ -26,12 +26,16 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): PluginInstance.__init__(self) - GlobalQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger='st ') + GlobalQueryHandler.__init__(self) + self.iconUrls = ["xdg:syncthing", f"file:{Path(__file__).parent}/syncthing.svg"] self._api_key = self.readConfig(self.config_key, str) if self._api_key: self.st = Syncthing(self._api_key) + def defaultTrigger(self): + return 'st ' + @property def api_key(self) -> str: return self._api_key @@ -84,7 +88,7 @@ def handleGlobalQuery(self, query): matcher = Matcher(query.string) # create device items - for id, d in devices.items(): + for device_id, d in devices.items(): device_name = d['name'] if match := matcher.match(device_name): @@ -94,50 +98,43 @@ def handleGlobalQuery(self, query): if d['paused']: actions.append( Action("resume", "Resume synchronization", - lambda did=id: self.st.system.resume(did)) + lambda did=device_id: self.st.system.resume(did)) ) else: actions.append( Action("pause", "Pause synchronization", - lambda did=id: self.st.system.pause(did)) + lambda did=device_id: self.st.system.pause(did)) ) - results.append( - RankItem( - StandardItem( - id=id, - text=f"{device_name}", - subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " - f"Shared: {device_folders if device_folders else 'Nothing'}.", - iconUrls=self.iconUrls, - actions=actions - ), - match.score - ) + item = StandardItem( + id=device_id, + text=f"{device_name}", + subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " + f"Shared: {device_folders if device_folders else 'Nothing'}.", + iconUrls=self.iconUrls, + actions=actions ) + results.append(RankItem(item, match)) + # create folder items - for id, f in folders.items(): + for folder_id, f in folders.items(): folder_name = f['label'] if match := matcher.match(folder_name): folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) - results.append( - RankItem( - StandardItem( - id=id, - text=folder_name, - subtext=f"Syncthing folder {f['path']}. " - f"Shared with {folders_devices if folders_devices else 'nobody'}.", - iconUrls=self.iconUrls, - actions=[ - Action("scan", "Scan the folder", - lambda fid=id: self.st.database.scan(fid)), - Action("open", "Open this folder in file browser", - lambda p=f['path']: openUrl(f'file://{p}')) - ] - ), - match.score - ) + item = StandardItem( + id=folder_id, + text=folder_name, + subtext=f"Syncthing folder {f['path']}. " + f"Shared with {folders_devices if folders_devices else 'nobody'}.", + iconUrls=self.iconUrls, + actions=[ + Action("scan", "Scan the folder", + lambda fid=folder_id: self.st.database.scan(fid)), + Action("open", "Open this folder in file browser", + lambda p=f['path']: openFile(p)) + ] ) + results.append(RankItem(item, match)) return results From 958eff2e66bacf8970caff4cd707ecbe89e8fca9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 217/243] [tex_to_unicode] v3 --- tex_to_unicode/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tex_to_unicode/__init__.py b/tex_to_unicode/__init__.py index baf5905d..629e4584 100644 --- a/tex_to_unicode/__init__.py +++ b/tex_to_unicode/__init__.py @@ -9,8 +9,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.3" +md_iid = "3.0" +md_version = "2.0" md_name = "TeX to Unicode" md_description = "Convert TeX mathmode commands to unicode characters" md_license = "MIT" @@ -23,7 +23,7 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__(self, self.id, self.name, self.description, defaultTrigger='tex ') + TriggerQueryHandler.__init__(self) self.COMBINING_LONG_SOLIDUS_OVERLAY = "\u0338" self.iconUrls = [f"file:{Path(__file__).parent}/tex.svg"] @@ -38,13 +38,16 @@ def _create_item(self, text: str, subtext: str, can_copy: bool): ) ) return StandardItem( - id=self.id, + id=self.id(), text=text, subtext=subtext, iconUrls=self.iconUrls, actions=actions, ) + def defaultTrigger(self): + return "tex " + def handleTriggerQuery(self, query): stripped = query.string.strip() From cc0b3b72c9059e4471292d0b0ed7416690335e8f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 218/243] [translators] v3 --- translators/__init__.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 85f7b098..862f427f 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -12,8 +12,8 @@ from albert import * import translators as ts -md_iid = '2.3' -md_version = "1.8" +md_iid = "3.0" +md_version = "2.0" md_name = "Translator" md_description = "Translate sentences using 'translators' package" md_license = "MIT" @@ -26,11 +26,8 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis="[[from] to] text", - defaultTrigger='tr ' - ) + TriggerQueryHandler.__init__(self) + self.iconUrls = [f"file:{Path(__file__).parent}/google_translate.png"] self._translator = self.readConfig('translator', str) @@ -69,6 +66,9 @@ def lang(self, value): self._lang = value self.writeConfig('lang', value) + def defaultTrigger(self): + return 'tr ' + def configWidget(self): return [ { @@ -88,6 +88,9 @@ def configWidget(self): } ] + def synopsis(self, s): + return "[[from] to] text" + def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: @@ -126,7 +129,7 @@ def handleTriggerQuery(self, query): ) query.add(StandardItem( - id=self.id, + id=self.id(), text=translation, subtext=f"{src.upper()} > {dst.upper()}", iconUrls=self.iconUrls, @@ -136,7 +139,7 @@ def handleTriggerQuery(self, query): except Exception as e: query.add(StandardItem( - id=self.id, + id=self.id(), text="Error", subtext=str(e), iconUrls=self.iconUrls From 20a33badfa61bcd0d7678a87358285fb535d2855 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 219/243] [unit_converter] v3 --- unit_converter/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/unit_converter/__init__.py b/unit_converter/__init__.py index 6fcf779f..cd26cf22 100644 --- a/unit_converter/__init__.py +++ b/unit_converter/__init__.py @@ -25,7 +25,7 @@ import pint from albert import * -md_iid = "2.3" +md_iid = "3.0" md_version = "1.8" md_name = "Unit Converter" md_description = "Convert between units" @@ -126,7 +126,7 @@ def formatted_from(self) -> str: def icon(self) -> str: """Return the icon for the result's dimensionality""" # strip characters from the dimensionality if not alphanumeric or underscore - dimensionality = re.sub(r"[^\w]", "", self.dimensionality) + dimensionality = re.sub(r"\W", "", self.dimensionality) return f"{dimensionality}.svg" def __repr__(self): @@ -345,14 +345,7 @@ class Plugin(PluginInstance, GlobalQueryHandler): def __init__(self): PluginInstance.__init__(self) - GlobalQueryHandler.__init__( - self, - id=self.id, - name=self.name, - description=self.description, - synopsis=" to ", - defaultTrigger="convert ", - ) + GlobalQueryHandler.__init__(self) self.unit_convert_regex = re.compile( r"(?P-?\d+\.?\d*)\s?(?P.*)\s(?:to|in)\s(?P.*)", @@ -361,6 +354,12 @@ def __init__(self): self.unit_converter = StandardUnitConverter() self.currency_converter = CurrencyConverter() + def defaultTrigger(self): + return "convert " + + def synopsis(self, query): + return " to " + def handleTriggerQuery(self, query: Query) -> None: if query_string := query.string.strip(): items = self.match_query(query_string) From 920b617ea142f9e0627a3c35653915a088db0ede Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 220/243] [virtualbox] v3 --- virtualbox/__init__.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/virtualbox/__init__.py b/virtualbox/__init__.py index 8ba78272..ad39ebbf 100644 --- a/virtualbox/__init__.py +++ b/virtualbox/__init__.py @@ -11,8 +11,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.7" +md_iid = "3.0" +md_version = "2.0" md_name = "VirtualBox" md_description = "Manage your VirtualBox machines" md_license = "MIT" @@ -64,13 +64,15 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger='vbox ' - ) + TriggerQueryHandler.__init__(self) self.iconUrls = ["xdg:virtualbox", ":unknown"] + def defaultTrigger(self): + return 'vbox ' + + def synopsis(self, query): + return "" + def configWidget(self): return [ { @@ -89,17 +91,17 @@ def handleTriggerQuery(self, query): for vm in filter(lambda vm: pattern in vm.name.lower(), virtualbox.VirtualBox().machines): actions = [] if vm.state == MachineState.powered_off or vm.state == MachineState.aborted: # 1 # 4 - actions.append(Action("startvm", "Start virtual machine", lambda vm=vm: startVm(vm))) + actions.append(Action("startvm", "Start virtual machine", lambda m=vm: startVm(m))) if vm.state == MachineState.saved: # 2 - actions.append(Action("restorevm", "Start saved virtual machine", lambda vm=vm: startVm(vm))) - actions.append(Action("discardvm", "Discard saved state", lambda vm=vm: discardSavedVm(vm))) + actions.append(Action("restorevm", "Start saved virtual machine", lambda m=vm: startVm(m))) + actions.append(Action("discardvm", "Discard saved state", lambda m=vm: discardSavedVm(m))) if vm.state == MachineState.running: # 5 - actions.append(Action("savevm", "Save virtual machine", lambda vm=vm: saveVm(vm))) - actions.append(Action("poweroffvm", "Power off via ACPI event (Power button)", lambda vm=vm: acpiPowerVm(vm))) - actions.append(Action("stopvm", "Turn off virtual machine", lambda vm=vm: stopVm(vm))) - actions.append(Action("pausevm", "Pause virtual machine", lambda vm=vm: pauseVm(vm))) + actions.append(Action("savevm", "Save virtual machine", lambda m=vm: saveVm(m))) + actions.append(Action("poweroffvm", "Power off via ACPI event (Power button)", lambda m=vm: acpiPowerVm(m))) + actions.append(Action("stopvm", "Turn off virtual machine", lambda m=vm: stopVm(m))) + actions.append(Action("pausevm", "Pause virtual machine", lambda m=vm: pauseVm(m))) if vm.state == MachineState.paused: # 6 - actions.append(Action("resumevm", "Resume virtual machine", lambda vm=vm: resumeVm(vm))) + actions.append(Action("resumevm", "Resume virtual machine", lambda m=vm: resumeVm(m))) items.append( StandardItem( From ff8ac96f6929591ec845198f237ecde6edcf6e51 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 00:12:00 +0100 Subject: [PATCH 221/243] [vpn] v3 --- vpn/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vpn/__init__.py b/vpn/__init__.py index db122112..f529bc93 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -8,8 +8,8 @@ from albert import * -md_iid = '2.3' -md_version = "1.6" +md_iid = "3.0" +md_version = "2.0" md_name = "VPN" md_description = "Manage NetworkManager VPN connections" md_license = "MIT" @@ -24,10 +24,10 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='vpn ' - ) + TriggerQueryHandler.__init__(self) + + def defaultTrigger(self): + return "vpn " def getVPNConnections(self): consStr = subprocess.check_output( From 69d51583cdb0cb2d5040e7535ae88332cc14e9f7 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 14:05:11 +0100 Subject: [PATCH 222/243] [wikipedia] v3.0 --- wikipedia/__init__.py | 46 ++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/wikipedia/__init__.py b/wikipedia/__init__.py index 2305117a..af92e2f6 100644 --- a/wikipedia/__init__.py +++ b/wikipedia/__init__.py @@ -10,15 +10,14 @@ import json from pathlib import Path -md_iid = '2.3' -md_version = "2.0" +md_iid = "3.0" +md_version = "3.0" md_name = "Wikipedia" md_description = "Search Wikipedia articles" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/wikipedia" md_authors = "@manuelschneid3r" - class Plugin(PluginInstance, TriggerQueryHandler): baseurl = 'https://en.wikipedia.org/w/api.php' @@ -29,10 +28,9 @@ class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='wiki ', supportsFuzzyMatching=True - ) + TriggerQueryHandler.__init__(self) + + self.fbh = FBH(self) self.fuzzy = False self.local_lang_code = getdefaultlocale()[0] @@ -42,9 +40,6 @@ def __init__(self): self.local_lang_code = 'en' warning("Failed getting language code. Using 'en'.") - self.fbh = FBH(self) - self.registerExtension(self.fbh) - params = { 'action': 'query', 'meta': 'siteinfo', @@ -66,8 +61,14 @@ def __init__(self): except Exception as error: warning('Error getting languages (%s). Defaulting to EN.' % error) - def __del__(self): - self.deregisterExtension(self.fbh) + def extensions(self): + return [self, self.fbh] + + def defaultTrigger(self): + return "wiki " + + def supportsFuzzyMatching(self): + return True def setFuzzyMatching(self, enabled: bool): self.fuzzy = enabled @@ -103,7 +104,7 @@ def handleTriggerQuery(self, query): url = data[3][i] results.append( StandardItem( - id=self.id, + id=self.id(), text=title, subtext=summary if summary else url, iconUrls=self.iconUrls, @@ -121,8 +122,8 @@ def handleTriggerQuery(self, query): else: query.add( StandardItem( - id=self.id, - text=self.name, + id=self.id(), + text=self.name(), subtext="Enter a query to search on Wikipedia", iconUrls=self.iconUrls ) @@ -130,8 +131,8 @@ def handleTriggerQuery(self, query): def createFallbackItem(self, q: str) -> Item: return StandardItem( - id=self.id, - text=self.name, + id=self.id(), + text=self.name(), subtext="Search '%s' on Wikipedia" % q, iconUrls=self.iconUrls, actions=[ @@ -144,8 +145,17 @@ def createFallbackItem(self, q: str) -> Item: class FBH(FallbackHandler): def __init__(self, p: Plugin): - FallbackHandler.__init__(self, p.id + 'fb', p.name, p.description) + FallbackHandler.__init__(self) self.plugin = p + def id(self): + return "wikipedia.fallbacks" + + def name(self): + return md_name + + def description(self): + return md_description + def fallbacks(self, q :str): return [self.plugin.createFallbackItem(q)] From 0958ea7e770730da6884c170d30190012e050782 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Feb 2025 14:06:11 +0100 Subject: [PATCH 223/243] [zeal] v3.0 --- zeal/__init__.py | 75 ++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/zeal/__init__.py b/zeal/__init__.py index 89e6bd63..1339a7d9 100644 --- a/zeal/__init__.py +++ b/zeal/__init__.py @@ -1,54 +1,55 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 Manuel Schneider -from albert import * +import albert -md_iid = '2.3' -md_version = '2.0' -md_name = 'Zeal' -md_description = 'Search in Zeal docs' +md_iid = "3.0" +md_version = "3.0" +md_name = "Zeal" +md_description = "Search in Zeal docs" md_license = "MIT" -md_url = 'https://github.com/albertlauncher/python/tree/main/zeal' +md_url = "https://github.com/albertlauncher/python/tree/main/zeal" md_authors = "@manuelschneid3r" md_bin_dependencies = ['zeal'] +def createItem(query: str): + return albert.StandardItem( + id=md_name, + text=md_name, + subtext=f"Search '{query}' in Zeal", + iconUrls=["xdg:zeal"], + actions=[albert.Action("zeal", "Search in Zeal", + lambda q=query: albert.runDetachedProcess(['zeal', q]))] + ) + +class FBH(albert.FallbackHandler): + + def id(self): + return "zeal_fbh" + + def name(self): + return md_name + + def description(self): + return md_description -class FBH(FallbackHandler): def fallbacks(self, s): - return [Plugin.createItem(s)] if s else [] + return [createItem(s)] if s else [] -class Plugin(PluginInstance, TriggerQueryHandler): +class Plugin(albert.PluginInstance, albert.TriggerQueryHandler): def __init__(self): - PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - defaultTrigger='z ' - ) - self.fbh = FBH( - id=self.id + 'fb', - name=self.name, - description=self.description - ) - - self.registerExtension(self.fbh) - - def __del__(self): - self.deregisterExtension(self.fbh) + albert.PluginInstance.__init__(self) + albert.TriggerQueryHandler.__init__(self) + self.fbh = FBH() + + def defaultTrigger(self): + return "z " + + def extensions(self): + return [self, self.fbh] def handleTriggerQuery(self, query): if stripped := query.string.strip(): - query.add(self.createItem(stripped)) - - @staticmethod - def createItem(query: str) -> Item: - return StandardItem( - id=md_name, - text=md_name, - subtext=f"Search '{query}' in Zeal", - iconUrls=["xdg:zeal"], - actions=[Action("zeal", "Search in Zeal", - lambda q=query: runDetachedProcess(['zeal', q]))] - ) - + query.add(createItem(stripped)) From 806da15b4ec4f0738cdd4730aa6a337a9abbd9ff Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 5 Mar 2025 11:52:16 +0100 Subject: [PATCH 224/243] [vpn] Archive The native VPN plugin now covers NetworkManager --- {vpn => .archive/vpn}/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {vpn => .archive/vpn}/__init__.py (100%) diff --git a/vpn/__init__.py b/.archive/vpn/__init__.py similarity index 100% rename from vpn/__init__.py rename to .archive/vpn/__init__.py From dd2d795aa068429cc6c75812d71928eb664bddbe Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 5 Mar 2025 13:57:30 +0100 Subject: [PATCH 225/243] Update readme --- README.md | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9a5cd63a..b95c05b6 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,11 @@ ## Official Albert Python plugin repository -This repository is shipped with Albert. Credits go to our contributors 👍 +These plugins are shipped with the app. + +Visit the website to learn [how to write plugins](https://albertlauncher.github.io/gettingstarted/extension/). + +Credits go to our contributors: - -### Contribution - -* Fork this repository. -* Clone it into the Python user plugin location. - ```shell - # on linux - git clone https://github.com//python.git ~/.local/share/albert/python/plugins - - # on macos - git clone https://github.com//python.git ~/Library/Application\ Support/albert/python/plugins - ``` -* Open the directory in your favorite IDE (PyCharmCE is a good choice). -* The Python plugins plugin installs a python stub file in your user plugin directory. This file serves as API documentation and gives you coding assistance if you are using a decent IDE. -* Write your plugin. Make sure it is polished (No bugs, few to no linting warnings, efficient, readable, maintainable, …). -* Commit, push, send a PR. From fc25d02f9f8b009691f369d9190677c54bd62800 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 6 Mar 2025 21:41:55 +0100 Subject: [PATCH 226/243] [emoji] Make sure cache location exists Close https://github.com/albertlauncher/albert/issues/1521 --- emoji/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/emoji/__init__.py b/emoji/__init__.py index 361198f0..49bfa0a3 100644 --- a/emoji/__init__.py +++ b/emoji/__init__.py @@ -12,7 +12,7 @@ from albert import * md_iid = "3.0" -md_version = "3.0" +md_version = "3.1" md_name = "Emoji" md_description = "Find and copy emojis by name" md_license = "MIT" @@ -160,8 +160,10 @@ def get_annotations(cache_path: Path, use_derived: bool) -> dict: json_derived = json.load(file_derived)['annotationsDerived']['annotations'] return json_full | json_derived - emojis = get_fully_qualified_emojis(self.cacheLocation()) - annotations = get_annotations(self.cacheLocation(), self.use_derived) + cache_location = self.cacheLocation() + cache_location.mkdir(parents=True, exist_ok=True) + emojis = get_fully_qualified_emojis(cache_location) + annotations = get_annotations(cache_location, self.use_derived) def remove_redundancy(sentences): sets_of_words = [set(sentence.lower().split()) for sentence in sentences] From 41f93b9506dacb6414ed7bad2826d13c97020bed Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 6 Mar 2025 21:50:11 +0100 Subject: [PATCH 227/243] [coingecko] Make sure cache location exists --- coingecko/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 76f9f089..328a3189 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -91,7 +91,9 @@ def __init__(self): self.items = [] self.mtime = 0 - self.coinCacheFilePath = self.cacheLocation() / "coins.json" + cache_location = self.cacheLocation() + cache_location.mkdir(parents=True, exist_ok=True) + self.coinCacheFilePath = cache_location / "coins.json" self.thread = CoinFetcherThread(self.updateIndexItems, self.coinCacheFilePath) self.thread.start() From a8507e5920ad3e68d13c9d0a821c29f5b5c06cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Rag=C3=A1ny-N=C3=A9meth?= Date: Sat, 8 Mar 2025 19:41:08 +0100 Subject: [PATCH 228/243] [jetbrains_projects] Fix Rider config --- jetbrains_projects/__init__.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 3dbfcf2b..c25c13ef 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -56,11 +56,21 @@ class Editor: config_dir_prefix: str binary: str - def __init__(self, name: str, icon: Path, config_dir_prefix: str, binaries: list[str]): + # Rider calls recentProjects.xml -> recentSolutions.xml and in it RecentProjectsManager -> RiderRecentProjectsManager + is_rider: bool + + def __init__( + self, + name: str, + icon: Path, + config_dir_prefix: str, + binaries: list[str], + is_rider = False): self.name = name self.icon = icon self.config_dir_prefix = config_dir_prefix self.binary = self._find_binary(binaries) + self.is_rider = is_rider @staticmethod def _find_binary(binaries: list[str]) -> Union[str, None]: @@ -78,13 +88,20 @@ def list_projects(self) -> List[Project]: if not dirs: return [] latest = sorted(dirs)[-1] - return self._parse_recent_projects(Path(latest) / "options" / "recentProjects.xml") + if not self.is_rider: + recent_projects_xml = "recentProjects.xml" + else: + recent_projects_xml = "recentSolutions.xml" + return self._parse_recent_projects(Path(latest) / "options" / recent_projects_xml) @staticmethod def _parse_recent_projects(recent_projects_file: Path) -> list[Project]: try: root = ElementTree.parse(recent_projects_file).getroot() - entries = root.findall(".//component[@name='RecentProjectsManager']//entry[@key]") + if not self.is_rider: + entries = root.findall(".//component[@name='RecentProjectsManager']//entry[@key]") + else: + entries = root.findall(".//component[@name='RiderRecentProjectsManager']//entry[@key]") projects = [] for entry in entries: @@ -173,7 +190,8 @@ def __init__(self): name="Rider", icon=plugin_dir / "icons" / "rider.svg", config_dir_prefix="JetBrains/Rider", - binaries=["rider", "rider-eap"]), + binaries=["rider", "rider-eap"], + is_rider=True), Editor( name="RubyMine", icon=plugin_dir / "icons" / "rubymine.svg", From 145b5e97461505f2a6479e29d1c254e8c425030d Mon Sep 17 00:00:00 2001 From: Jose David Rueda Date: Wed, 26 Mar 2025 04:49:48 -0500 Subject: [PATCH 229/243] [bitwarden] v3.1 - cache `rbw list` response - add cache_timeout config prop --- bitwarden/__init__.py | 100 ++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 6e9e8031..3c8bf20e 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- +import time +from dataclasses import dataclass +from enum import Enum from pathlib import Path -from subprocess import run, CalledProcessError +from subprocess import CalledProcessError, run from albert import * md_iid = "3.0" -md_version = "3.0" +md_version = "3.1" md_name = "Bitwarden" md_description = "'rbw' wrapper extension" md_license = "MIT" @@ -14,8 +17,18 @@ md_authors = ["@ovitor", "@daviddeadly", "@manuelschneid3r"] md_bin_dependencies = ["rbw"] +MAX_MINUTES_CACHE_TIMEOUT = 60 +DEFAULT_MINUTE_CACHE_TIMEOUT = 5 + + +@dataclass(frozen=True) +class ConfigKeys: + CACHE_TIMEOUT = "cache_timeout" + class Plugin(PluginInstance, TriggerQueryHandler): + _cached_items = None + _last_fetch_time = 0 iconUrls = [f"file:{Path(__file__).parent}/bw.svg"] @@ -23,8 +36,36 @@ def __init__(self): PluginInstance.__init__(self) TriggerQueryHandler.__init__(self) + self.cache_timeout = ( + self.readConfig(ConfigKeys.CACHE_TIMEOUT, int) + or DEFAULT_MINUTE_CACHE_TIMEOUT + ) + def defaultTrigger(self): - return 'bw ' + return "bw " + + @property + def cache_timeout(self): + return int(self._cache_timeout / 60) + + @cache_timeout.setter + def cache_timeout(self, value): + self._cache_timeout = int(value * 60) + self.writeConfig(ConfigKeys.CACHE_TIMEOUT, value) + + def configWidget(self): + return [ + { + "type": "label", + "text": "Cache (result of `rbw list`) duration", + }, + { + "type": "spinbox", + "property": ConfigKeys.CACHE_TIMEOUT, + "label": f"Minutes: (max: {MAX_MINUTES_CACHE_TIMEOUT}, disable: 0)", + "widget_properties": {"maximum": MAX_MINUTES_CACHE_TIMEOUT}, + }, + ] def handleTriggerQuery(self, query): if query.string.strip().lower() == "sync": @@ -37,11 +78,9 @@ def handleTriggerQuery(self, query): Action( id="sync", text="Syncing Bitwarden Vault", - callable=lambda: run( - ["rbw", "sync"], - ) + callable=lambda: self._sync_vault(), ) - ] + ], ) ) @@ -56,30 +95,38 @@ def handleTriggerQuery(self, query): Action( id="copy", text="Copy password to clipboard", - callable=lambda item=p: self._password_to_clipboard(item) + callable=lambda item=p: self._password_to_clipboard(item), ), Action( id="copy-auth", text="Copy auth code to clipboard", - callable=lambda item=p: self._code_to_clipboard(item) + callable=lambda item=p: self._code_to_clipboard(item), ), Action( id="copy-username", text="Copy username to clipboard", - callable=lambda username=p["user"]: - setClipboardText(text=username) + callable=lambda username=p["user"]: setClipboardText( + text=username + ), ), Action( id="edit", text="Edit entry in terminal", - callable=lambda item=p: self._edit_entry(item) - ) - ] + callable=lambda item=p: self._edit_entry(item), + ), + ], ) ) - @staticmethod - def _get_items(): + def _get_items(self): + not_first_time = self._cached_items is not None + + time_passed = time.time() - self._last_fetch_time + is_chache_fresh = time_passed < self._cache_timeout + + if not_first_time and is_chache_fresh: + return self._cached_items + field_names = ["id", "name", "user", "folder"] raw_items = run( ["rbw", "list", "--fields", ",".join(field_names)], @@ -98,12 +145,16 @@ def _get_items(): item["path"] = item["folder"] + "/" + item["name"] else: item["path"] = item["name"] + items.append(item) + self._cached_items = items + self._last_fetch_time = time.time() + return items def _filter_items(self, query): - passwords = self._get_items() + passwords = self._get_items() or [] search_fields = ["path", "user"] # Use a set for faster membership tests words = set(query.string.strip().lower().split()) @@ -121,15 +172,18 @@ def _filter_items(self, query): return filtered_passwords + def _sync_vault(self): + run(["rbw", "sync"], check=True) + + self._cached_items = None + self._last_fetch_time = 0 + @staticmethod def _password_to_clipboard(item): rbw_id = item["id"] password = run( - ["rbw", "get", rbw_id], - capture_output=True, - encoding="utf-8", - check=True + ["rbw", "get", rbw_id], capture_output=True, encoding="utf-8", check=True ).stdout.strip() setClipboardText(text=password) @@ -143,14 +197,14 @@ def _code_to_clipboard(item): ["rbw", "code", rbw_id], capture_output=True, encoding="utf-8", - check=True + check=True, ).stdout.strip() except CalledProcessError as err: code = run( ["echo", err.__str__()], capture_output=True, encoding="utf-8", - check=True + check=True, ).stdout.strip() setClipboardText(text=code) From abd1c4490c7aab0da80e0b66870b7246a73956fb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Mar 2025 10:38:59 +0100 Subject: [PATCH 230/243] [coingecko] Use batch add to avoid flicker --- coingecko/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/coingecko/__init__.py b/coingecko/__init__.py index 328a3189..df23f20d 100644 --- a/coingecko/__init__.py +++ b/coingecko/__init__.py @@ -9,7 +9,7 @@ from threading import Thread, Event md_iid = "3.0" -md_version = "2.0" +md_version = "2.1" md_name = "CoinGecko" md_description = "Access CoinGecko" md_license = "MIT" @@ -133,6 +133,4 @@ def updateIndexItems(self): # override default trigger handling to sort by rank def handleTriggerQuery(self, query): m = Matcher(query.string) - for item in self.items: - if m.match(item.symbol, item.name): - query.add(item) + query.add([item for item in self.items if m.match(item.symbol, item.name)]) From a6cc30136e23bc622804c54fb63db82aa74603e7 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Mar 2025 12:00:48 +0100 Subject: [PATCH 231/243] [jetbrains_projects] Fix automerge issues --- jetbrains_projects/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index c25c13ef..a286ad38 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -94,8 +94,7 @@ def list_projects(self) -> List[Project]: recent_projects_xml = "recentSolutions.xml" return self._parse_recent_projects(Path(latest) / "options" / recent_projects_xml) - @staticmethod - def _parse_recent_projects(recent_projects_file: Path) -> list[Project]: + def _parse_recent_projects(self, recent_projects_file: Path) -> list[Project]: try: root = ElementTree.parse(recent_projects_file).getroot() if not self.is_rider: From cb2318242cec1691647f6486d2943cfb6ae9498f Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Mar 2025 12:01:42 +0100 Subject: [PATCH 232/243] [jetbrains_projects] Adopt to core trigger behavior --- jetbrains_projects/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index a286ad38..c3cdd029 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -236,15 +236,15 @@ def handleTriggerQuery(self, query: Query): # sort by last opened editor_project_pairs.sort(key=lambda pair: pair[1].last_opened, reverse=True) - query.add([self._make_item(editor, project, query) for editor, project in editor_project_pairs]) + query.add([self._make_item(editor, project) for editor, project in editor_project_pairs]) @staticmethod - def _make_item(editor: Editor, project: Project, query: Query) -> Item: + def _make_item(editor: Editor, project: Project) -> Item: return StandardItem( id="%s-%s-%s" % (editor.binary, project.path, project.last_opened), text=project.name, subtext=project.path, - inputActionText=query.trigger + project.name, + inputActionText=project.name, iconUrls=["file:" + str(editor.icon)], actions=[ Action( From 3aa6a1396291c8055a16c28a12ab09c9dd40962b Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Mar 2025 12:02:15 +0100 Subject: [PATCH 233/243] [jetbrains_projects] Add match path option Related: https://github.com/albertlauncher/python/pull/202 --- jetbrains_projects/__init__.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index c3cdd029..5e50e509 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -135,6 +135,10 @@ def __init__(self): self.fuzzy = False + self._match_path = self.readConfig('match_path', bool) + if self._match_path is None: + self._match_path = False + plugin_dir = Path(__file__).parent editors = [ Editor( @@ -214,6 +218,15 @@ def __init__(self): ] self.editors = [e for e in editors if e.binary is not None] + @property + def match_path(self): + return self._match_path + + @match_path.setter + def match_path(self, value): + self._match_path = value + self.writeConfig('match_path', value) + def supportsFuzzyMatching(self): return True @@ -230,8 +243,13 @@ def handleTriggerQuery(self, query: Query): for editor in self.editors: for project in editor.list_projects(): - if Path(project.path).exists() and m.match(project.name, project.path): - editor_project_pairs.append((editor, project)) + if Path(project.path).exists(): + if self._match_path: + if m.match(project.name, project.path): + editor_project_pairs.append((editor, project)) + else: + if m.match(project.name): + editor_project_pairs.append((editor, project)) # sort by last opened editor_project_pairs.sort(key=lambda pair: pair[1].last_opened, reverse=True) @@ -259,6 +277,11 @@ def _make_item(editor: Editor, project: Project) -> Item: def configWidget(self): return [ + { + 'type': 'checkbox', + 'property': 'match_path', + 'label': 'Match path' + }, { 'type': 'label', 'text': __doc__.strip(), From 8df351d6cfb232e0df5569d60c436e41bc3e4896 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 26 Mar 2025 12:10:04 +0100 Subject: [PATCH 234/243] [jetbrains_projects] 4.0 --- jetbrains_projects/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index 5e50e509..fc4ae84d 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -5,8 +5,8 @@ """ This plugin allows you to quickly open projects of the Jetbrains IDEs -- Aqua - Android Studio +- Aqua - CLion - DataGrip - DataSpell @@ -16,13 +16,12 @@ - PyCharm - Rider - RubyMine +- RustRover - WebStorm - Writerside. Note that for this plugin to find the IDEs, a commandline launcher in $PATH is required. Open the IDE and click Tools -> Create Command-line Launcher to add one. - -Disclaimer: This plugin has no affiliation with JetBrains s.r.o.. The icons are used under the terms specified here. """ from dataclasses import dataclass @@ -34,7 +33,7 @@ from albert import * md_iid = "3.0" -md_version = "3.0" +md_version = "4.0" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -200,16 +199,16 @@ def __init__(self): icon=plugin_dir / "icons" / "rubymine.svg", config_dir_prefix="JetBrains/RubyMine", binaries=["rubymine", "rubymine-eap", "jetbrains-rubymine", "jetbrains-rubymine-eap"]), - Editor( - name="WebStorm", - icon=plugin_dir / "icons" / "webstorm.svg", - config_dir_prefix="JetBrains/WebStorm", - binaries=["webstorm", "webstorm-eap"]), Editor( name="RustRover", icon=plugin_dir / "icons" / "rustrover.svg", config_dir_prefix="JetBrains/RustRover", binaries=["rustrover", "rustrover-eap"]), + Editor( + name="WebStorm", + icon=plugin_dir / "icons" / "webstorm.svg", + config_dir_prefix="JetBrains/WebStorm", + binaries=["webstorm", "webstorm-eap"]), Editor( name="Writerside", icon=plugin_dir / "icons" / "writerside.svg", From 9b696d4ec77e5bc71a4ea86b7fcd392753eee605 Mon Sep 17 00:00:00 2001 From: M Naufal Shidqi Date: Fri, 28 Mar 2025 20:38:59 +0700 Subject: [PATCH 235/243] [x_window_switcher] 0.6.0 --- x_window_switcher/__init__.py | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 x_window_switcher/__init__.py diff --git a/x_window_switcher/__init__.py b/x_window_switcher/__init__.py new file mode 100644 index 00000000..00c180b9 --- /dev/null +++ b/x_window_switcher/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import subprocess +from collections import namedtuple +from albert import * + +md_iid = "3.0" +md_version = "0.6.0" +md_name = "X Window Switcher" +md_description = "Switch X11 Windows" +md_license = "MIT" +md_url = "https://github.com/albertlauncher/python/tree/main/x_window_switcher" +md_bin_dependencies = "wmctrl" +md_authors = ["Ed Perez", "Manuel S.", "dshoreman", "nopsqi"] + +Window = namedtuple("Window", ["wid", "desktop", "wm_class", "host", "wm_name"]) + + +class Plugin(PluginInstance, TriggerQueryHandler): + def __init__(self): + PluginInstance.__init__(self) + TriggerQueryHandler.__init__(self) + + # Check for X session and wmctrl availability + try: + subprocess.check_call(["wmctrl", "-m"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + raise Exception("wmctrl not found. Please install wmctrl.") + except subprocess.CalledProcessError: + raise Exception("Unable to communicate with X11 window manager. This plugin requires a running X session.") + + def defaultTrigger(self): + return 'w ' + + def handleTriggerQuery(self, query): + try: + for line in subprocess.check_output(['wmctrl', '-l', '-x']).splitlines(): + win = Window(*parseWindow(line)) + + if win.desktop == "-1": + continue + + win_instance, win_class = win.wm_class.replace(' ', '-').split('.', 1) + + m = Matcher(query.string) + if not query.string or m.match(win_instance + ' ' + win_class + ' ' + win.wm_name): + query.add(StandardItem( + id="%s%s" % (md_name, win.wm_class), + iconUrls=["xdg:%s" % win_instance], + text="%s - Desktop %s" % (win_class.replace('-', ' '), win.desktop), + subtext=win.wm_name, + actions=[Action("switch", + "Switch Window", + lambda w=win: runDetachedProcess(["wmctrl", '-i', '-a', w.wid])), + Action("move", + "Move window to this desktop", + lambda w=win: runDetachedProcess(["wmctrl", '-i', '-R', w.wid])), + Action("close", + "Close the window gracefully.", + lambda w=win: runDetachedProcess(["wmctrl", '-c', w.wid]))] + )) + except subprocess.CalledProcessError as e: + warning(f"Error executing wmctrl: {str(e)}") + + +def parseWindow(line): + win_id, desktop, rest = line.decode().split(None, 2) + win_class, rest = rest.split(' ', 1) + host, title = rest.strip().split(None, 1) + + return [win_id, desktop, win_class, host, title] From 53c4bdda1c4edcef226159deef4279739414fda0 Mon Sep 17 00:00:00 2001 From: mqus <8398165+mqus@users.noreply.github.com> Date: Fri, 28 Mar 2025 14:40:19 +0100 Subject: [PATCH 236/243] [jetbrains_projects] Add pycharm-professional to binary list --- jetbrains_projects/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index fc4ae84d..06229396 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -33,7 +33,7 @@ from albert import * md_iid = "3.0" -md_version = "4.0" +md_version = "4.1" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "MIT" @@ -187,7 +187,7 @@ def __init__(self): name="PyCharm", icon=plugin_dir / "icons" / "pycharm.svg", config_dir_prefix="JetBrains/PyCharm", - binaries=["charm", "pycharm", "pycharm-eap"]), + binaries=["charm", "pycharm", "pycharm-eap", "pycharm-professional"]), Editor( name="Rider", icon=plugin_dir / "icons" / "rider.svg", From 79969a6087322a4944be266c4e738e81d725a1e3 Mon Sep 17 00:00:00 2001 From: MagneFire Date: Sat, 21 Jun 2025 23:11:56 +0200 Subject: [PATCH 237/243] [bitwarden] Fix freeze when triggering bitwarden (#221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A freeze can be observed when triggering the bitwarden plugin. This seems to occur the second time `query.add` is called. To fix this issue the same logic as the `kill` plugin is used: Add all items to a local array before finally adding this using `query.add` This fixes https://github.com/albertlauncher/python/issues/215 Signed-off-by: Darrel Griët --- bitwarden/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bitwarden/__init__.py b/bitwarden/__init__.py index 3c8bf20e..87afe63a 100644 --- a/bitwarden/__init__.py +++ b/bitwarden/__init__.py @@ -68,8 +68,9 @@ def configWidget(self): ] def handleTriggerQuery(self, query): + results = [] if query.string.strip().lower() == "sync": - query.add( + results.append( StandardItem( id="sync", text="Sync Bitwarden Vault", @@ -85,7 +86,7 @@ def handleTriggerQuery(self, query): ) for p in self._filter_items(query): - query.add( + results.append( StandardItem( id=p["id"], text=p["path"], @@ -118,6 +119,8 @@ def handleTriggerQuery(self, query): ) ) + query.add(results) + def _get_items(self): not_first_time = self._cached_items is not None From a57b6e6ec6c585c3c41e866c1e634b68a104bf58 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 30 Jun 2025 15:27:52 +0200 Subject: [PATCH 238/243] [translators] Improve description --- translators/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translators/__init__.py b/translators/__init__.py index 862f427f..45541e8b 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -13,9 +13,9 @@ import translators as ts md_iid = "3.0" -md_version = "2.0" +md_version = "2.1" md_name = "Translator" -md_description = "Translate sentences using 'translators' package" +md_description = "Translate text using online translators" md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/translators" md_authors = "@manuelschneid3r" From cb6326caa62b092634e6cc686276bfd7dd62c850 Mon Sep 17 00:00:00 2001 From: Sharsie Date: Mon, 30 Jun 2025 15:56:44 +0200 Subject: [PATCH 239/243] [vscode_projects] Add plugin Provide a search of VSCode recent files and its Project Manager extension * [vscode_projects:1.1] Upgrade the interface version to 2.2 * [vscode_projects:1.2] Always add action to open workdir through VSCode If terminal command is specified, it becomes the default action while allowing the user to still open the workdir using default VSCode action without running through terminal * [vscode_projects:1.3] Use new Matcher introduced in interface version 2.3 * [vscode_projects:1.3] Remove unnecessary slashes in the icons url * [vscode_projects:1.3] Use cached iconUrls when building the standard item result * [vscode_projects:1.4] Normalize paths. Resolve symlinks to make sure only unique results are returned * [vscode_projects:1.5] Update to interface version 2.4 * [vscode_projects:1.6] Provide project tags in the subtext * [vscode_projects:1.6] Update plugin to interface version 3 * [vscode_projects:1.7] Fix caching and action text * [vscode_projects:1.8] Drop string normalization in favor of native Matcher * [vscode_projects:1.9] Optimize adding items to query --- vscode_projects/__init__.py | 550 ++++++++++++++++++++++++++++++++++++ vscode_projects/icon.svg | 41 +++ 2 files changed, 591 insertions(+) create mode 100644 vscode_projects/__init__.py create mode 100644 vscode_projects/icon.svg diff --git a/vscode_projects/__init__.py b/vscode_projects/__init__.py new file mode 100644 index 00000000..9f8690b6 --- /dev/null +++ b/vscode_projects/__init__.py @@ -0,0 +1,550 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Sharsie + +import os +import json +from pathlib import Path +from dataclasses import dataclass +from albert import * + +md_iid = "3.0" +md_version = "1.9" +md_name = "VSCode projects" +md_description = "Open VSCode projects" +md_url = "https://github.com/albertlauncher/python/tree/master/vscode_projects" +md_license = "MIT" +md_bin_dependencies = ["code"] +md_authors = ["@Sharsie"] + +@dataclass +class Project: + displayName: str + name: str + path: str + tags: list[str] + + +@dataclass +class SearchResult: + project: Project + # priority is used to sort returned results + priority: int + # sortIndex is a decision maker when two search results have same priority + sortIndex: int + + +@dataclass +class CachedConfig: + projects: list[Project] + mTime: float + + +class Plugin(PluginInstance, TriggerQueryHandler): + # Possible locations for Code configuration + _configStoragePaths = [ + os.path.join(os.environ["HOME"], ".config/Code/storage.json"), + os.path.join(os.environ["HOME"], + ".config/Code/User/globalStorage/storage.json"), + ] + + # Possible locations for Project Manager extension configuration + _configProjectManagerPaths = [ + os.path.join( + os.environ["HOME"], ".config/Code/User/globalStorage/alefragnani.project-manager/projects.json") + ] + + # Indicates whether results from the Recent list in VSCode should be searched + _recentEnabled = True + + # Indicates whether projects from Project Manager extension should be searched + _projectManagerEnabled = False + + # Defines sorting priorities for results + _sortPriority = { + "PMName": 1, + "PMPath": 5, + "PMTag": 10, + "Recent": 15 + } + + # Holds cached data from the json configurations + _configCache: dict[str, CachedConfig] = {} + + # Overrides the command to open projects + _terminalCommand = "" + + # Setting indicating whether results from the Recent list in VSCode should be searched + @property + def recentEnabled(self): + return self._recentEnabled + + @recentEnabled.setter + def recentEnabled(self, value): + self._recentEnabled = value + self.writeConfig("recentEnabled", value) + + # Setting indicating whether projects in Project Manager extension should be searched + @property + def projectManagerEnabled(self): + return self._projectManagerEnabled + + @projectManagerEnabled.setter + def projectManagerEnabled(self, value): + self._projectManagerEnabled = value + self.writeConfig("projectManagerEnabled", value) + + found = False + for p in self._configProjectManagerPaths: + if os.path.exists(p): + found = True + break + + if found == False: + warning( + "Project Manager search was enabled, but configuration file was not found") + notif = Notification( + title=f"{self.name}", + text=f"Configuration file was not found for the Project Manager extension. Please make sure the extension is installed." + ) + notif.send() + + # Priority settings for project manager results using name search + @property + def priorityPMName(self): + return self._sortPriority["PMName"] + + @priorityPMName.setter + def priorityPMName(self, value): + self._sortPriority["PMName"] = value + self.writeConfig("priorityPMName", value) + + # Priority settings for project manager results using path search + @property + def priorityPMPath(self): + return self._sortPriority["PMPath"] + + @priorityPMPath.setter + def priorityPMPath(self, value): + self._sortPriority["PMPath"] = value + self.writeConfig("priorityPMPath", value) + + # Priority settings for project manager results using tag search + @property + def priorityPMTag(self): + return self._sortPriority["PMTag"] + + @priorityPMTag.setter + def priorityPMTag(self, value): + self._sortPriority["PMTag"] = value + self.writeConfig("priorityPMTag", value) + + # Priority settings for recently opened files + @property + def priorityRecent(self): + return self._sortPriority["Recent"] + + @priorityRecent.setter + def priorityRecent(self, value): + self._sortPriority["Recent"] = value + self.writeConfig("priorityRecent", value) + + # Setting for custom command when opening resulted items + @property + def terminalCommand(self): + return self._terminalCommand + + @terminalCommand.setter + def terminalCommand(self, value): + self._terminalCommand = value + self.writeConfig("terminalCommand", value) + + def defaultTrigger(self): + return "code " + + def synopsis(self, query): + return "project name or path" + + def __init__(self): + self.iconUrls = [f"file:{Path(__file__).parent}/icon.svg"] + + PluginInstance.__init__(self) + + TriggerQueryHandler.__init__(self) + + configFound = False + + for p in self._configStoragePaths: + if os.path.exists(p): + configFound = True + break + + if not configFound: + warning("Could not find any VSCode configuration directory") + + self._initConfiguration() + + def configWidget(self): + return [ + { + "type": "label", + "text": """Recent files are sorted in order found in the VSCode configuration. +Sort order with Project Manager can be adjusted, lower number = higher priority = displays first. +With all priorities equal, PM results will take precedence over recents.""" + }, + { + "type": "label", + "text": """ +PM extension: https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager +""" + }, + + { + "type": "checkbox", + "property": "recentEnabled", + "label": "Search in Recent files" + }, + { + "type": "checkbox", + "property": "projectManagerEnabled", + "label": "Search in Project Manager extension" + }, + { + "type": "spinbox", + "property": "priorityPMName", + "label": "Priority: Project Manager entries matched by name", + "widget_properties": { + "minimum": 1, + "maximum": 99, + }, + }, + { + "type": "spinbox", + "property": "priorityPMPath", + "label": "Priority: Project Manager entries matched by path", + "widget_properties": { + "minimum": 1, + "maximum": 99, + }, + }, + { + "type": "spinbox", + "property": "priorityPMTag", + "label": "Priority: Project Manager entries matched by tag", + "widget_properties": { + "minimum": 1, + "maximum": 99, + }, + }, + { + "type": "spinbox", + "property": "priorityRecent", + "label": "Priority: Recent entries", + "widget_properties": { + "minimum": 1, + "maximum": 99, + }, + }, + { + "type": "label", + "text": """ +The way VSCode is opened can be overriden through terminal command. +Terminal will enter the working directory of the project upon selection, execute the command and then close itself. + +Usecase with direnv - To load direnv environment before opening VSCode, enter the following custom command: direnv exec . code . + +Usecase with single VSCode instance - To reuse the VSCode window instead of opening a new one, enter the following custom command: code -r .""" + }, + { + "type": "lineedit", + "property": "terminalCommand", + "label": "Run custom command in the workdir of selected item" + }, + ] + + def _initConfiguration(self): + # Recent search + recentEnabled = self.readConfig('recentEnabled', bool) + if recentEnabled is None: + self._recentEnabled = True + self.writeConfig("recentEnabled", True) + else: + self._recentEnabled = recentEnabled + + # Project Manager search + foundPM = False + for p in self._configProjectManagerPaths: + if os.path.exists(p): + foundPM = True + break + + projectManagerEnabled = self.readConfig('projectManagerEnabled', bool) + if projectManagerEnabled is None: + # If not configured, check if the project manager configuration file exists and if so, enable PM search + if foundPM: + self._projectManagerEnabled = True + self.writeConfig("projectManagerEnabled", True) + else: + self._projectManagerEnabled = False + else: + self._projectManagerEnabled = projectManagerEnabled + + # Priority settings + for p in self._sortPriority: + prio = self.readConfig(f"priority{p}", int) + if prio is None: + self.writeConfig(f"priority{p}", self._sortPriority[p]) + else: + self._sortPriority[p] = prio + + # Terminal command setting + terminalCommand = self.readConfig('terminalCommand', str) + if terminalCommand is not None: + self._terminalCommand = terminalCommand + + def handleTriggerQuery(self, query): + if not query.isValid: + return + + if query.string == "": + return + + matcher = Matcher(query.string) + + results: dict[str, SearchResult] = {} + + if self.recentEnabled: + results = self._searchInRecentFiles(matcher, results) + + if self.projectManagerEnabled: + results = self._searchInProjectManager(matcher, results) + + sortedItems = sorted(results.values(), key=lambda item: "%s_%s_%s" % ( + '{:03d}'.format(item.priority), '{:03d}'.format(item.sortIndex), item.project.name), reverse=False) + + items: list[StandardItem] = [] + for i in sortedItems: + items.append(self._createItem(i.project, query)) + + query.add(items) + + # Creates an item for the query based on the project and plugin settings + def _createItem(self, project: Project, query: Query) -> StandardItem: + actions: list[Action] = [] + + if self.terminalCommand != "": + actions.append( + Action( + id="open-terminal", + text=f"Run terminal command in project's workdir: {self.terminalCommand}", + callable=lambda: runTerminal(f"cd {project.path} && {self.terminalCommand}") + ) + ) + + actions.append( + Action( + id="open-code", + text="Open with VSCode", + callable=lambda: runDetachedProcess( + ["code", project.path]), + ) + ) + + subtext = "" + + if len(project.tags) > 0: + subtext = "<" + ",".join(project.tags) + "> " + + return StandardItem( + id=project.path, + text=project.displayName, + subtext=f"{subtext}{project.path}", + iconUrls=self.iconUrls, + inputActionText=f"{query.trigger}{project.displayName}", + actions=actions, + ) + + def _searchInRecentFiles(self, matcher: Matcher, results: dict[str, SearchResult]) -> dict[str, SearchResult]: + sortIndex = 1 + + for path in self._configStoragePaths: + c = self._getStorageConfig(path) + for proj in c.projects: + # Resolve sym links to get unique results + resolvedPath = str(Path(proj.path).resolve()) + if matcher.match(proj.name) or matcher.match(proj.path) or matcher.match(resolvedPath): + results[resolvedPath] = self._getHigherPriorityResult( + SearchResult( + project=proj, + priority=self.priorityRecent, + sortIndex=sortIndex + ), + results.get(resolvedPath), + ) + + if results.get(resolvedPath) is not None: + sortIndex += 1 + + return results + + def _searchInProjectManager(self, matcher: Matcher, results: dict[str, SearchResult]) -> dict[str, SearchResult]: + for path in self._configProjectManagerPaths: + c = self._getProjectManagerConfig(path) + for proj in c.projects: + # Resolve sym links to get unique results + resolvedPath = str(Path(proj.path).resolve()) + if matcher.match(proj.name): + results[resolvedPath] = self._getHigherPriorityResult( + SearchResult( + project=proj, + priority=self.priorityPMName, + sortIndex=0 if matcher.match(proj.name).isExactMatch() else 1 + ), + results.get(resolvedPath), + ) + + if matcher.match(proj.path) or matcher.match(resolvedPath): + results[resolvedPath] = self._getHigherPriorityResult( + SearchResult( + project=proj, + priority=self.priorityPMPath, + sortIndex=1 + ), + results.get(resolvedPath), + ) + + for tag in proj.tags: + if matcher.match(tag): + results[resolvedPath] = self._getHigherPriorityResult( + SearchResult( + project=proj, + priority=self.priorityPMTag, + sortIndex=1 + ), + results.get(resolvedPath), + ) + break + + return results + + # Compares the search results to return the one with higher priority + # For nitpickers: higher priorty = lower number + def _getHigherPriorityResult(self, current: SearchResult, prev: SearchResult | None) -> SearchResult: + if prev is None or current.priority < prev.priority or (current.priority == prev.priority and current.sortIndex < prev.sortIndex): + return current + + return prev + + def _getStorageConfig(self, path: str) -> CachedConfig: + c: CachedConfig = self._configCache.get(path, CachedConfig([], 0)) + + if not os.path.exists(path): + return c + + mTime = os.stat(path).st_mtime + + if mTime == c.mTime: + return c + + c.mTime = mTime + + with open(path) as configFile: + # Load the storage json + storageConfig = json.loads(configFile.read()) + + if ( + "lastKnownMenubarData" in storageConfig + and "menus" in storageConfig["lastKnownMenubarData"] + and "File" in storageConfig["lastKnownMenubarData"]["menus"] + and "items" in storageConfig["lastKnownMenubarData"]["menus"]["File"] + ): + # These are all the menu items in File dropdown + for menuItem in storageConfig["lastKnownMenubarData"]["menus"]["File"]["items"]: + # Cannot safely detect proper menu item, as menu item IDs change over time + # Instead we will search all submenus and check for IDs inside the submenu items + if ( + not "id" in menuItem + or not "submenu" in menuItem + or not "items" in menuItem["submenu"] + ): + continue + + for submenuItem in menuItem["submenu"]["items"]: + # Check of submenu item with id "openRecentFolder" and make sure it contains necessarry keys + if ( + not "id" in submenuItem + or submenuItem['id'] != "openRecentFolder" + or not "enabled" in submenuItem + or submenuItem["enabled"] != True + or not "label" in submenuItem + or not "uri" in submenuItem + or not "path" in submenuItem["uri"] + ): + continue + + # Get the full path to the project + recentPath = submenuItem["uri"]["path"] + if not os.path.exists(recentPath): + continue + + displayName = recentPath.split("/")[-1] + + # Inject the project + c.projects.append(Project( + displayName=displayName, + name=displayName, + path=recentPath, + tags=[], + )) + + self._configCache[path] = c + + return c + + def _getProjectManagerConfig(self, path: str) -> CachedConfig: + c = self._configCache.get(path, CachedConfig([], 0)) + + if not os.path.exists(path): + return c + + mTime = os.stat(path).st_mtime + + if mTime == c.mTime: + return c + + c.mTime = mTime + + with open(path) as configFile: + configuredProjects = json.loads(configFile.read()) + + for p in configuredProjects: + # Make sure we have necessarry keys + if ( + not "rootPath" in p + or not "name" in p + or not "enabled" in p + or p["enabled"] != True + ): + continue + + # Grab the path to the project + rootPath = p["rootPath"] + if os.path.exists(rootPath) == False: + continue + + project = Project( + displayName=p["name"], + name=p["name"], + path=rootPath, + tags=[], + ) + + # Search against the query string + if "tags" in p: + for tag in p["tags"]: + project.tags.append(tag) + + c.projects.append(project) + + self._configCache[path] = c + + return c diff --git a/vscode_projects/icon.svg b/vscode_projects/icon.svg new file mode 100644 index 00000000..c453e633 --- /dev/null +++ b/vscode_projects/icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ab649e7159285baa307802ee0dba53812502f807 Mon Sep 17 00:00:00 2001 From: Sharsie Date: Mon, 7 Jul 2025 23:15:47 +0200 Subject: [PATCH 240/243] [vscode_projects:1.10] Fixes duplicated trigger in query auto completion (#224) --- vscode_projects/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vscode_projects/__init__.py b/vscode_projects/__init__.py index 9f8690b6..7ed263c9 100644 --- a/vscode_projects/__init__.py +++ b/vscode_projects/__init__.py @@ -8,7 +8,7 @@ from albert import * md_iid = "3.0" -md_version = "1.9" +md_version = "1.10" md_name = "VSCode projects" md_description = "Open VSCode projects" md_url = "https://github.com/albertlauncher/python/tree/master/vscode_projects" @@ -103,7 +103,7 @@ def projectManagerEnabled(self, value): warning( "Project Manager search was enabled, but configuration file was not found") notif = Notification( - title=f"{self.name}", + title=self.name, text=f"Configuration file was not found for the Project Manager extension. Please make sure the extension is installed." ) notif.send() @@ -359,7 +359,7 @@ def _createItem(self, project: Project, query: Query) -> StandardItem: text=project.displayName, subtext=f"{subtext}{project.path}", iconUrls=self.iconUrls, - inputActionText=f"{query.trigger}{project.displayName}", + inputActionText=project.displayName, actions=actions, ) From 7b0c53260efb6c881736c1c33268bc630102ef39 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 17 Jul 2025 12:33:43 +0200 Subject: [PATCH 241/243] [syncthing] v3 - Drop PyPi dependendencies `syncthing` is broken for years. `syncthing2` simply fixes the silly dependency issue but is still heavily outdated. - Add pause/resume folder action (was not available in the PyPi pkgs) - Show exceptions as item in trigger query handler - Visualize paused devices/folders with desaturated icons --- syncthing/__init__.py | 228 ++++++++++++++++++------------- syncthing/syncthing.svg | 26 ---- syncthing/syncthing_active.svg | 1 + syncthing/syncthing_inactive.svg | 1 + 4 files changed, 137 insertions(+), 119 deletions(-) delete mode 100644 syncthing/syncthing.svg create mode 100644 syncthing/syncthing_active.svg create mode 100644 syncthing/syncthing_inactive.svg diff --git a/syncthing/__init__.py b/syncthing/__init__.py index ce4cb3e7..6fde0df1 100644 --- a/syncthing/__init__.py +++ b/syncthing/__init__.py @@ -1,59 +1,100 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 Manuel Schneider -""" -Quickly pause/resume/open/scan shares and devices. -""" - +import json +import urllib.error +import urllib.request from pathlib import Path from albert import * -from syncthing import Syncthing md_iid = "3.0" -md_version = "2.0" +md_version = "3.0" md_name = "Syncthing" -md_description = "Trigger basic syncthing actions." +md_description = "Control the local Syncthing instance." md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/syncthing" md_authors = "@manuelschneid3r" -md_lib_dependencies = "syncthing2" + + +# https://docs.syncthing.net/dev/rest.html +class Syncthing: + def __init__(self, api_key, base_url="http://localhost:8384"): + self.api_key = api_key + self.base_url = base_url + + def _request(self, method, endpoint, data=None) -> dict: + url = f"{self.base_url}{endpoint}" + headers = { + "X-API-Key": self.api_key, + "Content-Type": "application/json" + } + body = json.dumps(data).encode("utf-8") if data else None + req = urllib.request.Request(url, data=body, headers=headers, method=method) + + try: + with urllib.request.urlopen(req) as resp: + if not (200 <= resp.status < 300): + raise Exception(f"Unexpected status {resp.status}") + content = resp.read().decode() + return json.loads(content) if content else {} + except urllib.error.HTTPError as e: + raise Exception(f"HTTP {e.code}: {e.read().decode()}") + + def _get(self, endpoint): + return self._request("GET", endpoint) + + def _post(self, endpoint, data=None): + return self._request("POST", endpoint, data) + + def _patch(self, endpoint, data=None): + return self._request("PATCH", endpoint, data) + + def config(self): + return self._get('/rest/config') + + def resumeDevice(self, device_id:str): + return self._patch(f'/rest/config/devices/{device_id}', {'paused': False}) + + def pauseDevice(self, device_id:str): + return self._patch(f'/rest/config/devices/{device_id}', {'paused': True}) + + def resumeFolder(self, folder_id:str): + return self._patch(f'/rest/config/folders/{folder_id}', {'paused': False}) + + def pauseFolder(self, folder_id:str): + return self._patch(f'/rest/config/folders/{folder_id}', {'paused': True}) + + def scanFolder(self, folder_id:str): + return self._post(f'/rest/db/scan?folder={folder_id}') class Plugin(PluginInstance, GlobalQueryHandler): config_key = 'syncthing_api_key' + icon_urls_active = [f"file:{Path(__file__).parent}/syncthing_active.svg"] + icon_urls_inactive = [f"file:{Path(__file__).parent}/syncthing_inactive.svg"] def __init__(self): PluginInstance.__init__(self) GlobalQueryHandler.__init__(self) - - self.iconUrls = ["xdg:syncthing", f"file:{Path(__file__).parent}/syncthing.svg"] - self._api_key = self.readConfig(self.config_key, str) - if self._api_key: - self.st = Syncthing(self._api_key) + self.st = Syncthing(self.readConfig(self.config_key, str) or '') def defaultTrigger(self): return 'st ' @property def api_key(self) -> str: - return self._api_key + return self.st.api_key @api_key.setter def api_key(self, value: str): - if self._api_key != value: - self._api_key = value + if self.st.api_key != value: + self.st.api_key = value self.writeConfig(self.config_key, value) - self.st = Syncthing(self._api_key) - def configWidget(self): return [ - { - 'type': 'label', - 'text': __doc__.strip(), - }, { 'type': 'lineedit', 'property': 'api_key', @@ -62,79 +103,80 @@ def configWidget(self): } ] + def handleTriggerQuery(self, query): + try: + super().handleTriggerQuery(query) + except Exception as e: + query.add(StandardItem(id="err", text="Error", subtext=str(e), iconUrls=self.icon_urls_active)) + def handleGlobalQuery(self, query): - results = [] + config = self.st.config() + + devices = dict() + for d in config['devices']: + if not d['name']: + d['name'] = d['deviceID'] + d['_shared_folders'] = {} + devices[d['deviceID']] = d - if self.st: - - config = self.st.system.config() - - devices = dict() - for d in config['devices']: - if not d['name']: - d['name'] = d['deviceID'] - d['_shared_folders'] = {} - devices[d['deviceID']] = d - - folders = dict() - for f in config['folders']: - if not f['label']: - f['label'] = f['id'] - for d in f['devices']: - devices[d['deviceID']]['_shared_folders'][f['id']] = f - folders[f['id']] = f - - matcher = Matcher(query.string) - - # create device items - for device_id, d in devices.items(): - device_name = d['name'] - - if match := matcher.match(device_name): - device_folders = ", ".join([f['label'] for f in d['_shared_folders'].values()]) - - actions = [] - if d['paused']: - actions.append( - Action("resume", "Resume synchronization", - lambda did=device_id: self.st.system.resume(did)) - ) - else: - actions.append( - Action("pause", "Pause synchronization", - lambda did=device_id: self.st.system.pause(did)) - ) - - item = StandardItem( - id=device_id, - text=f"{device_name}", - subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " - f"Shared: {device_folders if device_folders else 'Nothing'}.", - iconUrls=self.iconUrls, - actions=actions - ) - - results.append(RankItem(item, match)) - - # create folder items - for folder_id, f in folders.items(): - folder_name = f['label'] - if match := matcher.match(folder_name): - folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) - item = StandardItem( - id=folder_id, - text=folder_name, - subtext=f"Syncthing folder {f['path']}. " - f"Shared with {folders_devices if folders_devices else 'nobody'}.", - iconUrls=self.iconUrls, - actions=[ - Action("scan", "Scan the folder", - lambda fid=folder_id: self.st.database.scan(fid)), - Action("open", "Open this folder in file browser", - lambda p=f['path']: openFile(p)) - ] - ) - results.append(RankItem(item, match)) + folders = dict() + for f in config['folders']: + if not f['label']: + f['label'] = f['id'] + for d in f['devices']: + devices[d['deviceID']]['_shared_folders'][f['id']] = f + folders[f['id']] = f + + results = [] + matcher = Matcher(query.string) + + # create device items + for device_id, d in devices.items(): + device_name = d['name'] + + if match := matcher.match(device_name): + device_folders = ", ".join([f['label'] for f in d['_shared_folders'].values()]) + + actions = [] + if d['paused']: + actions.append(Action("resume", "Resume", lambda did=device_id: self.st.resumeDevice(did))) + else: + actions.append(Action("pause", "Pause", lambda did=device_id: self.st.pauseDevice(did))) + + item = StandardItem( + id=device_id, + text=f"{device_name}", + subtext=f"{'PAUSED · ' if d['paused'] else ''}Device · " + f"Shared: {device_folders if device_folders else 'Nothing'}.", + iconUrls=self.icon_urls_inactive if d['paused'] else self.icon_urls_active, + actions=actions + ) + + results.append(RankItem(item, match)) + + # create folder items + for folder_id, f in folders.items(): + folder_name = f['label'] + if match := matcher.match(folder_name): + folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) + + actions = [] + if f['paused']: + actions.append(Action("resume", "Resume", lambda fid=folder_id: self.st.resumeFolder(fid))) + else: + actions.append(Action("pause", "Pause", lambda fid=folder_id: self.st.pauseFolder(fid))) + actions.append(Action("open", "Open", lambda p=f['path']: openFile(p))) + actions.append(Action("scan", "Scan", lambda fid=folder_id: self.st.scanFolder(fid))) + + item = StandardItem( + id=folder_id, + text=folder_name, + subtext=f"{'PAUSED · ' if f['paused'] else ''}Folder · {f['path']} · " + f"Shared with {folders_devices if folders_devices else 'nobody'}.", + iconUrls=self.icon_urls_inactive if f['paused'] else self.icon_urls_active, + actions=actions + ) + results.append(RankItem(item, match)) return results diff --git a/syncthing/syncthing.svg b/syncthing/syncthing.svg deleted file mode 100644 index ce92210f..00000000 --- a/syncthing/syncthing.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/syncthing/syncthing_active.svg b/syncthing/syncthing_active.svg new file mode 100644 index 00000000..b56b7cbc --- /dev/null +++ b/syncthing/syncthing_active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/syncthing/syncthing_inactive.svg b/syncthing/syncthing_inactive.svg new file mode 100644 index 00000000..247b4d43 --- /dev/null +++ b/syncthing/syncthing_inactive.svg @@ -0,0 +1 @@ + \ No newline at end of file From f10c7895f665ed389203127c99e94aa3cf9065bf Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 25 Jul 2025 14:03:09 +0200 Subject: [PATCH 242/243] Split modules --- README.md | 12 ++--- history_to_submodules.sh | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100755 history_to_submodules.sh diff --git a/README.md b/README.md index b95c05b6..3d0aeaf3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ -## Official Albert Python plugin repository +# ⚠️ ARCHIVED ⚠️ -These plugins are shipped with the app. +The plugins in this repository have been moved to dedicated repositories using `history_to_submodules.sh`. -Visit the website to learn [how to write plugins](https://albertlauncher.github.io/gettingstarted/extension/). - -Credits go to our contributors: - - - - +See https://github.com/orgs/albertlauncher/repositories. diff --git a/history_to_submodules.sh b/history_to_submodules.sh new file mode 100755 index 00000000..054cc79c --- /dev/null +++ b/history_to_submodules.sh @@ -0,0 +1,106 @@ +#! /usr/bin/env bash + +#set -x +set -e + +# Find history dirs +# git log --pretty=format: --name-only | grep -E '^(.archive/)?[^/]+' -o | sort -u | uniq + +git clone --bare git@github.com:albertlauncher/python.git .bare || true + +function filter ( + + if [[ $# -lt 2 ]]; then + echo "Error: At least two arguments required." + return 1 + fi + + local repo_name="$(echo $1 | tr '_' '-')" + local filter_path="$2" + shift 2 + + declare -a additional_filter_paths + if [[ $# -gt 0 ]]; then + additional_filter_paths=$(printf -- "--path %s " "$@") + fi + + echo "ℹ️" $repo_name $filter_path $additional_filter_paths + + git clone .bare $repo_name + cd $repo_name + git filter-repo --subdirectory-filter ${filter_path} ${additional_filter_paths[@]} + + gh repo create "albertlauncher/albert-plugin-python-$repo_name" --public --disable-wiki + git remote add origin "git@github.com:albertlauncher/albert-plugin-python-$repo_name.git" + git push -f --set-upstream origin main + open "https://github.com/albertlauncher/albert-plugin-python-$repo_name" + + cd .. +) + + +filter arch_wiki arch_wiki .archive/arch_wiki ArchWiki +filter atom_projects atom_projects atom_projects.py .archive/atom_projects AtomProjects.py +filter aur aur .archive/aur AUR +filter base_converter base_converter base_converter.py .archive/base_converter BaseConverter.py +filter binance binance .archive/binance Binance +filter bitfinex bitfinex .archive/bitfinex Bitfinex +filter bitwarden bitwarden bitwarden.py .archive/bitwarden +filter coingecko coingecko +filter coinmarketcap coinmarketcap .archive/coinmarketcap CoinMarketCap +filter color color +filter copyq copyq copyq.py .archive/copyq CopyQ.py +filter currency_converter currency_converter currency_converter.py .archive/currency_converter Currency.py +filter dango_emoji dango_emoji .archive/dango_emoji dangoemoji +filter dango_kao dango_kao .archive/dango_kao dangokao +filter datetime datetime datetime.py .archive/datetime DateTime.py epoch Epoch.py +filter dice_roll dice_roll .archive/dice_roll +filter docker docker .archive/docker +filter duckduckgo duckduckgo +filter emoji emoji EmojiPicker unicode_emoji .archive/unicode_emoji +filter find find find .archive/find +filter fortune fortune fortune.py .archive/fortune Fortune.py +filter gnome_dictionary gnome_dictionary gnome_dictionary.py .archive/gnome_dictionary GnomeDictionary.py +filter gnote gnote gnote.py .archive/gnote Gnote.py +filter goldendict goldendict goldendict.py .archive/goldendict GoldenDict.py +filter google_translate google_translate google_translate.py .archive/google_translate GoogleTranslate.py +filter googletrans googletrans .archive/googletrans +filter inhibit_sleep inhibit_sleep .archive/inhibit_sleep +filter ip ip ip.py .archive/ip Ip.py +filter jetbrains_projects jetbrains_projects jetbrains-projects.py .archive/jetbrains_projects JetbrainsProjects +filter kill kill kill.py .archive/kill Kill.py +filter locate locate locate.py .archive/locate Locate.py +filter lpass lpass .archive/lpass +filter mathematica_eval mathematica_eval mathematica_eval.py .archive/mathematica_eval +filter multi_google_translate multi_google_translate multi_google_translate.py .archive/multi_google_translate MultiGoogleTranslate.py +filter node_eval node_eval .archive/node_eval +filter npm npm .archive/npm Npm +filter packagist packagist .archive/packagist Packagist +filter pacman pacman pacman.py .archive/pacman Pacman.py +filter pass pass pass.py .archive/pass Pass.py +filter php_eval php_eval .archive/php_eval +filter pidgin pidgin pidgin.py .archive/pidgin Pidgin.py +filter pomodoro pomodoro .archive/pomodoro Pomodoro +filter python_eval python_eval python Python +filter rand rand .archive/rand +filter scrot scrot scrot.py Scrot.py .archive/scrot +filter syncthing syncthing +filter tex_to_unicode tex_to_unicode tex_to_unicode.py .archive/tex_to_unicode +filter texdoc texdoc .archive/texdoc +filter timer timer Timer .archive/timer +filter tomboy tomboy tomboy.py Tomboy.py .archive/tomboy +filter translators translators +filter trash trash trash.py Trash.py .archive/trash +filter unit_converter unit_converter .archive/unit_converter +filter units units units.py Units.py .archive/units +filter virtualbox virtualbox virtualbox.py VirtualBox.py .archive/virtualbox +filter vpn vpn vpn.py .archive/vpn +filter vscode_projects vscode_projects +filter wikipedia wikipedia Wikipedia Wikipedia.py +filter x_window_switcher x_window_switcher window_switcher window_switcher.py WindowSwitcher.py SwitchApp.py .archive/window_switcher +filter xkcd xkcd .archive/xkcd +filter youtube youtube youtube.py Youtube.py .archive/youtube +filter zeal zeal zeal.py Zeal.py .archive/zeal + + + From 7a1612cf8caaad998fe0aa9af617c2ef645d21de Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Thu, 7 Aug 2025 11:15:34 +0200 Subject: [PATCH 243/243] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d0aeaf3..95b590ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ # ⚠️ ARCHIVED ⚠️ -The plugins in this repository have been moved to dedicated repositories using `history_to_submodules.sh`. - -See https://github.com/orgs/albertlauncher/repositories. +The plugins in this repository have been moved to [dedicated repositories](https://github.com/orgs/albertlauncher/repositories) using `history_to_submodules.sh`.

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 z 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 literal 0 HcmV?d00001 From fd853de382bf2bff957ebec668462284b8a01a2d Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 30 Aug 2023 08:54:54 +0200 Subject: [PATCH 083/243] [googletrans] Archive. py-googletrans is broken. --- {googletrans => .archive/googletrans}/__init__.py | 4 ++-- .../googletrans}/google_translate.png | Bin 2 files changed, 2 insertions(+), 2 deletions(-) rename {googletrans => .archive/googletrans}/__init__.py (95%) rename {googletrans => .archive/googletrans}/google_translate.png (100%) diff --git a/googletrans/__init__.py b/.archive/googletrans/__init__.py similarity index 95% rename from googletrans/__init__.py rename to .archive/googletrans/__init__.py index a97d4a81..3c0d240d 100644 --- a/googletrans/__init__.py +++ b/.archive/googletrans/__init__.py @@ -17,11 +17,11 @@ md_description = "Translate sentences using googletrans" md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/" -md_lib_dependencies = "googletrans==3.1.0a0" +md_lib_dependencies = "googletrans==4.0.0-rc1" md_maintainers = "@manuelschneid3r" -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): TriggerQueryHandler.__init__(self, diff --git a/googletrans/google_translate.png b/.archive/googletrans/google_translate.png similarity index 100% rename from googletrans/google_translate.png rename to .archive/googletrans/google_translate.png From d3873a2cafa1fa8093c249a1264e98936f7fe39d Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 30 Aug 2023 10:22:00 +0200 Subject: [PATCH 084/243] [locate] Fix imports. --- locate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locate/__init__.py b/locate/__init__.py index 461de92b..29ed4c4c 100644 --- a/locate/__init__.py +++ b/locate/__init__.py @@ -22,7 +22,7 @@ md_bin_dependencies = "locate" -class Plugin(TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): TriggerQueryHandler.__init__(self, From 3a2cf18d3c8f5e15046de431c1689d5464032e72 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 30 Aug 2023 21:12:05 +0200 Subject: [PATCH 085/243] [translators] Fix synopsis --- translators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translators/__init__.py b/translators/__init__.py index 4bc994d8..3f1f9b3d 100644 --- a/translators/__init__.py +++ b/translators/__init__.py @@ -95,7 +95,7 @@ def handleTriggerQuery(self, query): and splits[0] in self.src_languages and splits[1] in self.dst_languages: src, dst, text = splits elif len(splits := stripped.split(maxsplit=1)) == 2 and splits[0] in self.src_languages: - src, dst, text = splits[0], self.lang, splits[1] + src, dst, text = 'auto', splits[0], splits[1] else: src, dst, text = 'auto', self.lang, stripped From 2d7ddf12a011c5c977ff420995eed684acbd37a4 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 4 Sep 2023 12:59:18 +0200 Subject: [PATCH 086/243] [stub] Proper PluginInstance.configWidget documentation --- albert.pyi | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/albert.pyi b/albert.pyi index b659c7d8..ed0809ad 100644 --- a/albert.pyi +++ b/albert.pyi @@ -96,20 +96,31 @@ class PluginInstance(ABC): def writeConfig(self, key: str, value: str|int|float|bool): ... - def configWidget(self): + def configWidget(self) -> List[dict]: """ - [ - { - 'type': 'lineedit'|'checkbox'|'spinbox'|'doublespinbox', - 'property_name': '…', - 'display_name': '…', - 'widget_properties': { - 'widget_property': bool|int|float|string, - … - } - }, - … - ] + Descriptive config widget factory. + + Define a static config widget using a list of dicts, each defining a row in the resulting form layout. + Supported keys are: + + - 'property' The name of the property that will be set upon editing the forms. + - 'label' The text displayed in front of the the editor widget. + - 'type' The type of editor widget used. See the supported types below. + - 'items' The list of strings used for 'type': 'combobox'. + - 'widget_properties' Dict setting the widget properties of the editor widget. + See the links along the editor types below (but also the base classes) to find available properties. + Note that due to the restricted type conversion only properties of type str|int|float|bool are settable. + + The supported editor widget types are: + + * 'checkbox' for boolean properties (See https://doc.qt.io/qt-6/qcheckbox.html) + * 'spinbox' for integer properties. (See https://doc.qt.io/qt-6/qspinbox.html) + * 'doublespinbox' for float properties. (See https://doc.qt.io/qt-6/qdoublespinbox.html) + * 'lineedit' if you want the user to input any string. (See https://doc.qt.io/qt-6/qlineedit.html) + * 'combobox' if you want the user to choose a string. (See https://doc.qt.io/qt-6/qcombobox.html) + + Returns: + A list of dicts, describing a form layout as defined above. """ class Action: From e902c5a119d67e94a5dbb838cd177b18397414bb Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Mon, 4 Sep 2023 13:11:04 +0200 Subject: [PATCH 087/243] [stub] Proper PluginInstance.read/writeConfig documentation --- albert.pyi | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/albert.pyi b/albert.pyi index ed0809ad..3283f1d7 100644 --- a/albert.pyi +++ b/albert.pyi @@ -91,10 +91,17 @@ class PluginInstance(ABC): ... def readConfig(self, key: str, type: type[str|int|float|bool]) -> str|int|float|bool|None: - ... + """ + Read a config value from the Albert settings. + Note: Due to limitations of QSettings on some platforms the type may be lost, therefore the type expected has to + be passed. + + Returns: + The requested value or None if the value does not exist or errors occurred. + """ def writeConfig(self, key: str, value: str|int|float|bool): - ... + """Write a config value to the Albert settings.""" def configWidget(self) -> List[dict]: """ From 4c07cc8878c027bf7ecf379176c6e491a68924d0 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Fri, 8 Sep 2023 13:14:29 +0200 Subject: [PATCH 088/243] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 993425dc..81e3ce5b 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,9 @@ This repository is shipped with albert. If you want to have bleeding edge plugin ```shell git clone https://github.com/albertlauncher/python.git ~/.local/share/albert/python/plugins ``` + +Credits go to our contributors + + + + From a261a5df2b414d4938dda82f001230fa8a81d8d1 Mon Sep 17 00:00:00 2001 From: Pete Hamlin Date: Fri, 8 Sep 2023 11:15:54 +0000 Subject: [PATCH 089/243] [pass] Add pass otp * feat: Updated pass extension to use OTP * fix: Regression * fix: Per request, removed maxmil from maintainers --- pass/__init__.py | 151 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 39 deletions(-) diff --git a/pass/__init__.py b/pass/__init__.py index 9a868c3a..5da2d5fc 100644 --- a/pass/__init__.py +++ b/pass/__init__.py @@ -4,12 +4,12 @@ import os from albert import * -md_iid = '2.0' -md_version = "1.4" +md_iid = "2.1" +md_version = "1.5" md_name = "Pass" md_description = "Manage passwords in pass" md_bin_dependencies = ["pass"] -md_maintainers = "@maxmil" +md_maintainers = ["@Pete-Hamlin"] md_license = "BSD-3" HOME_DIR = os.environ["HOME"] @@ -17,40 +17,109 @@ class Plugin(PluginInstance, TriggerQueryHandler): - def __init__(self): - TriggerQueryHandler.__init__(self, - id=md_id, - name=md_name, - description=md_description, - synopsis='', - defaultTrigger='pass ') + TriggerQueryHandler.__init__( + self, + id=md_id, + name=md_name, + description=md_description, + synopsis="", + defaultTrigger="pass ", + ) PluginInstance.__init__(self, extensions=[self]) self.iconUrls = ["xdg:dialog-password"] + self._use_otp = self.readConfig("use_otp", bool) or False + self._otp_glob = self.readConfig("otp_glob", str) or "*-otp.gpg" + + @property + def use_otp(self): + return self._use_otp + + @use_otp.setter + def use_otp(self, value): + print(f"Setting _use_otp to {value}") + self._use_otp = value + self.writeConfig("use_otp", value) + + @property + def otp_glob(self): + return self._otp_glob + + @otp_glob.setter + def otp_glob(self, value): + print(f"Setting _otp_glob to {value}") + self._otp_glob = value + self.writeConfig("otp_glob", value) + + def configWidget(self): + return [ + {"type": "checkbox", "property": "use_otp", "label": "Enable pass OTP extension"}, + { + "type": "lineedit", + "property": "otp_glob", + "label": "Glob pattern for OTP passwords", + "widget_properties": {"placeholderText": "*-otp.gpg"}, + }, + ] def handleTriggerQuery(self, query): if query.string.strip().startswith("generate"): self.generatePassword(query) - else: + elif query.string.strip().startswith("otp") and self._use_otp: + self.showOtp(query) + else: self.showPasswords(query) def generatePassword(self, query): location = query.string.strip()[9:] - query.add(StandardItem( - id="generate_password", - iconUrls=self.iconUrls, - text="Generate a new password", - subtext="The new password will be located at %s" % location, - inputActionText="pass %s" % query.string, - actions=[ - Action("generate", "Generate", lambda: runDetachedProcess(["pass", "generate", "--clip", location, "20"])) - ] - )) + query.add( + StandardItem( + id="generate_password", + iconUrls=self.iconUrls, + text="Generate a new password", + subtext="The new password will be located at %s" % location, + inputActionText="pass %s" % query.string, + actions=[ + Action( + "generate", + "Generate", + lambda: runDetachedProcess(["pass", "generate", "--clip", location, "20"]), + ) + ], + ) + ) + + def showOtp(self, query): + otp_query = query.string.strip()[4:] + passwords = [] + if otp_query: + passwords = self.getPasswordsFromSearch(otp_query, otp=True) + else: + passwords = self.getPasswords(otp=True) + + results = [] + for password in passwords: + results.append( + StandardItem( + id=password, + iconUrls=self.iconUrls, + text=password.split("/")[-1], + subtext=password, + actions=[ + Action( + "copy", + "Copy", + lambda pwd=password: runDetachedProcess(["pass", "otp", "--clip", pwd]), + ), + ], + ), + ) + query.add(results) def showPasswords(self, query): if query.string.strip(): - passwords = self.getPasswordsFromSearch(query) + passwords = self.getPasswordsFromSearch(query.string) else: passwords = self.getPasswords() @@ -65,31 +134,35 @@ def showPasswords(self, query): iconUrls=self.iconUrls, inputActionText="pass %s" % password, actions=[ - Action("copy", "Copy", lambda pwd=password: runDetachedProcess(["pass", "--clip", pwd])), - Action("edit", "Edit", lambda pwd=password: runDetachedProcess(["pass", "edit", pwd])), - Action("remove", "Remove", lambda pwd=password: runDetachedProcess(["pass", "rm", "--force", pwd])), - ] + Action( + "copy", + "Copy", + lambda pwd=password: runDetachedProcess(["pass", "--clip", pwd]), + ), + Action( + "edit", + "Edit", + lambda pwd=password: runDetachedProcess(["pass", "edit", pwd]), + ), + Action( + "remove", + "Remove", + lambda pwd=password: runDetachedProcess(["pass", "rm", "--force", pwd]), + ), + ], ), ) query.add(results) - def getPasswords(self): + def getPasswords(self, otp=False): passwords = [] for root, dirnames, filenames in os.walk(PASS_DIR, followlinks=True): - for filename in fnmatch.filter(filenames, "*.gpg"): - passwords.append( - os.path.join(root, filename.replace(".gpg", "")).replace(PASS_DIR, "") - ) + for filename in fnmatch.filter(filenames, self._otp_glob if otp else "*.gpg"): + passwords.append(os.path.join(root, filename.replace(".gpg", "")).replace(PASS_DIR, "")) return sorted(passwords, key=lambda s: s.lower()) - def getPasswordsFromSearch(self, query): - passwords = [] - for password in self.getPasswords(): - if query.string.strip().lower() not in password.lower(): - continue - - passwords.append(password) - + def getPasswordsFromSearch(self, otp_query, otp=False): + passwords = [password for password in self.getPasswords(otp) if otp_query.strip().lower() in password.lower()] return passwords From f33d95575b28de8825320a19991353ec02374109 Mon Sep 17 00:00:00 2001 From: proItheus <50539150+proItheus@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:46:04 +0000 Subject: [PATCH 090/243] [goldendict] Fix import issue --- goldendict/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goldendict/__init__.py b/goldendict/__init__.py index 92ef6d95..80126cf6 100644 --- a/goldendict/__init__.py +++ b/goldendict/__init__.py @@ -1,4 +1,4 @@ -from albert import Action, Item, TriggerQuery, PluginInstance, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error +from albert import Action, StandardItem, TriggerQuery, PluginInstance, TriggerQueryHandler, runDetachedProcess # pylint: disable=import-error md_iid = '2.0' md_version = '1.3' From f1e3cf96b263df519b9d30984eb99b4850fc1976 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 3 Oct 2023 20:40:37 +0200 Subject: [PATCH 091/243] [jetbrains] Add RustRover editor --- jetbrains_projects/__init__.py | 7 +- jetbrains_projects/rustrover.svg | 132 +++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 jetbrains_projects/rustrover.svg diff --git a/jetbrains_projects/__init__.py b/jetbrains_projects/__init__.py index d8dc59f6..e461c7e6 100644 --- a/jetbrains_projects/__init__.py +++ b/jetbrains_projects/__init__.py @@ -16,7 +16,7 @@ from albert import * md_iid = '2.0' -md_version = "1.5" +md_version = "1.6" md_name = "Jetbrains projects" md_description = "Open your JetBrains projects" md_license = "GPL-3" @@ -155,6 +155,11 @@ def __init__(self): icon=plugin_dir / "webstorm.svg", config_dir_prefix="JetBrains/WebStorm", binaries=["webstorm", "webstorm-eap"]), + Editor( + name="RustRover", + icon=plugin_dir / "rustrover.svg", + config_dir_prefix="JetBrains/RustRover", + binaries=["rustrover", "rustrover-eap"]), ] self.editors = [e for e in editors if e.binary is not None] diff --git a/jetbrains_projects/rustrover.svg b/jetbrains_projects/rustrover.svg new file mode 100644 index 00000000..bd5621c5 --- /dev/null +++ b/jetbrains_projects/rustrover.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 924d9999c5a3ea93a0888495f13103f68c9e09c1 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Sun, 29 Oct 2023 08:48:16 +0100 Subject: [PATCH 092/243] [stub] Fix links --- albert.pyi | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/albert.pyi b/albert.pyi index 3283f1d7..a105ec78 100644 --- a/albert.pyi +++ b/albert.pyi @@ -32,7 +32,7 @@ md_credits: [str|List(str)] | Third party credit(s) and license notes ## The Plugin class The plugin class is the entry point for a Python plugin. It is instantiated on plugin initialization and has to subclass -PluginInstance. Implement extension(s) by subclassing _one_ extension class (TriggerQueryHandler etc…) provided by the +PluginInstance. Implement extensions by subclassing _one_ extension class (TriggerQueryHandler etc…) provided by the built-in `albert` module and pass the list of your extensions to the PluginInstance init function. Due to the differences in type systems multiple inheritance of extensions is not supported. (Python does not support virtual inheritance, which is used in the C++ space to inherit from 'Extension'). For more details see @@ -51,7 +51,7 @@ from typing import overload class PluginInstance(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_plugin_instance.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1PluginInstance.html""" def __init__(self, extensions: List[Extension] = []): ... @@ -131,7 +131,7 @@ class PluginInstance(ABC): """ class Action: - """https://albertlauncher.github.io/reference/classalbert_1_1_action.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1Action.html""" def __init__(self, id: str, @@ -141,7 +141,7 @@ class Action: class Item(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_item.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1Item.html""" @abstractmethod def id(self) -> str: @@ -161,7 +161,7 @@ class Item(ABC): @abstractmethod def iconUrls(self) -> List[str]: - """See https://albertlauncher.github.io/reference/classalbert_1_1_icon_provider.html""" + """See https://albertlauncher.github.io/reference/classalbert_1_1IconProvider.html""" @abstractmethod def actions(self) -> List[Action]: @@ -169,7 +169,7 @@ class Item(ABC): class StandardItem(Item): - """https://albertlauncher.github.io/reference/structalbert_1_1_standard_item.html""" + """https://albertlauncher.github.io/reference/structalbert_1_1StandardItem.html""" def __init__(self, id: str = '', @@ -189,7 +189,7 @@ class StandardItem(Item): class Extension(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_extension.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1Extension.html""" @property def id(self) -> str: @@ -205,7 +205,7 @@ class Extension(ABC): class FallbackHandler(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_fallback_handler.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1FallbackHandler.html""" @abstractmethod def fallbacks(self, query: str ) ->List[Item]: @@ -213,7 +213,7 @@ class FallbackHandler(ABC): class TriggerQuery(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_trigger_query_handler_1_1_trigger_query.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1TriggerQueryHandler_1_1TriggerQuery.html""" @property def trigger(self) -> str: @@ -237,7 +237,7 @@ class TriggerQuery(ABC): class TriggerQueryHandler(Extension): - """https://albertlauncher.github.io/reference/classalbert_1_1_trigger_query_handler.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1TriggerQueryHandler.html""" def __init__(self, id: str, @@ -283,7 +283,7 @@ class TriggerQueryHandler(Extension): class RankItem: - """https://albertlauncher.github.io/reference/classalbert_1_1_rank_item.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1RankItem.html""" def __init__(self, item: Item, score: float): ... @@ -293,7 +293,7 @@ class RankItem: class GlobalQuery(ABC): - """https://albertlauncher.github.io/reference/classalbert_1_1_global_query_handler_1_1_global_query.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1GlobalQueryHandler_1_1GlobalQuery.html""" @property def string(self) -> str: @@ -305,7 +305,7 @@ class GlobalQuery(ABC): class GlobalQueryHandler(TriggerQueryHandler): - """https://albertlauncher.github.io/reference/classalbert_1_1_global_query_handler.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1GlobalQueryHandler.html""" def __init__(self, id: str, @@ -329,7 +329,7 @@ class GlobalQueryHandler(TriggerQueryHandler): class IndexItem: - """https://albertlauncher.github.io/reference/classalbert_1_1_index_item.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1IndexItem.html""" def __init__(self, item: AbstractItem, string: str): ... @@ -339,7 +339,7 @@ class IndexItem: class IndexQueryHandler(GlobalQueryHandler): - """https://albertlauncher.github.io/reference/classalbert_1_1_index_query_handler.html""" + """https://albertlauncher.github.io/reference/classalbert_1_1IndexQueryHandler.html""" @abstractmethod def updateIndexItems(self): From 5974e8bcf4b7076fcee6d2410d3195d31c18e6f9 Mon Sep 17 00:00:00 2001 From: Manuel Schneider Date: Wed, 8 Nov 2023 18:29:01 +0100 Subject: [PATCH 093/243] Use global issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ---------------------------- .github/ISSUE_TEMPLATE/config.yml | 14 ------------ 2 files changed, 47 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4b860dc1..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Bug report -about: Please check the existing issues and discuss your issue in the chats before creating a new one. ---- - - - -#### Description -A brief description of the issue. If applicable, add screenshots. - -#### Expected behavior -A brief description of what you expected to happen - -#### Steps to reproduce -Steps to reproduce the behavior - -#### Source -e.g. ppa:name, repository, built from source, etc … - -#### Debug output - -