├── .archive ├── README.md ├── atom_projects │ └── __init__.py ├── base_converter │ └── __init__.py ├── binance │ ├── Binance.svg │ └── __init__.py ├── bitfinex │ ├── Bitfinex.svg │ └── __init__.py ├── currency_converter │ └── __init__.py ├── dango_emoji │ ├── __init__.py │ └── dangoemoji.png ├── dango_kao │ ├── __init__.py │ └── kaoicon.svg ├── find │ └── __init__.py ├── fortune │ └── __init__.py ├── gnome_dictionary │ └── __init__.py ├── gnote │ └── __init__.py ├── google_translate │ └── __init__.py ├── googletrans │ ├── __init__.py │ └── google_translate.png ├── inhibit_sleep │ └── __init__.py ├── ip │ └── __init__.py ├── lpass │ ├── __init__.py │ └── lastpass.svg ├── mathematica_eval │ └── __init__.py ├── multi_google_translate │ └── __init__.py ├── node_eval │ ├── __init__.py │ └── nodejs.svg ├── npm │ ├── __init__.py │ └── logo.svg ├── packagist │ ├── __init__.py │ └── logo.png ├── php_eval │ ├── __init__.py │ └── php.svg ├── pidgin │ └── __init__.py ├── rand │ ├── __init__.py │ └── rand.png ├── scrot │ └── __init__.py ├── texdoc │ ├── __init__.py │ └── texdoc-logo.svg ├── timer │ └── __init__.py ├── tomboy │ └── __init__.py ├── unicode_emoji │ ├── __init__.py │ └── emoji.txt ├── units │ └── __init__.py ├── vpn │ └── __init__.py ├── window_switcher │ └── __init__.py ├── xkcd │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── image.png │ ├── install-plugin.sh │ └── misc │ │ └── demo.gif └── youtube │ ├── __init__.py │ └── youtube.svg ├── .github └── workflows │ ├── telegram_notify_comments.yml │ └── telegram_notify_issues.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── README.md ├── arch_wiki ├── __init__.py └── arch.svg ├── aur ├── __init__.py └── arch.svg ├── bitwarden ├── __init__.py └── bw.svg ├── coingecko ├── __init__.py └── coingecko.png ├── color └── __init__.py ├── copyq └── __init__.py ├── dice_roll ├── __init__.py └── icons │ ├── d10.svg │ ├── d100.svg │ ├── d12.svg │ ├── d2.svg │ ├── d20.svg │ ├── d4.svg │ ├── d6.svg │ ├── d8.svg │ └── dice.svg ├── docker ├── __init__.py ├── running.png └── stopped.png ├── duckduckgo ├── __init__.py └── duckduckgo.svg ├── emoji └── __init__.py ├── goldendict └── __init__.py ├── jetbrains_projects ├── __init__.py └── icons │ ├── androidstudio.svg │ ├── aqua.svg │ ├── clion.svg │ ├── datagrip.svg │ ├── dataspell.svg │ ├── goland.svg │ ├── idea.svg │ ├── phpstorm.svg │ ├── pycharm.svg │ ├── rider.svg │ ├── rubymine.svg │ ├── rustrover.svg │ ├── webstorm.svg │ └── writerside.svg ├── kill └── __init__.py ├── locate ├── __init__.py └── locate.svg ├── pacman ├── __init__.py └── arch.svg ├── pass └── __init__.py ├── pomodoro ├── __init__.py └── pomodoro.svg ├── python_eval ├── __init__.py └── python.svg ├── syncthing ├── __init__.py └── syncthing.svg ├── tex_to_unicode ├── __init__.py └── tex.svg ├── translators ├── __init__.py └── google_translate.png ├── unit_converter ├── __init__.py └── icons │ ├── currency.svg │ ├── current.svg │ ├── length.svg │ ├── lengthtime.svg │ ├── luminosity.svg │ ├── mass.svg │ ├── printing_unit.svg │ ├── substance.svg │ ├── temperature.svg │ ├── time.svg │ └── unit_converter.svg ├── virtualbox └── __init__.py ├── wikipedia ├── __init__.py └── wikipedia.png ├── x_window_switcher └── __init__.py └── zeal └── __init__.py /.archive/README.md: -------------------------------------------------------------------------------- 1 | # The archive 2 | 3 | 0.18 changed the API and most of the plugins here have no maintainer. Please volunteer as maintainer and help getting plugins shipped. 👍 4 | -------------------------------------------------------------------------------- /.archive/atom_projects/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """List and open your Atom projects. 4 | 5 | Synopsis: [filter]""" 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import os 10 | import re 11 | import time 12 | from pathlib import Path 13 | 14 | import cson 15 | 16 | from albert import * 17 | 18 | __title__ = "Atom Projects" 19 | __version__ = "0.4.0" 20 | __triggers__ = "atom " 21 | __authors__ = "Manuel S." 22 | __exec_deps__ = ["atom"] 23 | __py_deps__ = ["cson"] 24 | 25 | projects_file = str(Path.home()) + "/.atom/projects.cson" 26 | iconPath = iconLookup('atom') 27 | mtime = 0 28 | projects = [] 29 | 30 | 31 | def updateProjects(): 32 | global mtime 33 | try: 34 | new_mtime = os.path.getmtime(projects_file) 35 | except Exception as e: 36 | warning("Could not get mtime of file: " + projects_file + str(e)) 37 | if mtime != new_mtime: 38 | mtime = new_mtime 39 | with open(projects_file) as projects_cson: 40 | global projects 41 | projects = cson.loads(projects_cson.read()) 42 | 43 | 44 | def handleQuery(query): 45 | if not query.isTriggered: 46 | return 47 | 48 | updateProjects() 49 | 50 | stripped = query.string.strip() 51 | 52 | items = [] 53 | for project in projects: 54 | if re.search(stripped, project['title'], re.IGNORECASE): 55 | items.append(Item(id=__title__ + project['title'], 56 | icon=iconPath, 57 | text=project['title'], 58 | subtext="Group: %s" % (project['group'] if 'group' in project else "None"), 59 | actions=[ 60 | ProcAction(text="Open project in Atom", 61 | commandline=["atom"] + project['paths']) 62 | ])) 63 | return items 64 | -------------------------------------------------------------------------------- /.archive/base_converter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Convert representations of numbers. 4 | 5 | Synopsis: 6 | 7 | [padding] 8 | 9 | where is a literal of the form '0bXXX' (binary), '0XXX' (octal), 10 | or '0xXXX' (hexadecimal).""" 11 | 12 | # Copyright (c) 2022 Manuel Schneider 13 | 14 | import numpy as np 15 | from collections import defaultdict 16 | 17 | from albert import Item, ClipAction 18 | 19 | __title__ = "Base Converter" 20 | __version__ = "0.4.1" 21 | __triggers__ = "base " 22 | __authors__ = ["Manuel S.", "Keating950"] 23 | __py_deps__ = ["numpy"] 24 | 25 | 26 | class keyed_defaultdict(defaultdict): 27 | def __missing__(self, key): 28 | return self.default_factory(key) 29 | 30 | 31 | base_prefixes = keyed_defaultdict(lambda k: 8 if k[0] == "0" and len(k) > 1 else 10) 32 | base_prefixes["0b"] = 2 33 | base_prefixes["0x"] = 16 34 | 35 | 36 | def buildItem(completion, dst, number, padding=0): 37 | item = Item(id=__title__, completion=completion) 38 | try: 39 | src = base_prefixes[number[:2]] 40 | dst = int(dst) 41 | padding = int(padding) 42 | integer = int(number, src) 43 | item.text = np.base_repr(integer, dst) 44 | if integer >= 0 and len(item.text) < padding: 45 | item.text = '0'*(padding-len(item.text)) + item.text 46 | item.subtext = "Base %s representation of %s (base %s)" % (dst, number, src) 47 | item.addAction(ClipAction("Copy to clipboard", item.text)) 48 | except Exception as e: 49 | item.text = e.__class__.__name__ 50 | item.subtext = str(e) 51 | return item 52 | 53 | 54 | def handleQuery(query): 55 | if query.isTriggered: 56 | fields = query.string.split() 57 | if len(fields) == 2: 58 | return buildItem(query.rawString, fields[0], fields[1]) 59 | else: 60 | item = Item(id=__title__) 61 | item.text = __title__ 62 | item.subtext = "Enter a query in the form of \"<dstbase> <number>\"" 63 | return item 64 | else: 65 | fields = query.string.split() 66 | if len(fields) < 2: 67 | return 68 | src = base_prefixes[fields[:3]] 69 | number = fields[1] 70 | padding = 0 if len(fields) < 3 else fields[2] 71 | results = [] 72 | for dst in sorted(base_prefixes.values().append(8)): 73 | if dst == src: 74 | continue 75 | results.append(buildItem(query.rawString, dst, number, padding)) 76 | return results 77 | -------------------------------------------------------------------------------- /.archive/binance/Binance.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /.archive/binance/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Access the Binance markets. 4 | 5 | Synopsis: 6 | filter 7 | [filter]""" 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | from albert import * 12 | import time 13 | import os 14 | import urllib.request 15 | import urllib.error 16 | from collections import namedtuple 17 | import json 18 | from threading import Thread, Event 19 | 20 | __title__ = "Binance" 21 | __version__ = "0.4.3" 22 | __triggers__ = "bnc " 23 | __authors__ = "Manuel S." 24 | 25 | iconPath = os.path.dirname(__file__) + "/Binance.svg" 26 | exchangeInfoUrl = "https://api.binance.com/api/v1/exchangeInfo" 27 | tradeUrl = "https://www.binance.com/en/trade/%s_%s?layout=pro" 28 | markets = [] 29 | thread = None 30 | 31 | Market = namedtuple("Market" , ["base", "quote"]) 32 | 33 | class UpdateThread(Thread): 34 | def __init__(self): 35 | super().__init__() 36 | self._stopevent = Event() 37 | 38 | def run(self): 39 | while True: 40 | global thread 41 | try: 42 | global markets 43 | with urllib.request.urlopen(exchangeInfoUrl) as response: 44 | symbols = json.loads(response.read().decode())['symbols'] 45 | markets.clear() 46 | for symbol in symbols: 47 | # Skip this strange 123456 market 48 | if symbol['baseAsset'] != "123": 49 | markets.append(Market(base=symbol['baseAsset'], 50 | quote=symbol['quoteAsset'])) 51 | info("Binance markets updated.") 52 | self._stopevent.wait(3600) # Sleep 1h, wakeup on stop event 53 | except Exception as e: 54 | warning("Updating Binance markets failed: %s" % str(e)) 55 | self._stopevent.wait(60) # Sleep 1 min, wakeup on stop event 56 | 57 | if self._stopevent.is_set(): 58 | return 59 | 60 | def stop(self): 61 | self._stopevent.set() 62 | 63 | 64 | def initialize(): 65 | global thread 66 | thread = UpdateThread() 67 | thread.start() 68 | 69 | 70 | def finalize(): 71 | global thread 72 | if thread is not None: 73 | thread.stop() 74 | thread.join() 75 | 76 | 77 | def makeItem(market): 78 | url = tradeUrl % (market.base, market.quote) 79 | return Item( 80 | id="%s_%s%s" % (__title__, market.base, market.quote), 81 | icon=iconPath, 82 | text="%s/%s" % (market.base, market.quote), 83 | subtext="Open the %s/%s market on binance.com" % (market.base, market.quote), 84 | completion="%s%s%s" % (__triggers__, market.base, market.quote), 85 | actions=[ 86 | UrlAction("Show market in browser", url), 87 | ClipAction('Copy URL to clipboard', url) 88 | ] 89 | ) 90 | 91 | 92 | def handleQuery(query): 93 | items = [] 94 | stripped = query.string.strip().upper() 95 | 96 | if query.isTriggered: 97 | if stripped: 98 | for market in markets: 99 | if ("%s%s" % (market.base, market.quote)).startswith(stripped): 100 | items.append(makeItem(market)) 101 | else: 102 | for market in markets: 103 | items.append(makeItem(market)) 104 | else: 105 | for market in markets: 106 | if stripped and ("%s%s" % (market.base, market.quote)).startswith(stripped): 107 | items.append(makeItem(market)) 108 | 109 | return items 110 | -------------------------------------------------------------------------------- /.archive/bitfinex/Bitfinex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.archive/bitfinex/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Access the Bitfinex markets. 4 | 5 | Synopsis: 6 | filter 7 | [filter]""" 8 | 9 | from albert import * 10 | import time 11 | import os 12 | import urllib.request 13 | import urllib.error 14 | import json 15 | from collections import namedtuple 16 | from threading import Thread, Event 17 | 18 | __title__ = "BitFinex" 19 | __version__ = "0.4.0" 20 | __triggers__ = "bfx " 21 | __authors__ = "Manuel S." 22 | 23 | iconPath = os.path.dirname(__file__) + "/Bitfinex.svg" 24 | symbolsEndpoint = "https://api.bitfinex.com/v1/symbols" 25 | tradeUrl = "https://www.bitfinex.com/t/%s:%s" 26 | markets = [] 27 | thread = None 28 | 29 | Market = namedtuple("Market" , ["base", "quote"]) 30 | 31 | class UpdateThread(Thread): 32 | def __init__(self): 33 | super().__init__() 34 | self._stopevent = Event() 35 | 36 | def run(self): 37 | while True: 38 | global thread 39 | try: 40 | global markets 41 | with urllib.request.urlopen(symbolsEndpoint) as response: 42 | symbols = json.loads(response.read().decode()) 43 | markets.clear() 44 | for symbol in symbols: 45 | symbol = symbol.upper() 46 | markets.append(Market(base=symbol[0:3], quote=symbol[3:6])) 47 | info("Bitfinex markets updated.") 48 | self._stopevent.wait(3600) # Sleep 1h, wakeup on stop event 49 | except Exception as e: 50 | warning("Updating Bitfinex markets failed: %s" % str(e)) 51 | self._stopevent.wait(60) # Sleep 1 min, wakeup on stop event 52 | 53 | if self._stopevent.is_set(): 54 | return 55 | 56 | def stop(self): 57 | self._stopevent.set() 58 | 59 | 60 | def initialize(): 61 | global thread 62 | thread = UpdateThread() 63 | thread.start() 64 | 65 | 66 | def finalize(): 67 | global thread 68 | if thread is not None: 69 | thread.stop() 70 | thread.join() 71 | 72 | def makeItem(market): 73 | url = tradeUrl % (market.base, market.quote) 74 | return Item( 75 | id="%s_%s%s" % (__title__, market.base, market.quote), 76 | icon=iconPath, 77 | text="%s/%s" % (market.base, market.quote), 78 | subtext="Open the %s/%s market on bitfinex.com" % (market.base, market.quote), 79 | completion="%s%s%s" % (__triggers__, market.base, market.quote), 80 | actions=[ 81 | UrlAction("Show market in browser", url), 82 | ClipAction('Copy URL to clipboard', url) 83 | ] 84 | ) 85 | 86 | 87 | def handleQuery(query): 88 | items = [] 89 | stripped = query.string.strip().upper() 90 | 91 | if query.isTriggered: 92 | if stripped: 93 | for market in markets: 94 | if ("%s%s" % (market.base, market.quote)).startswith(stripped): 95 | items.append(makeItem(market)) 96 | else: 97 | for market in markets: 98 | items.append(makeItem(market)) 99 | else: 100 | for market in markets: 101 | if stripped and ("%s%s" % (market.base, market.quote)).startswith(stripped): 102 | items.append(makeItem(market)) 103 | 104 | return items 105 | -------------------------------------------------------------------------------- /.archive/currency_converter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Convert currencies. 4 | 5 | Current backends: ECB, Yahoo. 6 | 7 | Synopsis: [to|as|in] """ 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | import re 12 | import time 13 | from urllib.request import urlopen 14 | from xml.etree import ElementTree 15 | 16 | from albert import * 17 | 18 | __title__ = "Currency converter" 19 | __version__ = "0.4.0" 20 | __authors__ = "Manuel S." 21 | 22 | iconPath = iconLookup('accessories-calculator') or ":python_module" 23 | 24 | 25 | class EuropeanCentralBank: 26 | 27 | url = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml" 28 | 29 | def __init__(self): 30 | self.lastUpdate = 0 31 | self.exchange_rates = dict() 32 | self.name = "European Central Bank" 33 | 34 | def convert(self, amount, src, dst): 35 | if self.lastUpdate < time.time()-10800: # Update every 3 hours 36 | self.exchange_rates.clear() 37 | with urlopen(EuropeanCentralBank.url) as response: 38 | tree = ElementTree.fromstring(response.read().decode()) 39 | for child in tree[2][0]: 40 | curr = child.attrib['currency'] 41 | rate = float(child.attrib['rate']) 42 | self.exchange_rates[curr] = rate 43 | self.exchange_rates["EUR"] = 1.0 # For simpler algorithmic 44 | info("%s: Updated foreign exchange rates." % __title__) 45 | debug(str(self.exchange_rates)) 46 | self.lastUpdate = time.time() 47 | 48 | if src in self.exchange_rates and dst in self.exchange_rates: 49 | src_rate = self.exchange_rates[src] 50 | dst_rate = self.exchange_rates[dst] 51 | return str(amount / src_rate * dst_rate) 52 | 53 | class Yahoo: 54 | 55 | def __init__(self): 56 | self.name = "Yahoo" 57 | 58 | def convert(self, amount, src, dst): 59 | if amount.is_integer: 60 | amount = int(amount) 61 | url = 'https://search.yahoo.com/search?p=%s+%s+to+%s' % (amount, src, dst) 62 | with urlopen(url) as response: 63 | html = response.read().decode() 64 | m = re.search('(\d+(\.\d+)?)', html) 65 | if m: 66 | return m.group(1) 67 | 68 | 69 | providers = [EuropeanCentralBank(), Yahoo()] 70 | regex = re.compile(r"(\d+\.?\d*)\s+(\w{3})(?:\s+(?:to|in|as))?\s+(\w{3})") 71 | 72 | def handleQuery(query): 73 | match = regex.fullmatch(query.string.strip()) 74 | if match: 75 | prep = (float(match.group(1)), match.group(2).upper(), match.group(3).upper()) 76 | item = Item(id=__title__, icon=iconPath) 77 | for provider in providers: 78 | result = provider.convert(*prep) 79 | if result: 80 | item.text = result 81 | item.subtext = "Value of %s %s in %s (Source: %s)" % (*prep, provider.name) 82 | item.addAction(ClipAction("Copy result to clipboard", result)) 83 | return item 84 | else: 85 | warning("None of the foreign exchange rate providers came up with a result for %s" % str(prep)) 86 | -------------------------------------------------------------------------------- /.archive/dango_emoji/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Find emojis using getdango.com 4 | 5 | Dango uses a form of artificial intelligence called deep learning to understand the nuances of \ 6 | human emotion, and predict emoji based on what you type. If your emojis are not rendering properly \ 7 | install an emoji font like e.g.: https://github.com/eosrei/emojione-color-font 8 | 9 | Synopsis: """ 10 | 11 | 12 | from albert import * 13 | import json 14 | import os 15 | import urllib.error 16 | from urllib.request import urlopen, Request 17 | from urllib.parse import urlencode 18 | 19 | 20 | __title__ = "Dango Emoji" 21 | __version__ = "0.4.1" 22 | __triggers__ = ":" 23 | __authors__ = "David Britt" 24 | 25 | 26 | iconPath = os.path.dirname(__file__) + "/dangoemoji.png" 27 | dangoUrl = "https://emoji.getdango.com/api/emoji" 28 | emojipedia_url = "https://emojipedia.org/%s" 29 | 30 | 31 | def handleQuery(query): 32 | results = [] 33 | if query.isTriggered: 34 | 35 | item = Item( 36 | id=__title__, 37 | icon=icon_path, 38 | text=__title__ 39 | ) 40 | 41 | if len(query.string) >= 2: 42 | try: 43 | url = "%s?%s" % (dangoUrl, urlencode({"q": query.string, "syn": 0})) 44 | with urlopen(Request(url)) as response: 45 | 46 | json_data = json.loads(response.read().decode()) 47 | 48 | if json_data["results"][0]["score"] > 0.025: 49 | all_emojis = [] 50 | for emoj in json_data["results"]: 51 | if emoj["score"] > 0.025: 52 | all_emojis.append(emoj["text"]) 53 | 54 | string_emojis = ''.join(all_emojis) 55 | 56 | results.append(Item( 57 | id=__title__, 58 | icon=icon_path, 59 | text=string_emojis, 60 | subtext="Score > 0.025", 61 | actions=[ 62 | ClipAction( 63 | "Copy translation to clipboard", string_emojis) 64 | ] 65 | )) 66 | 67 | for emoj in json_data["results"]: 68 | results.append(Item( 69 | id=__title__, 70 | icon=icon_path, 71 | text=str(emoj["text"]), 72 | subtext=str(emoj["score"]), 73 | actions=[ 74 | ClipAction( 75 | "Copy translation to clipboard", str(emoj["text"])), 76 | UrlAction("Open in Emojipedia", 77 | emojipedia_url % str(emoj["text"])) 78 | ] 79 | )) 80 | 81 | except urllib.error.URLError as urlerr: 82 | print("Troubleshoot internet connection: %s" % urlerr) 83 | item.subtext = "Connection error" 84 | return item 85 | else: 86 | item.subtext = "Search emojis!" 87 | return item 88 | return results 89 | -------------------------------------------------------------------------------- /.archive/dango_emoji/dangoemoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/dango_emoji/dangoemoji.png -------------------------------------------------------------------------------- /.archive/dango_kao/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Find kaomojis using getdango.com 4 | 5 | Dango uses a form of artificial intelligence called deep learning to understand the nuances of \ 6 | human emotion, and predict emoji based on what you type. 7 | 8 | Synopsis: """ 9 | 10 | from albert import * 11 | import os 12 | import json 13 | import urllib.error 14 | from urllib.request import urlopen, Request 15 | from urllib.parse import urlencode 16 | 17 | __title__ = "Dango Kaomoji" 18 | __version__ = "0.4.0" 19 | __triggers__ = "kao " 20 | __authors__ = "David Britt" 21 | 22 | icon_path = os.path.dirname(__file__) + "/kaoicon.svg" 23 | dangoUrl = "https://customer.getdango.com/dango/api/query/kaomoji" 24 | 25 | 26 | def handleQuery(query): 27 | results = [] 28 | 29 | if query.isTriggered: 30 | 31 | item = Item( 32 | id=__title__, 33 | icon=icon_path, 34 | text=__title__, 35 | ) 36 | 37 | if len(query.string) >= 2: 38 | try: 39 | url = "%s?%s" % (dangoUrl, urlencode({"q": query.string})) 40 | with urlopen(Request(url)) as response: 41 | json_data = json.loads(response.read().decode()) 42 | for emoj in json_data["items"]: 43 | results.append(Item( 44 | id=__title__, 45 | icon=icon_path, 46 | text=emoj["text"], 47 | actions=[ 48 | ClipAction( 49 | "Copy translation to clipboard", emoj["text"]) 50 | ] 51 | )) 52 | except urllib.error.URLError as urlerr: 53 | print("Troubleshoot internet connection: %s" % urlerr) 54 | item.subtext = "Connection error" 55 | return item 56 | else: 57 | item.subtext = "Search emojis!" 58 | return item 59 | 60 | return results 61 | -------------------------------------------------------------------------------- /.archive/dango_kao/kaoicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.archive/find/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | broken 5 | """ 6 | # Copyright (c) 2022 Manuel Schneider 7 | 8 | import albert 9 | from albert import * 10 | from time import sleep 11 | import io 12 | import os 13 | import subprocess 14 | from pathlib import Path 15 | 16 | __iid__ = "0.5" 17 | __version__ = "1.0" 18 | __id__ = "find" 19 | __name__ = "Find" 20 | __description__ = "Online search your file system" 21 | __license__ = "BSD-3" 22 | __url__ = "https://github.com/albertlauncher/python/tree/master/find" 23 | __maintainers__ = "@manuelschneid3r" 24 | __authors__ = ["@manuelschneid3r"] 25 | __bin_dependencies__ = ["find"] 26 | __default_trigger__ = "find " 27 | __synopsis__ = "" 28 | 29 | 30 | class Plugin(Plugin, QueryHandler): 31 | def __init__(self): 32 | albert.Plugin.__init__(self) 33 | albert.QueryHandler.__init__(self) 34 | 35 | def id(self): 36 | return __id__; 37 | 38 | def name(self): 39 | return __name__; 40 | 41 | def takeThisAndModifyR(self, item): 42 | item.id = "takeThisAndModifyR"; 43 | 44 | def takeThisAndModifyR_(self, item): 45 | item.id = "takeThisAndModifyR_"; 46 | 47 | def takeThisAndModifyP(self, item): 48 | item.id = "takeThisAndModifyP"; 49 | 50 | def description(self): 51 | return __description__; 52 | 53 | def handleQuery(self, query): 54 | info(query.string) 55 | proc = subprocess.Popen(["find", Path.home(), "iname", query.string], stdout=subprocess.PIPE) 56 | for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): 57 | absolute = os.path.abspath(line) 58 | item = Item( 59 | id=absolute, 60 | text=os.path.basename(absolute), 61 | subtext=absolute, 62 | completion="", 63 | icon=[":python"], 64 | actions=[ 65 | Action( 66 | id="clip", 67 | text="setClipboardText (ClipAction)", 68 | callable=lambda: setClipboardText(text=configLocation()) 69 | ) 70 | ] 71 | ) 72 | query.add(item) 73 | 74 | 75 | def initialize(self): 76 | info("Find::initialize") 77 | 78 | # def extensions(self): 79 | # return [self.e] 80 | ## pass 81 | -------------------------------------------------------------------------------- /.archive/fortune/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Display random poignant, inspirational, silly or snide phrase. 4 | 5 | Fortune wrapper extension. 6 | 7 | Synopsis: """ 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | import subprocess as sp 12 | from albert import * 13 | 14 | __title__ = "Fortune" 15 | __version__ = "0.4.0" 16 | __triggers__ = "fortune" 17 | __authors__ = "Kelvin Wong" 18 | __exec_deps__ = ["fortune"] 19 | 20 | iconPath = iconLookup("font") 21 | 22 | 23 | def handleQuery(query): 24 | if query.isTriggered: 25 | newFortune = generateFortune() 26 | if newFortune is not None: 27 | return getFortuneItem(query, newFortune) 28 | 29 | 30 | def generateFortune(): 31 | try: 32 | return sp.check_output(["fortune", "-s"]).decode().strip() 33 | except sp.CalledProcessError as e: 34 | return None 35 | 36 | 37 | def getFortuneItem(query, fortune): 38 | return Item( 39 | id=__title__, 40 | icon=iconPath, 41 | text=fortune, 42 | subtext="Copy this random, hopefully interesting, adage", 43 | actions=[ClipAction("Copy to clipboard", fortune)] 44 | ) 45 | -------------------------------------------------------------------------------- /.archive/gnome_dictionary/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Gnome dictionary. 4 | 5 | Needs 'gnome-dictionary' to be already installed. 6 | 7 | Sysnopsis: """ 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | from subprocess import run 12 | 13 | from albert import * 14 | 15 | __title__ = "Gnome Dictionary" 16 | __version__ = "0.4.0" 17 | __triggers__ = "def " 18 | __authors__ = "Nikhil Wanpal" 19 | __exec_deps__ = ["gnome-dictionary"] 20 | 21 | iconPath = iconLookup('accessories-dictionary') 22 | 23 | 24 | def handleQuery(query): 25 | if query.isTriggered: 26 | return Item(id=__title__, 27 | icon=iconPath, 28 | text=__title__, 29 | subtext="Search for '%s' using %s" % (query.string, __title__), 30 | actions=[ProcAction("Opens %s and searches for '%s'" % (__title__, query.string), 31 | ["gnome-dictionary", "--look-up=%s" % query.string])]) 32 | -------------------------------------------------------------------------------- /.archive/gnote/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Search, open, create and delete notes. 4 | 5 | Synopsis: [filter]""" 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import re 10 | from datetime import datetime 11 | 12 | from dbus import DBusException, Interface, SessionBus 13 | 14 | from albert import * 15 | 16 | __title__ = "Gnote" 17 | __version__ = "0.4.1" 18 | __triggers__ = "gn " 19 | __authors__ = "Manuel S." 20 | __exec_deps__ = ["gnote"] 21 | __py_deps__ = ["dbus"] 22 | 23 | BUS = "org.gnome.%s" % __title__ 24 | OBJ = "/org/gnome/%s/RemoteControl" % __title__ 25 | IFACE = 'org.gnome.%s.RemoteControl' % __title__ 26 | iconPath = iconLookup("gnote") 27 | 28 | 29 | def handleQuery(query): 30 | results = [] 31 | if query.isTriggered: 32 | try: 33 | if not SessionBus().name_has_owner(BUS): 34 | warning("Seems like gnote is not running") 35 | return 36 | 37 | obj = SessionBus().get_object(bus_name=BUS, object_path=OBJ) 38 | iface = Interface(obj, dbus_interface=IFACE) 39 | 40 | if query.string.strip(): 41 | for note in iface.SearchNotes(query.string.lower(), False): 42 | results.append( 43 | Item(id="%s%s" % (__title__, note), 44 | icon=iconPath, 45 | text=iface.GetNoteTitle(note), 46 | subtext="%s%s" % ("".join(["#%s " % re.search('.+:.+:(.+)', s).group(1) for s in iface.GetTagsForNote(note)]), 47 | datetime.fromtimestamp(iface.GetNoteChangeDate(note)).strftime("Note from %c")), 48 | actions=[ 49 | FuncAction("Open note", 50 | lambda note=note: iface.DisplayNote(note)), 51 | FuncAction("Delete note", 52 | lambda note=note: iface.DeleteNote(note)) 53 | ])) 54 | else: 55 | def createAndShowNote(): 56 | note = iface.CreateNote() 57 | iface.DisplayNote(note) 58 | 59 | results.append(Item(id="%s-create" % __title__, 60 | icon=iconPath, 61 | text=__title__, 62 | subtext="%s notes" % __title__, 63 | actions=[ 64 | FuncAction("Open %s" % __title__, 65 | lambda: iface.DisplaySearch()), 66 | FuncAction("Create a new note", createAndShowNote) 67 | ])) 68 | except DBusException as e: 69 | critical(str(e)) 70 | return results 71 | -------------------------------------------------------------------------------- /.archive/google_translate/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Translate text using Google Translate. 4 | 5 | Check available languages here: https://cloud.google.com/translate/docs/languages 6 | 7 | Synopsis: """ 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | import json 12 | import urllib.parse 13 | import urllib.request 14 | 15 | from albert import * 16 | 17 | __title__ = "Google Translate" 18 | __version__ = "0.4.0" 19 | __triggers__ = "tr " 20 | __authors__ = "Manuel S." 21 | 22 | ua = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36" 23 | urltmpl = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=%s&tl=%s&dt=t&q=%s" 24 | 25 | iconPath = iconLookup('config-language') or ":python_module" 26 | 27 | def handleQuery(query): 28 | if query.isTriggered: 29 | fields = query.string.split() 30 | item = Item(id=__title__, icon=iconPath) 31 | if len(fields) >= 3: 32 | src = fields[0] 33 | dst = fields[1] 34 | txt = " ".join(fields[2:]) 35 | url = urltmpl % (src, dst, urllib.parse.quote_plus(txt)) 36 | req = urllib.request.Request(url, headers={'User-Agent': ua}) 37 | with urllib.request.urlopen(req) as response: 38 | data = json.loads(response.read().decode('utf-8')) 39 | result = data[0][0][0] 40 | item.text = result 41 | item.subtext = "%s-%s translation of %s" % (src.upper(), dst.upper(), txt) 42 | item.addAction(ClipAction("Copy translation to clipboard", result)) 43 | return item 44 | else: 45 | item.text = __title__ 46 | item.subtext = "Enter a query in the form of \"<srclang> <dstlang> <text>\"" 47 | return item 48 | -------------------------------------------------------------------------------- /.archive/googletrans/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Translator using py-googletrans 5 | """ 6 | 7 | from locale import getdefaultlocale 8 | from pathlib import Path 9 | from time import sleep 10 | 11 | from albert import * 12 | from googletrans import Translator, LANGUAGES 13 | 14 | md_iid = '2.0' 15 | md_version = "1.2" 16 | md_name = "Google Translate" 17 | md_description = "Translate sentences using googletrans" 18 | md_license = "BSD-3" 19 | md_url = "https://github.com/albertlauncher/python/" 20 | md_lib_dependencies = "googletrans==4.0.0-rc1" 21 | md_maintainers = "@manuelschneid3r" 22 | 23 | 24 | class Plugin(PluginInstance, TriggerQueryHandler): 25 | 26 | def __init__(self): 27 | TriggerQueryHandler.__init__(self, 28 | id=md_id, 29 | name=md_name, 30 | description=md_description, 31 | synopsis="[[src] dest] text", 32 | defaultTrigger='tr ') 33 | PluginInstance.__init__(self, extensions=[self]) 34 | self.iconUrls = [f"file:{Path(__file__).parent}/google_translate.png"] 35 | self.translator = Translator() 36 | self.lang = getdefaultlocale()[0][0:2] 37 | 38 | def handleTriggerQuery(self, query): 39 | stripped = query.string.strip() 40 | if stripped: 41 | for _ in range(50): 42 | sleep(0.01) 43 | if not query.isValid: 44 | return 45 | 46 | src = None 47 | dest, text = self.lang, stripped 48 | splits = text.split(maxsplit=1) 49 | if 1 < len(splits) and splits[0] in LANGUAGES: 50 | dest, text = splits[0], splits[1] 51 | splits = text.split(maxsplit=1) 52 | if 1 < len(splits) and splits[0] in LANGUAGES: 53 | src = dest 54 | dest, text = splits[0], splits[1] 55 | 56 | if src: 57 | translation = self.translator.translate(text, src=src, dest=dest) 58 | else: 59 | translation = self.translator.translate(text, dest=dest) 60 | 61 | query.add(StandardItem( 62 | id=md_id, 63 | text=translation.text, 64 | subtext=f'From {LANGUAGES[translation.src]} to {LANGUAGES[translation.dest]}', 65 | iconUrls=self.iconUrls, 66 | actions=[Action("copy", "Copy result to clipboard", 67 | lambda t=translation.text: setClipboardText(t))] 68 | )) 69 | -------------------------------------------------------------------------------- /.archive/googletrans/google_translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/googletrans/google_translate.png -------------------------------------------------------------------------------- /.archive/inhibit_sleep/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | """ 5 | Provides an item 'Inhibit sleep' which can be used to temporarily disable system suspension. 6 | 7 | This is a prototype using `systemd-inhibit`. A sophisticated implementation would probably use the systemd D-Bus \ 8 | interface documented [here](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html). 9 | """ 10 | 11 | from albert import * 12 | from subprocess import Popen, TimeoutExpired 13 | 14 | md_iid = '2.3' 15 | md_version = '1.2' 16 | md_name = 'Inhibit sleep' 17 | md_description = 'Inhibit system sleep mode.' 18 | md_license = "MIT" 19 | md_url = 'https://github.com/albertlauncher/python/tree/main/inhibit_sleep' 20 | md_authors = "@manuelschneid3r" 21 | md_bin_dependencies = ['systemd-inhibit', "sleep"] 22 | 23 | 24 | class Plugin(PluginInstance, GlobalQueryHandler): 25 | 26 | def __init__(self): 27 | PluginInstance.__init__(self) 28 | GlobalQueryHandler.__init__( 29 | self, self.id, self.name, self.description, 30 | defaultTrigger='is ' 31 | ) 32 | self.proc = None 33 | 34 | def finalize(self): 35 | if self.proc: 36 | self.toggle() 37 | 38 | def toggle(self): 39 | if self.proc: 40 | self.proc.terminate() 41 | try: 42 | self.proc.wait(timeout=1) 43 | except TimeoutExpired: 44 | self.proc.kill() 45 | self.proc = None 46 | else: 47 | self.proc = Popen(["systemd-inhibit", 48 | "--what=idle:sleep", "--who=Albert", "--why=User", 49 | "sleep", "infinity"]) 50 | info(str(self.proc)) 51 | 52 | def configWidget(self): 53 | return [ 54 | { 55 | 'type': 'label', 56 | 'text': __doc__.strip(), 57 | 'widget_properties': { 58 | 'textFormat': 'Qt::MarkdownText' 59 | } 60 | } 61 | ] 62 | 63 | def handleGlobalQuery(self, query): 64 | stripped = query.string.strip().lower() 65 | if stripped in "inhibit sleep": 66 | return [ 67 | RankItem( 68 | StandardItem( 69 | id=md_name, 70 | text=md_name, 71 | subtext=f"{'Enable' if self.proc else 'Disable'} sleep mode", 72 | iconUrls=[f"gen:?text=💤"], 73 | actions=[Action("inhibit", "Toggle", self.toggle)] 74 | ), 75 | len(stripped)/len(md_name)) 76 | 77 | ] 78 | return [] 79 | -------------------------------------------------------------------------------- /.archive/ip/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Get internal and external IP address. 4 | 5 | Synopsis: """ 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import socket 10 | from urllib import request 11 | 12 | from albert import * 13 | 14 | __title__ = "IP Addresses" 15 | __version__ = "0.4.0" 16 | __triggers__ = "ip " 17 | __authors__ = ["Manuel S.", "Benedict Dudel"] 18 | 19 | iconPath = iconLookup("preferences-system-network") 20 | 21 | 22 | def handleQuery(query): 23 | if not query.isTriggered: 24 | return None 25 | 26 | with request.urlopen("https://ipecho.net/plain") as response: 27 | externalIP = response.read().decode() 28 | 29 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 30 | s.connect(("10.255.255.255", 1)) 31 | internalIP = s.getsockname()[0] 32 | s.close() 33 | 34 | items = [] 35 | if externalIP: 36 | items.append(Item( 37 | id = __title__, 38 | icon = iconPath, 39 | text = externalIP, 40 | subtext = "Your external ip address from ipecho.net", 41 | actions = [ClipAction("Copy ip address to clipboard", externalIP)] 42 | )) 43 | 44 | if internalIP: 45 | items.append(Item( 46 | id = __title__, 47 | icon = iconPath, 48 | text = internalIP, 49 | subtext = "Your internal ip address", 50 | actions = [ClipAction("Copy ip address to clipboard", internalIP)] 51 | )) 52 | 53 | return items 54 | -------------------------------------------------------------------------------- /.archive/lpass/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """LastPass Vault Search 4 | 5 | Synopsis: """ 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | from shutil import which 10 | from albert import * 11 | import subprocess 12 | import re 13 | import os 14 | 15 | __title__ = 'LastPass' 16 | __version__ = '0.4.1' 17 | __triggers__ = 'lp ' 18 | __authors__ = 'David Piçarra' 19 | __exec_deps__ = ['lpass'] 20 | 21 | if not which('lpass'): 22 | raise Exception("`lpass` is not in $PATH.") 23 | clipmgrs = ['xclip', 'xsel', 'pbcopy', 'putclip'] 24 | hasclipmgr = False 25 | for mgr in clipmgrs: 26 | if which(mgr): 27 | hasclipmgr = True 28 | break 29 | if not hasclipmgr: 30 | raise Exception("`xclip`, `xsel`, `pbcopy`, or `putclip` is not in $PATH.") 31 | 32 | ICON_PATH = os.path.dirname(__file__)+"/lastpass.svg" 33 | 34 | def handleQuery(query): 35 | if query.isTriggered: 36 | stripped = query.string.strip() 37 | 38 | try: 39 | lpass = subprocess.check_output(['lpass', 'status']) 40 | except Exception as e: 41 | return Item( 42 | id=__title__, 43 | icon=ICON_PATH, 44 | text=f'Not logged in.', 45 | subtext=f'Please enter your lastpass email address', 46 | actions=[ 47 | ProcAction("lpass login with given email", ["lpass", "login", stripped]), 48 | ] 49 | ) 50 | 51 | 52 | if stripped: 53 | try: 54 | lpass = subprocess.Popen(['lpass', 'ls', '--long'], stdout=subprocess.PIPE) 55 | try: 56 | output = subprocess.check_output(['grep', '-i', stripped], stdin=lpass.stdout) 57 | except subprocess.CalledProcessError as e: 58 | return Item( 59 | id=__title__, 60 | icon=ICON_PATH, 61 | text=__title__, 62 | subtext=f'No results found for {stripped}' 63 | ) 64 | items = [] 65 | for line in output.splitlines(): 66 | match = re.match(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2} (.*) \[id: (\d*)\] \[username: (.*)\]', line.decode("utf-8")) 67 | items.append(Item( 68 | id=__title__, 69 | icon=ICON_PATH, 70 | text=match.group(1), 71 | subtext=match.group(3), 72 | actions=[ 73 | ProcAction("Copy password to clipboard", ["lpass", "show", "-cp", match.group(2)]), 74 | ProcAction("Copy username to clipboard", ["lpass", "show", "-cu", match.group(2)]), 75 | ProcAction("Copy notes to clipboard", ["lpass", "show", "-c", "--notes", match.group(2)]) 76 | ] 77 | )) 78 | 79 | return items 80 | 81 | except subprocess.CalledProcessError as e: 82 | return Item( 83 | id=__title__, 84 | icon=ICON_PATH, 85 | text=f'Error: {str(e.output)}', 86 | subtext=str(e), 87 | actions=[ClipAction('Copy CalledProcessError to clipboard', str(e))] 88 | ) 89 | except Exception as e: 90 | return Item( 91 | id=__title__, 92 | icon=ICON_PATH, 93 | text=f'Generic Exception: {str(e)}', 94 | subtext=str(e), 95 | actions=[ClipAction('Copy Exception to clipboard', str(e))] 96 | ) 97 | 98 | else: 99 | return Item( 100 | id=__title__, 101 | icon=ICON_PATH, 102 | text=__title__, 103 | subtext='Search the LastPass vault' 104 | ) 105 | -------------------------------------------------------------------------------- /.archive/lpass/lastpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.archive/mathematica_eval/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import subprocess 4 | from tempfile import NamedTemporaryFile 5 | from threading import Lock 6 | 7 | from albert import * 8 | 9 | md_iid = "2.0" 10 | md_version = "1.1" 11 | md_name = "Mathematica Eval" 12 | md_description = "Evaluate Mathemtica code" 13 | md_license = "GPL-3.0" 14 | md_url = "https://github.com/albertlauncher/python/tree/master/mathematica_eval" 15 | md_maintainers = "@tyilo" 16 | md_bin_dependencies = ["wolframscript"] 17 | 18 | 19 | class Plugin(PluginInstance, TriggerQueryHandler): 20 | 21 | def __init__(self): 22 | TriggerQueryHandler.__init__(self, 23 | id=md_id, 24 | name=md_name, 25 | description=md_description, 26 | synopsis='', 27 | defaultTrigger='mma ') 28 | PluginInstance.__init__(self, extensions=[self]) 29 | 30 | def handleTriggerQuery(self, query: TriggerQuery) -> None: 31 | stripped = query.string.strip() 32 | if not stripped: 33 | return 34 | 35 | with NamedTemporaryFile("w") as f: 36 | f.write(stripped) 37 | f.flush() 38 | process = subprocess.Popen( 39 | ["wolframscript", "-print", "-f", f.name], 40 | encoding="utf-8", 41 | stdout=subprocess.PIPE, 42 | ) 43 | 44 | while True: 45 | if not query.isValid: 46 | process.kill() 47 | return 48 | 49 | try: 50 | output, _ = process.communicate(timeout=0.1) 51 | break 52 | except subprocess.TimeoutExpired: 53 | pass 54 | 55 | result_str = output.strip() 56 | 57 | query.add( 58 | StandardItem( 59 | id=md_id, 60 | text=result_str, 61 | inputActionText=query.trigger + result_str, 62 | iconUrls=["xdg:wolfram-mathematica"], 63 | actions=[ 64 | Action( 65 | "copy", 66 | "Copy result to clipboard", 67 | lambda r=result_str: setClipboardText(r), 68 | ), 69 | ], 70 | ) 71 | ) 72 | -------------------------------------------------------------------------------- /.archive/multi_google_translate/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Use Google Translate to translate your sentence into multiple languages. 4 | 5 | Visit the following link to check available languages: \ 6 | https://cloud.google.com/translate/docs/languages. To add or remove languages use modifier key \ 7 | when trigger is activated or go to: '~/.config/albert/org.albert.extension.mtr/config.json' \ 8 | Add or remove elements based on the ISO-Codes that you found on the google documentation page. 9 | 10 | Synopsis: [query]""" 11 | 12 | # Copyright (c) 2022 Manuel Schneider 13 | 14 | import json 15 | import os 16 | import urllib.error 17 | import urllib.parse 18 | import urllib.request 19 | from time import sleep 20 | 21 | from albert import (ClipAction, Item, ProcAction, UrlAction, configLocation, 22 | iconLookup) 23 | 24 | __title__ = "MultiTranslate" 25 | __version__ = "0.4.2" 26 | __triggers__ = "mtr " 27 | __authors__ = "David Britt" 28 | 29 | iconPath = iconLookup('config-language') 30 | if not iconPath: 31 | iconPath = ":python_module" 32 | 33 | ua = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36" 34 | urltmpl = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=%s&dt=t&q=%s" 35 | urlbrowser = "https://translate.google.com/#auto/%s/%s" 36 | configurationFileName = "language_config.json" 37 | configuration_directory = os.path.join(configLocation(), __title__) 38 | language_configuration_file = os.path.join(configuration_directory, configurationFileName) 39 | languages = [] 40 | 41 | def initialize(): 42 | if os.path.exists(language_configuration_file): 43 | with open(language_configuration_file) as json_config: 44 | languages.extend(json.load(json_config)["languages"]) 45 | else: 46 | languages.extend(["en", "zh-CN", "hi", "es", "ru", "pt", "id", "bn", "ar", "ms", "ja", "fr", "de"]) 47 | try: 48 | os.makedirs(configuration_directory, exist_ok=True) 49 | try: 50 | with open(language_configuration_file, "w") as output_file: 51 | json.dump({"languages": languages}, output_file) 52 | except OSError: 53 | print("There was an error opening the file: %s" % language_configuration_file) 54 | except OSError: 55 | print("There was an error making the directory: %s" % configuration_directory) 56 | 57 | 58 | def handleQuery(query): 59 | results = [] 60 | if query.isTriggered: 61 | 62 | # avoid rate limiting 63 | sleep(0.2) 64 | if not query.isValid: 65 | return 66 | 67 | item = Item( 68 | id=__title__, 69 | icon=iconPath, 70 | text=__title__, 71 | actions=[ProcAction("Open the language configuration file.", 72 | commandline=["xdg-open", language_configuration_file])] 73 | ) 74 | if len(query.string) >= 2: 75 | for lang in languages: 76 | try: 77 | url = urltmpl % (lang, urllib.parse.quote_plus(query.string)) 78 | req = urllib.request.Request(url, headers={'User-Agent': ua}) 79 | with urllib.request.urlopen(req) as response: 80 | #print(type()) 81 | #try: 82 | data = json.loads(response.read().decode()) 83 | #except TypeError as typerr: 84 | # print("Urgh this type.error. %s" % typerr) 85 | translText = data[0][0][0] 86 | sourceText = data[2] 87 | if sourceText == lang: 88 | continue 89 | else: 90 | results.append( 91 | Item( 92 | id=__title__, 93 | icon=iconPath, 94 | text="%s" % (translText), 95 | subtext="%s" % lang.upper(), 96 | actions=[ 97 | ClipAction("Copy translation to clipboard", translText), 98 | UrlAction("Open in your Browser", urlbrowser % (lang, query.string)) 99 | ] 100 | ) 101 | ) 102 | except urllib.error.URLError as urlerr : 103 | print("Check your internet connection: %s" % urlerr) 104 | item.subtext = "Check your internet connection." 105 | return item 106 | else: 107 | item.subtext = "Enter a query: 'mtr <text>'. Languages {%s}" % ", ".join(languages) 108 | return item 109 | return results 110 | -------------------------------------------------------------------------------- /.archive/node_eval/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Evaluate simple JavaScript expressions. Use it with care every keystroke triggers an evaluation.""" 4 | 5 | # Copyright (c) 2022 Manuel Schneider 6 | 7 | import os 8 | import subprocess 9 | from albert import * 10 | 11 | __title__ = 'Node Eval' 12 | __version__ = '0.4.0' 13 | __triggers__ = 'node ' 14 | __authors__ = 'Hammed Oyedele' 15 | __exec_deps__ = ['node'] 16 | 17 | iconPath = os.path.dirname(__file__) + '/nodejs.svg' 18 | 19 | 20 | def run(exp): 21 | return subprocess.getoutput('node --print "%s"' % exp.replace('"', '\\"')) 22 | 23 | 24 | def handleQuery(query): 25 | if query.isTriggered: 26 | item = Item( 27 | id=__title__, 28 | icon=iconPath 29 | ) 30 | stripped = query.string.strip() 31 | 32 | if stripped == '': 33 | item.text = 'Enter a JavaScript expression...' 34 | else: 35 | item.text = run(stripped) 36 | item.subtext = run( 37 | 'Object.prototype.toString.call(%s).slice(8, -1).toLowerCase()' % stripped) 38 | item.addAction(ClipAction('Copy result to clipboard', item.text)) 39 | 40 | return item 41 | -------------------------------------------------------------------------------- /.archive/npm/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Install, remove and search packages in the npmjs.com database. 4 | 5 | If no search query is supplied you have the option to update all globally installed packages. 6 | 7 | Synopsis: [filter]""" 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | from albert import * 12 | import os 13 | import json 14 | import subprocess 15 | 16 | __title__ = "npm" 17 | __version__ = "0.4.0" 18 | __triggers__ = "npm " 19 | __authors__ = "Benedict Dudel" 20 | __exec_deps__ = ["npm"] 21 | 22 | iconPath = iconLookup("npm") or os.path.dirname(__file__)+"/logo.svg" 23 | 24 | def handleQuery(query): 25 | if query.isTriggered: 26 | if not query.string.strip(): 27 | return Item( 28 | id = __title__, 29 | icon = iconPath, 30 | text = "Update", 31 | subtext = "Update all globally installed packages", 32 | actions = [ 33 | TermAction("Update packages", ["npm", "update", "--global"]) 34 | ] 35 | ) 36 | 37 | items = getSearchResults(query.string.strip()) 38 | if not items: 39 | return Item( 40 | id = __title__, 41 | icon = iconPath, 42 | text = "Search on npmjs.com", 43 | subtext = "No modules found in local database. Try to search on npmjs.com", 44 | actions = [ 45 | UrlAction( 46 | "Search on npmjs.com", 47 | "https://www.npmjs.com/search?q=%s" % query.string.strip() 48 | ) 49 | ] 50 | ) 51 | 52 | return items 53 | 54 | def getSearchResults(query): 55 | proc = subprocess.run(["npm", "search", "--json", query], stdout=subprocess.PIPE) 56 | 57 | items = [] 58 | for module in json.loads(proc.stdout.decode()): 59 | items.append( 60 | Item( 61 | id = __title__, 62 | icon = iconPath, 63 | text = "%s (%s)" % (module["name"], module["version"]), 64 | subtext = module.get("description", ""), 65 | actions = [ 66 | UrlAction("Open module on npmjs.com", "https://www.npmjs.com/package/%s" % module["name"]), 67 | TermAction("Install", ["npm", "install", "--global", module["name"]]), 68 | TermAction("Update", ["npm", "update", "--global", module["name"]]), 69 | TermAction("Remove", ["npm", "uninstall", "--global", module["name"]]), 70 | ] 71 | ) 72 | ) 73 | 74 | return items 75 | -------------------------------------------------------------------------------- /.archive/npm/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.archive/packagist/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Search for PHP packages on Packagist. 4 | 5 | To install packages you need to have installed composer. By default this extension will search by \ 6 | package name. But searching for packages by type or tag is supported as well. 7 | 8 | Synopsis: [tag|type] """ 9 | 10 | # Copyright (c) 2022 Manuel Schneider 11 | 12 | from albert import * 13 | import os 14 | import json 15 | import urllib.request 16 | 17 | __title__ = "Packagist" 18 | __version__ = "0.4.0" 19 | __triggers__ = "packagist " 20 | __authors__ = "Benedict Dudel" 21 | __exec_deps__ = ["composer"] 22 | 23 | iconPath = os.path.dirname(__file__)+"/logo.png" 24 | 25 | 26 | def handleQuery(query): 27 | if query.isTriggered: 28 | if not query.string.strip(): 29 | return [ 30 | Item( 31 | id = "packagist-search-by-tag", 32 | icon = iconPath, 33 | text = "by tag", 34 | subtext = "Searching for packages by tag", 35 | completion = "%stag " % __triggers__, 36 | actions=[] 37 | ), 38 | Item( 39 | id = "packagist-search-by-type", 40 | icon = iconPath, 41 | text = "by type", 42 | subtext = "Searching for packages by type", 43 | completion = "%stype " % __triggers__, 44 | actions=[] 45 | ) 46 | ] 47 | 48 | if query.string.strip().startswith("tag "): 49 | if query.string.strip()[4:]: 50 | return getItems("https://packagist.org/search.json?tags=%s" % query.string.strip()[4:]) 51 | 52 | if query.string.strip().startswith("type "): 53 | if query.string.strip()[5:]: 54 | return getItems("https://packagist.org/search.json?type=%s" % query.string.strip()[5:]) 55 | 56 | return getItems("https://packagist.org/search.json?q=%s" % query.string) 57 | 58 | def getItems(url): 59 | items = [] 60 | with urllib.request.urlopen(url) as uri: 61 | packages = json.loads(uri.read().decode()) 62 | for package in packages['results']: 63 | items.append( 64 | Item( 65 | id = "packagist-package-%s" % package["name"], 66 | icon = iconPath, 67 | text = package["name"], 68 | subtext = package["description"], 69 | completion = "%sname %s" % (__triggers__, package["name"]), 70 | actions = [ 71 | UrlAction( 72 | text = "Open on packagist.org", 73 | url = package["url"] 74 | ), 75 | UrlAction( 76 | text = "Open url of repository", 77 | url = package["repository"] 78 | ), 79 | TermAction( 80 | text = "Install", 81 | commandline = ["composer", "global", "require", package['name']] 82 | ), 83 | TermAction( 84 | text = "Remove", 85 | commandline = ["composer", "global", "remove", package['name']] 86 | ) 87 | ] 88 | ) 89 | ) 90 | 91 | return items 92 | -------------------------------------------------------------------------------- /.archive/packagist/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/packagist/logo.png -------------------------------------------------------------------------------- /.archive/php_eval/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Evaluate simple PHP expressions. Use it with care every keystroke triggers an evaluation.""" 4 | 5 | # Copyright (c) 2022 Manuel Schneider 6 | 7 | import os 8 | import subprocess 9 | from albert import * 10 | 11 | __title__ = 'PHP Eval' 12 | __version__ = '0.4.0' 13 | __triggers__ = 'php ' 14 | __authors__ = 'Hammed Oyedele' 15 | __exec_deps__ = ['php'] 16 | 17 | iconPath = os.path.dirname(__file__) + '/php.svg' 18 | 19 | def run(exp): 20 | return subprocess.getoutput('php -r "%s"' % exp.replace('"', '\\"')) 21 | 22 | 23 | def handleQuery(query): 24 | if query.isTriggered: 25 | item = Item( 26 | id=__title__, 27 | icon=iconPath 28 | ) 29 | stripped = query.string.strip() 30 | 31 | if stripped == '': 32 | item.text = 'Enter a PHP expression...' 33 | else: 34 | item.text = run('echo %s;' % stripped) 35 | item.subtext = run('echo gettype(%s);' % stripped) 36 | item.addAction(ClipAction('Copy result to clipboard', item.text)) 37 | 38 | return item 39 | -------------------------------------------------------------------------------- /.archive/php_eval/php.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /.archive/pidgin/__init__.py: -------------------------------------------------------------------------------- 1 | """Open Pidgin chats. 2 | 3 | Matching contacts will be suggested. 4 | 5 | Synopsis: """ 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import dbus 10 | 11 | from albert import * 12 | 13 | __title__ = "Pidgin" 14 | __version__ = "0.4.0" 15 | __authors__ = "Greizgh" 16 | __triggers__ = "pidgin " 17 | __exec_deps__ = ["python"] 18 | __py_deps__ = ["dbus"] 19 | 20 | iconPath = iconLookup("pidgin") 21 | bus = dbus.SessionBus() 22 | 23 | 24 | class ContactHandler: 25 | """Handle pidgin contact list""" 26 | 27 | _purple = None 28 | _contacts = [] 29 | 30 | def __init__(self): 31 | self.refresh() 32 | 33 | def refresh(self): 34 | """Refresh both dbus connection and pidgin contact list""" 35 | try: 36 | self._contacts = [] 37 | self._purple = bus.get_object( 38 | "im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject" 39 | ) 40 | accounts = self._purple.PurpleAccountsGetAllActive() 41 | for account in accounts: 42 | buddies = self._purple.PurpleFindBuddies(account, "") 43 | for buddy in buddies: 44 | # if purple.PurpleBuddyIsOnline(buddy): 45 | name = self._purple.PurpleBuddyGetAlias(buddy) 46 | self._contacts.append((name, account)) 47 | except dbus.DBusException: 48 | critical("Could not connect to pidgin service") 49 | 50 | def isReady(self): 51 | """Check that this handler is ready to communicate""" 52 | return self._purple is not None 53 | 54 | def chatWith(self, account, name): 55 | """Open a pidgin chat window""" 56 | self._purple.PurpleConversationNew(1, account, name) 57 | 58 | def getMatch(self, query): 59 | """Get buddies matching query""" 60 | normalized = query.lower() 61 | return [item for item in self._contacts if normalized in item[0].lower()] 62 | 63 | 64 | handler = ContactHandler() 65 | 66 | 67 | def handleQuery(query): 68 | if not handler.isReady(): 69 | handler.refresh() 70 | 71 | if query.isTriggered: 72 | target = query.string.strip() 73 | 74 | if target: 75 | items = [] 76 | for match in handler.getMatch(target): 77 | items.append( 78 | Item( 79 | id=__title__, 80 | icon=iconPath, 81 | text="Chat with {}".format(match[0]), 82 | subtext="Open a pidgin chat window", 83 | completion=match[0], 84 | actions=[ 85 | FuncAction( 86 | "Open chat window", 87 | lambda: handler.chatWith(match[1], match[0]), 88 | ) 89 | ], 90 | ) 91 | ) 92 | return items 93 | -------------------------------------------------------------------------------- /.archive/rand/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Draws a random integer. 4 | 5 | This extension provides the rand item, which can be used with : 6 | - 1 argument a : draws an integer between 1 and a (included) 7 | - 2 argumens a, b: draws an integer between a and b (included) 8 | - 3 arguments a, b, nb: draws nb integers between a and b (included) 9 | 10 | Synopsis: 11 | rand [min] max [numbers] 12 | """ 13 | 14 | import os 15 | 16 | import random 17 | 18 | from albertv0 import * 19 | 20 | __iid__ = "PythonInterface/v0.1" 21 | __prettyname__ = "Rand" 22 | __version__ = "0.1" 23 | __trigger__ = "rand " 24 | __author__ = "Cyprien Ruffino" 25 | __dependencies__ = [] 26 | 27 | 28 | usage_string = "Usage: [min] max [numbers]" 29 | 30 | 31 | def createBlankItem(text): 32 | return Item( 33 | id=__prettyname__, 34 | icon="rand/rand.png", 35 | text=str(text), 36 | subtext="", 37 | actions=[]) 38 | 39 | 40 | def handleQuery(query): 41 | if query.isTriggered: 42 | 43 | tokens = query.string.split(" ") 44 | 45 | # No arguments 46 | if len(tokens) == 1 and tokens[0] == "": 47 | return createBlankItem(usage_string) 48 | 49 | # At least one argument 50 | try: 51 | tokens = [int(token) for token in tokens] 52 | except ValueError: 53 | return createBlankItem(usage_string) 54 | 55 | if len(tokens) == 1: 56 | b = tokens[0] 57 | rand = random.randint(1, b) 58 | return createBlankItem(str(rand)) 59 | 60 | elif len(tokens) == 2: 61 | a, b = tokens 62 | rand = random.randint(a, b) 63 | return createBlankItem(str(rand)) 64 | 65 | elif len(tokens) == 3: 66 | a, b, nb = tokens 67 | items = [] 68 | for i in range(nb): 69 | rand = random.randint(a, b) 70 | items.append(createBlankItem(rand)) 71 | return items 72 | 73 | else: 74 | return createBlankItem(usage_string) 75 | -------------------------------------------------------------------------------- /.archive/rand/rand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/rand/rand.png -------------------------------------------------------------------------------- /.archive/scrot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Take screenshots of screens, areas or windows. 4 | 5 | This extension wraps the command line utility scrot to make screenshots from albert. When the \ 6 | screenshot was made you will hear a sound which indicates that the screenshot was taken \ 7 | successfully.Screenshots will be saved in XDG_PICTURES_DIR or in the temp directory. 8 | 9 | Synopsis: """ 10 | 11 | # Copyright (c) 2022 Manuel Schneider 12 | 13 | import os 14 | import subprocess 15 | import tempfile 16 | from shutil import which 17 | 18 | from albert import FuncAction, Item, iconLookup 19 | 20 | __title__ = "SCReenshOT utility" 21 | __version__ = "0.4.0" 22 | __triggers__ = "scrot " 23 | __authors__ = "Benedict Dudel" 24 | __exec_deps__ = ["scrot", "xclip"] 25 | 26 | iconPath = iconLookup("camera-photo") 27 | 28 | 29 | def handleQuery(query): 30 | if query.isTriggered: 31 | return [ 32 | Item( 33 | id = "%s-whole-screen" % __title__, 34 | icon = iconPath, 35 | text = "Screen", 36 | subtext = "Take a screenshot of the whole screen", 37 | actions = [ 38 | FuncAction( 39 | "Take screenshot of whole screen", 40 | lambda: doScreenshot([]) 41 | ), 42 | FuncAction( 43 | "Take screenshot of multiple displays", 44 | lambda: doScreenshot(["--multidisp"]) 45 | ), 46 | ] 47 | ), 48 | Item( 49 | id = "%s-area-of-screen" % __title__, 50 | icon = iconPath, 51 | text = "Area", 52 | subtext = "Draw a rectangle with your mouse to capture an area", 53 | actions = [ 54 | FuncAction( 55 | "Take screenshot of selected area", 56 | lambda: doScreenshot(["--select"]) 57 | ), 58 | ] 59 | ), 60 | Item( 61 | id = "%s-current-window" % __title__, 62 | icon = iconPath, 63 | text = "Window", 64 | subtext = "Take a screenshot of the current active window", 65 | actions = [ 66 | FuncAction( 67 | "Take screenshot of window with borders", 68 | lambda: doScreenshot(["--focused", "--border"]) 69 | ), 70 | FuncAction( 71 | "Take screenshot of window without borders", 72 | lambda: doScreenshot(["--focused"]) 73 | ), 74 | ] 75 | ), 76 | ] 77 | 78 | def getScreenshotDirectory(): 79 | if which("xdg-user-dir") is None: 80 | return tempfile.gettempdir() 81 | 82 | proc = subprocess.run(["xdg-user-dir", "PICTURES"], stdout=subprocess.PIPE) 83 | 84 | pictureDirectory = proc.stdout.decode("utf-8") 85 | if pictureDirectory: 86 | return pictureDirectory.strip() 87 | 88 | return tempfile.gettempdir() 89 | 90 | def doScreenshot(additionalArguments): 91 | file = os.path.join(getScreenshotDirectory(), "%Y-%m-%d-%T-screenshot.png") 92 | 93 | command = "sleep 0.1 && scrot --exec 'xclip -selection c -t image/png < $f' %s " % file 94 | proc = subprocess.Popen(command + " ".join(additionalArguments), shell=True) 95 | -------------------------------------------------------------------------------- /.archive/texdoc/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """texdoc extension 4 | 5 | This is an extension to search for LaTeX documentation. 6 | 7 | Synopsis: """ 8 | 9 | # Copyright (c) 2022 Manuel Schneider 10 | 11 | import re 12 | import subprocess 13 | from pathlib import Path 14 | from albert import * 15 | 16 | __title__ = 'TeXdoc' 17 | __version__ = '0.4.0' 18 | __triggers__ = 'td' 19 | __authors__ = 'Florian Adamsky (@cit)' 20 | __exec_deps__ = ['texdoc'] 21 | 22 | iconPath = Path(__file__).parent / 'texdoc-logo.svg' 23 | texdoc_cmd = ['texdoc', '-I', '-q', '-s', '-M'] 24 | 25 | def handleQuery(query): 26 | if not query.isTriggered: 27 | return 28 | 29 | query.disableSort() 30 | 31 | stripped_query = query.string.strip() 32 | 33 | if stripped_query: 34 | process = subprocess.run(texdoc_cmd + [stripped_query], 35 | stdout=subprocess.PIPE) 36 | texdoc_output = process.stdout.decode('utf-8') 37 | 38 | results = [] 39 | for line in texdoc_output.split("\n"): 40 | 41 | match = re.search('\t(/.*/)([\w\.-]+)\t\t', line, re.IGNORECASE) 42 | if match: 43 | directory = match.group(1).strip() 44 | filename = match.group(2).strip() 45 | full_path = directory.join(['/', filename]) 46 | 47 | results.append(Item(id = __title__, 48 | icon = str(iconPath), 49 | text = filename, 50 | subtext = directory, 51 | completion = full_path, 52 | actions = [ 53 | ProcAction(text = 'This action opens the documentation.', 54 | commandline=['xdg-open', full_path]) 55 | ])) 56 | 57 | return results 58 | else: 59 | return Item(id = __title__, 60 | icon = str(iconPath), 61 | text = __title__, 62 | subtext = 'Enter a query to search with texdoc', 63 | completion = query.rawString) 64 | -------------------------------------------------------------------------------- /.archive/timer/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # # Copyright (c) 2018-2024 Manuel Schneider 3 | # # Copyright (c) 2020 Andreas Dominik Preikschat 4 | 5 | """ 6 | Takes arguments in the form of '`[[hrs:]mins:]secs [name]`'. Empty fields resolve to `0`. \ 7 | Fields exceeding the maximum amount of the time interval are automatically refactorized. 8 | 9 | Examples: 10 | - `5:` starts a 5 minutes timer 11 | - `1:: ` starts a 1 hour timer 12 | - `120:` starts a 2 hours timer 13 | """ 14 | 15 | import threading 16 | from datetime import timedelta 17 | from pathlib import Path 18 | from time import strftime, time, localtime 19 | 20 | from albert import * 21 | 22 | md_iid = '2.3' 23 | md_version = "1.8" 24 | md_name = "Timer" 25 | md_description = "Set up timers" 26 | md_license = "MIT" 27 | md_url = "https://github.com/albertlauncher/python/tree/main/timer" 28 | md_authors = ["@manuelschneid3r", "@googol42"] 29 | 30 | 31 | class Timer(threading.Timer): 32 | 33 | def __init__(self, interval, name, callback): 34 | super().__init__(interval=interval, 35 | function=lambda: callback(self)) 36 | self.name = name 37 | self.begin = int(time()) 38 | self.end = self.begin + interval 39 | self.start() 40 | 41 | 42 | class Plugin(PluginInstance, TriggerQueryHandler): 43 | 44 | def __init__(self): 45 | TriggerQueryHandler.__init__(self, 46 | id=md_id, 47 | name=md_name, 48 | description=md_description, 49 | synopsis='[[hrs:]mins:]secs [name]', 50 | defaultTrigger='timer ') 51 | PluginInstance.__init__(self) 52 | self.iconUrls = [f"file:{Path(__file__).parent}/time.svg"] 53 | self.soundPath = Path(__file__).parent / "bing.wav" 54 | self.timers = [] 55 | self.notification = None 56 | 57 | def finalize(self): 58 | for timer in self.timers: 59 | timer.cancel() 60 | self.timers.clear() 61 | 62 | def startTimer(self, interval, name): 63 | self.timers.append(Timer(interval, name, self.onTimerTimeout)) 64 | 65 | def deleteTimer(self, timer): 66 | self.timers.remove(timer) 67 | timer.cancel() 68 | 69 | def onTimerTimeout(self, timer): 70 | self.notification = Notification( 71 | title=f"Timer '{timer.name if timer.name else 'Timer'}'", 72 | body=f"Timed out at {strftime('%X', localtime(timer.end))}" 73 | ) 74 | self.deleteTimer(timer) 75 | 76 | def configWidget(self): 77 | return [ 78 | { 79 | 'type': 'label', 80 | 'text': __doc__.strip(), 81 | 'widget_properties': { 'textFormat': 'Qt::MarkdownText' } 82 | } 83 | ] 84 | 85 | def handleTriggerQuery(self, query): 86 | if not query.isValid: 87 | return 88 | 89 | if query.string.strip(): 90 | args = query.string.strip().split(maxsplit=1) 91 | fields = args[0].split(":") 92 | name = args[1] if 1 < len(args) else '' 93 | if not all(field.isdigit() or field == '' for field in fields): 94 | return StandardItem( 95 | id=self.name, 96 | text="Invalid input", 97 | subtext="Enter a query in the form of '%s[[hours:]minutes:]seconds [name]'" % self.defaultTrigger(), 98 | iconUrls=self.iconUrls, 99 | ) 100 | 101 | seconds = 0 102 | fields.reverse() 103 | for i in range(len(fields)): 104 | seconds += int(fields[i] if fields[i] else 0)*(60**i) 105 | 106 | query.add(StandardItem( 107 | id=self.name, 108 | text=str(timedelta(seconds=seconds)), 109 | subtext='Set a timer with name "%s"' % name if name else 'Set a timer', 110 | iconUrls=self.iconUrls, 111 | actions=[Action("set-timer", "Set timer", lambda sec=seconds: self.startTimer(sec, name))] 112 | )) 113 | return 114 | 115 | # List timers 116 | items = [] 117 | for timer in self.timers: 118 | m, s = divmod(timer.interval, 60) 119 | h, m = divmod(m, 60) 120 | identifier = "%d:%02d:%02d" % (h, m, s) 121 | 122 | timer_name_with_quotes = '"%s"' % timer.name if timer.name else '' 123 | items.append(StandardItem( 124 | id=self.name, 125 | text='Delete timer %s [%s]' % (timer_name_with_quotes, identifier), 126 | subtext="Times out %s" % strftime("%X", localtime(timer.end)), 127 | iconUrls=self.iconUrls, 128 | actions=[Action("delete-timer", "Delete timer", lambda t=timer: self.deleteTimer(t))] 129 | )) 130 | 131 | if items: 132 | query.add(items) 133 | -------------------------------------------------------------------------------- /.archive/tomboy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Search, open, create and delete Tomboy notes. 4 | 5 | Synopsis: """ 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import re 10 | from datetime import datetime 11 | from dbus import DBusException, Interface, SessionBus 12 | from albert import * 13 | 14 | __title__ = "Tomboy" 15 | __version__ = "0.4.1" 16 | __triggers__ = "tb " 17 | __authors__ = "Manuel S." 18 | __exec_deps__ = ["tomboy"] 19 | __py_deps__ = ["dbus"] 20 | 21 | BUS = "org.gnome.%s" % __title__ 22 | OBJ = "/org/gnome/%s/RemoteControl" % __title__ 23 | IFACE = 'org.gnome.%s.RemoteControl' % __title__ 24 | iconPath = iconLookup("tomboy") 25 | 26 | def handleQuery(query): 27 | results = [] 28 | if query.isTriggered: 29 | try: 30 | if not SessionBus().name_has_owner(BUS): 31 | warning("Seems like %s is not running" % __title__) 32 | return 33 | 34 | obj = SessionBus().get_object(bus_name=BUS, object_path=OBJ) 35 | iface = Interface(obj, dbus_interface=IFACE) 36 | 37 | if query.string.strip(): 38 | for note in iface.SearchNotes(query.string.lower(), False): 39 | results.append( 40 | Item(id="%s%s" % (__title__, note), 41 | icon=iconPath, 42 | text=iface.GetNoteTitle(note), 43 | subtext="%s%s" % ("".join(["#%s " % re.search('.+:.+:(.+)', s).group(1) for s in iface.GetTagsForNote(note)]), 44 | datetime.fromtimestamp(iface.GetNoteChangeDate(note)).strftime("Note from %c")), 45 | actions=[ 46 | FuncAction("Open note", 47 | lambda note=note: iface.DisplayNote(note)), 48 | FuncAction("Delete note", 49 | lambda note=note: iface.DeleteNote(note)) 50 | ])) 51 | else: 52 | def createAndShowNote(): 53 | note = iface.CreateNote() 54 | iface.DisplayNote(note) 55 | 56 | results.append(Item(id="%s-create" % __title__, 57 | icon=iconPath, 58 | text=__title__, 59 | subtext="%s notes" % __title__, 60 | actions=[ 61 | FuncAction("Open %s" % __title__, 62 | lambda: iface.DisplaySearch()), 63 | FuncAction("Create a new note", createAndShowNote) 64 | ])) 65 | except DBusException as e: 66 | critical(str(e)) 67 | return results 68 | -------------------------------------------------------------------------------- /.archive/unicode_emoji/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Offline Unicode emoji picker. 4 | 5 | Synopsis: [filter]""" 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | from albert import * 10 | from collections import namedtuple 11 | from threading import Thread 12 | import datetime 13 | import os 14 | import subprocess 15 | import urllib.request 16 | import shutil 17 | 18 | __title__ = "Unicode Emojis" 19 | __version__ = "0.4.3" 20 | __triggers__ = ":" 21 | __authors__ = ["Tim Zeitz", "Manuel S."] 22 | __exec_deps__ = ["convert"] 23 | 24 | EmojiSpec = namedtuple('EmojiSpec', ['string', 'name', 'modifiers']) 25 | emoji_data_src_url = "https://unicode.org/Public/emoji/latest/emoji-test.txt" 26 | emoji_data_path = os.path.join(dataLocation(), "emoji.txt") 27 | icon_path_template = os.path.join(cacheLocation(), __name__, "%s.png") 28 | emojiSpecs = [] 29 | thread = None 30 | 31 | 32 | class WorkerThread(Thread): 33 | def __init__(self): 34 | super().__init__() 35 | self.stop = False 36 | 37 | def run(self): 38 | 39 | # Create cache dir 40 | cache_dir_path = os.path.join(cacheLocation(), __name__) 41 | if not os.path.exists(cache_dir_path): 42 | os.mkdir(cache_dir_path) 43 | 44 | # Build the index and icon cache 45 | # global emojiSpecs 46 | emojiSpecs.clear() 47 | with open(emoji_data_path) as f: 48 | for line in f: 49 | if "; fully-qualified" in line: 50 | emoji, desc = line.split('#', 1)[-1].split(None, 1) 51 | desc = [d.strip().lower() for d in desc.split(':')] 52 | emojiSpecs.append(EmojiSpec(emoji, desc[0], desc[1] if len(desc)==2 else "")) 53 | 54 | icon_path = icon_path_template % emoji 55 | if not os.path.exists(icon_path): 56 | subprocess.call(["convert", "-pointsize", "64", "-background", "transparent", "pango:%s" % emoji, icon_path]) 57 | 58 | if self.stop: 59 | return 60 | 61 | def initialize(): 62 | src_directory = os.path.dirname(os.path.realpath(__file__)) 63 | 64 | # if no emoji data exists copy offline src as fallback 65 | if not os.path.isfile(emoji_data_path): 66 | shutil.copyfile(os.path.join(src_directory, "emoji.txt"), emoji_data_path) 67 | 68 | current_version = get_emoji_data_version(emoji_data_path) 69 | 70 | try: 71 | new_path = os.path.join(dataLocation(), "emoji-new.txt") 72 | 73 | # try to fetch the latest emoji data 74 | with urllib.request.urlopen(emoji_data_src_url) as response, open(new_path, 'wb') as out_file: 75 | # save it 76 | shutil.copyfileobj(response, out_file) 77 | 78 | # update emoji data if the fetched data is newer 79 | if get_emoji_data_version(new_path) > current_version: 80 | shutil.copyfile(new_path, emoji_data_path) 81 | 82 | os.remove(new_path) 83 | 84 | except Exception as e: 85 | warning(e) 86 | 87 | # Build the index and icon cache 88 | global thread 89 | thread = WorkerThread() 90 | thread.start() 91 | 92 | def finalize(): 93 | global thread 94 | if thread is not None: 95 | thread.stop = True 96 | thread.join() 97 | 98 | def get_emoji_data_version(path): 99 | with open(emoji_data_path) as f: 100 | for line in f: 101 | if "# Date: " in line: 102 | return datetime.datetime.strptime(line.strip(), "# Date: %Y-%m-%d, %H:%M:%S GMT") 103 | 104 | def handleQuery(query): 105 | if query.isValid and query.isTriggered: 106 | items = [] 107 | query_tokens = query.string.lower().split() 108 | # filter emojiSpecs where all query words are in any of the emoji description words 109 | for es in filter(lambda e: all(any(n in s for s in [e.name, e.modifiers]) for n in query_tokens), emojiSpecs): 110 | items.append(Item(id = "%s%s" % (__name__, es.string), 111 | completion = es.name if not es.modifiers else " ".join([es.name, es.modifiers]), 112 | icon = icon_path_template % es.string, 113 | text = es.name.capitalize(), 114 | subtext = es.modifiers.capitalize() if es.modifiers else "(No modifiers)", 115 | actions = [ClipAction("Copy to clipboard", es.string)])) 116 | return items 117 | -------------------------------------------------------------------------------- /.archive/units/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Convert units. 4 | 5 | This extension is a wrapper for the (extremely) powerful GNU units tool. Note that spaces are \ 6 | interpreted as separators, i.e. dont use spaces between numbers and units. 7 | 8 | Synopsis: 9 | [dst] 10 | to """ 11 | 12 | # Copyright (c) 2022 Manuel Schneider 13 | 14 | import re 15 | import subprocess as sp 16 | from albert import * 17 | 18 | __title__ = "GNU Units" 19 | __version__ = "0.4.2" 20 | __triggers__ = "units " 21 | __authors__ = ["Manuel S.", "iyzana"] 22 | __exec_deps__ = ["units"] 23 | 24 | icon = iconLookup('calc') or ":python_module" 25 | 26 | regex = re.compile(r"(\S+)(?:\s+to)\s+(\S+)") 27 | unitListOutput = re.compile(r"(\d+(e[+-]\d{2,})?;)+[\d.]+(e[+-]\d{2,})?") 28 | 29 | 30 | def getUnitsResult(args): 31 | command = ['units', '--terse', '--'] + list(args) 32 | query = "units -t -- %s" % ' '.join(args) 33 | try: 34 | output = sp.check_output(command, stderr=sp.STDOUT).decode().strip() 35 | 36 | # usually we want terse output, but when we get a unit-list output 37 | # it looks like this 1;124;18;11;14.025322 which is not friendly 38 | # so we're falling back to not quite terse output 39 | if unitListOutput.fullmatch(output): 40 | command = ['units', '--strict', '--one-line', 41 | '--quiet', '--'] + list(args) 42 | query = "units -s1q -- %s" % ' '.join(args) 43 | output = sp.check_output( 44 | command, stderr=sp.STDOUT).decode().strip() 45 | 46 | return (output, query, True) 47 | except sp.CalledProcessError as e: 48 | return (e.stdout.decode().strip().splitlines()[0], query, False) 49 | 50 | 51 | def handleQuery(query): 52 | if query.isTriggered: 53 | args = query.string.split() 54 | item = Item(id='python.gnu_units', icon=icon) 55 | if args: 56 | result, command, success = getUnitsResult(args) 57 | item.text = result 58 | item.subtext = "Result of '%s'" % command 59 | item.addAction(ClipAction("Copy to clipboard", item.text)) 60 | else: 61 | item.text = "Empty input" 62 | item.subtext = "Enter a query of the form []" 63 | return item 64 | else: 65 | match = regex.fullmatch(query.string.strip()) 66 | if match: 67 | args = match.group(1, 2) 68 | result, command, success = getUnitsResult(args) 69 | if not success: 70 | return 71 | item = Item(id='python.gnu_units', icon=icon) 72 | item.text = result 73 | item.subtext = "Result of '%s'" % command 74 | item.addAction(ClipAction("Copy to clipboard", item.text)) 75 | return item 76 | -------------------------------------------------------------------------------- /.archive/vpn/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2020 janeklb 3 | # Copyright (c) 2023 Bierchermuesli 4 | # Copyright (c) 2020-2024 Manuel Schneider 5 | 6 | import subprocess 7 | from collections import namedtuple 8 | 9 | from albert import * 10 | 11 | md_iid = "3.0" 12 | md_version = "2.0" 13 | md_name = "VPN" 14 | md_description = "Manage NetworkManager VPN connections" 15 | md_license = "MIT" 16 | md_url = "https://github.com/albertlauncher/python/tree/main/vpn" 17 | md_authors = ["@janeklb", "@Bierchermuesli", "@manuelschneid3r"] 18 | md_bin_dependencies = ["nmcli"] 19 | 20 | 21 | class Plugin(PluginInstance, TriggerQueryHandler): 22 | 23 | VPNConnection = namedtuple('VPNConnection', ['name', 'connected']) 24 | 25 | def __init__(self): 26 | PluginInstance.__init__(self) 27 | TriggerQueryHandler.__init__(self) 28 | 29 | def defaultTrigger(self): 30 | return "vpn " 31 | 32 | def getVPNConnections(self): 33 | consStr = subprocess.check_output( 34 | 'nmcli -t connection show', 35 | shell=True, 36 | encoding='UTF-8' 37 | ) 38 | for conStr in consStr.splitlines(): 39 | con = conStr.split(':') 40 | if con[2] in ['vpn', 'wireguard']: 41 | yield self.VPNConnection(name=con[0], connected=con[3] != '') 42 | 43 | @staticmethod 44 | def buildItem(con): 45 | name = con.name 46 | command = 'down' if con.connected else 'up' 47 | text = f'Connect to {name}' if command == 'up' else f'Disconnect from {name}' 48 | commandline = ['nmcli', 'connection', command, 'id', name] 49 | return StandardItem( 50 | id=f'vpn-{command}-{name}', 51 | text=name, 52 | subtext=text, 53 | iconUrls=['xdg:network-wired'], 54 | inputActionText=name, 55 | actions=[Action("run", text=text, callable=lambda: runDetachedProcess(commandline))] 56 | ) 57 | 58 | def handleTriggerQuery(self, query): 59 | if query.isValid: 60 | connections = self.getVPNConnections() 61 | if query.string: 62 | connections = [con for con in connections if query.string.lower() in con.name.lower()] 63 | query.add([self.buildItem(con) for con in connections]) 64 | -------------------------------------------------------------------------------- /.archive/window_switcher/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """List and manage X11 windows. 4 | 5 | Synopsis: """ 6 | 7 | # Copyright (c) 2022 Manuel Schneider 8 | 9 | import subprocess 10 | from collections import namedtuple 11 | from albert import Item, ProcAction, iconLookup 12 | 13 | __title__ = "Window Switcher" 14 | __version__ = "0.4.5" 15 | __authors__ = ["Ed Perez", "Manuel S.", "dshoreman"] 16 | __exec_deps__ = ["wmctrl"] 17 | 18 | Window = namedtuple("Window", ["wid", "desktop", "wm_class", "host", "wm_name"]) 19 | 20 | def handleQuery(query): 21 | stripped = query.string.strip().lower() 22 | if stripped: 23 | results = [] 24 | for line in subprocess.check_output(['wmctrl', '-l', '-x']).splitlines(): 25 | win = Window(*parseWindow(line)) 26 | 27 | if win.desktop == "-1": 28 | continue 29 | 30 | win_instance, win_class = win.wm_class.replace(' ', '-').split('.') 31 | matches = [ 32 | win_instance.lower(), 33 | win_class.lower(), 34 | win.wm_name.lower() 35 | ] 36 | 37 | if any(stripped in match for match in matches): 38 | iconPath = iconLookup(win_instance) or iconLookup(win_class.lower()) 39 | results.append(Item(id="%s%s" % (__title__, win.wm_class), 40 | icon=iconPath, 41 | text="%s - Desktop %s" % (win_class.replace('-',' '), win.desktop), 42 | subtext=win.wm_name, 43 | actions=[ProcAction("Switch Window", 44 | ["wmctrl", '-i', '-a', win.wid] ), 45 | ProcAction("Move window to this desktop", 46 | ["wmctrl", '-i', '-R', win.wid] ), 47 | ProcAction("Close the window gracefully.", 48 | ["wmctrl", '-c', win.wid])])) 49 | return results 50 | 51 | def parseWindow(line): 52 | win_id, desktop, rest = line.decode().split(None, 2) 53 | win_class, rest = rest.split(' ', 1) 54 | host, title = rest.strip().split(None, 1) 55 | 56 | return [win_id, desktop, win_class, host, title] 57 | -------------------------------------------------------------------------------- /.archive/xkcd/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ###################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyc 10 | 11 | # Packages # 12 | ###################### 13 | # it's better to unpack these files and commit the raw source 14 | # git has its own built in compression methods 15 | *.7z 16 | *.dmg 17 | *.gz 18 | *.iso 19 | *.jar 20 | *.rar 21 | *.tar 22 | *.zip 23 | 24 | # Logs and databases # 25 | ###################### 26 | *.log 27 | *.sql 28 | *.sqlite 29 | 30 | # OS generated files # 31 | ###################### 32 | .DS_Store 33 | .DS_Store? 34 | ._* 35 | .Spotlight-V100 36 | .Trashes 37 | ehthumbs.db 38 | Thumbs.db 39 | 40 | tags 41 | 42 | _build 43 | 44 | # CMake 45 | build* 46 | documentation 47 | *.user* 48 | log 49 | *.dir* 50 | *.a 51 | *.make 52 | doc/html* 53 | doc/latex* 54 | 55 | # Python 56 | .mypy_cache 57 | # Python - Coverage 58 | .coverage 59 | coverage.xml 60 | htmlcov/ 61 | 62 | # Vim 63 | Session.vim 64 | .netrwhist 65 | *~ 66 | tags 67 | .projections.json 68 | compile_commands.json 69 | 70 | 71 | # backup files - created during sed operations 72 | *.bak 73 | -------------------------------------------------------------------------------- /.archive/xkcd/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Nikos Koukis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /.archive/xkcd/README.md: -------------------------------------------------------------------------------- 1 | # Albert xkcd Plugin 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## Description 9 | 10 | The xkcd Albert plugin lets you launch an xkcd comic in your browser from the 11 | albert prompt. Here are its main features: 12 | 13 | * On toggle (default trigger: `xkcd`) it shows you the comics in newest-first 14 | order. 15 | * If you want to find a comic with a specific title then you can use fuzzy search to do so): 16 | * `xkcd some words from the title` 17 | 18 | ## Demo 19 | 20 | ![demo_gif](https://github.com/bergercookie/xkcd-albert-plugin/blob/master/misc/demo.gif) 21 | 22 | ## Motivation 23 | 24 | I love reading xkcd. I also wanted to get into developing plugins for Albert 25 | thus this was the perfect opportunity to do so. 26 | 27 | ## Manual installation instructions 28 | 29 | Requirements: 30 | 31 | - Albert - [Installation instructions](https://albertlauncher.github.io/docs/installing/) 32 | - Albert Python Interface: v0.2 33 | - Python version >= 3.5 34 | 35 | 36 | Download and run the ``install-plugin.sh`` script or run the following to do 37 | that automatically: 38 | 39 | ``````sh 40 | curl https://raw.githubusercontent.com/bergercookie/xkcd-albert-plugin/master/install-plugin.sh | bash 41 | `````` 42 | 43 | ## Self Promotion 44 | 45 | If you find this tool useful, please [star it on 46 | Github](https://github.com/bergercookie/xkcd-albert-plugin) 47 | 48 | ## TODO List 49 | 50 | See [ISSUES list](https://github.com/bergercookie/xkcd-albert-plugin/issues) for 51 | the things that I'm currently either working on or interested in implementing in 52 | the near future. In case there's something you are interesting in working on, 53 | don't hesitate to either ask for clarifications or just do it and directly make 54 | a PR. 55 | -------------------------------------------------------------------------------- /.archive/xkcd/__init__.py: -------------------------------------------------------------------------------- 1 | """Fetch xkcd comics like a boss.""" 2 | 3 | # Copyright (c) 2022 Manuel Schneider 4 | 5 | from datetime import datetime, timedelta 6 | from pathlib import Path 7 | import json 8 | import os 9 | import subprocess 10 | import sys 11 | 12 | import albertv0 as v0 13 | from fuzzywuzzy import process 14 | from shutil import which 15 | 16 | __iid__ = "PythonInterface/v0.2" 17 | __prettyname__ = "xkcd" 18 | __version__ = "0.1" 19 | __trigger__ = "xkcd" 20 | __author__ = "Nikos Koukis" 21 | __dependencies__ = [] 22 | __homepage__ = "https://github.com/bergercookie/xkcd-albert-plugin" 23 | 24 | 25 | # TODO pyproject toml file 26 | # TODO xkcd-dl executable? 27 | # TODO Upload to github - change support url on error 28 | # TODO Send to albert plugins 29 | 30 | if not which("xkcd-dl"): 31 | raise RuntimeError("xkcd-dl not in $PATH - Please install it via pip3 first.") 32 | 33 | iconPath = v0.iconLookup("xkcd") 34 | if not iconPath: 35 | iconPath = os.path.join(os.path.dirname(__file__), "image.png") 36 | SETTINGS_PATH = Path(v0.cacheLocation()) / "xkcd" 37 | LAST_UPDATE_PATH = SETTINGS_PATH / "last_update" 38 | XKCD_DICT = Path.home() / ".xkcd_dict.json" 39 | 40 | 41 | def initialize(): 42 | # Called when the extension is loaded (ticked in the settings) - blocking 43 | 44 | # create cache location 45 | SETTINGS_PATH.mkdir(parents=False, exist_ok=True) 46 | if not LAST_UPDATE_PATH.is_file(): 47 | update_date_file() 48 | update_xkcd_db() 49 | 50 | 51 | def finalize(): 52 | pass 53 | 54 | 55 | def handleQuery(query): 56 | results = [] 57 | 58 | # check whether I have downlaoded the latest metadata 59 | with open(LAST_UPDATE_PATH, "r") as f: 60 | date_str = float(f.readline().strip()) 61 | 62 | last_date = datetime.fromtimestamp(date_str) 63 | if datetime.now() - last_date > timedelta(days=1): # run an update daily 64 | update_date_file() 65 | update_xkcd_db() 66 | 67 | if query.isTriggered: 68 | try: 69 | with open(XKCD_DICT, "r", encoding="utf-8") as f: 70 | d = json.load(f) 71 | 72 | if len(query.string) in [0, 1]: # Display all items 73 | for k, v in d.items(): 74 | results.append(get_as_item(k, v)) 75 | else: # fuzzy search 76 | desc_to_item = {item[1]["description"]: item for item in d.items()} 77 | matched = process.extract( 78 | query.string.strip(), list(desc_to_item.keys()), limit=20 79 | ) 80 | for m in [elem[0] for elem in matched]: 81 | # bypass a unicode issue - use .get 82 | item = desc_to_item.get(m) 83 | if item: 84 | results.append(get_as_item(*item)) 85 | 86 | except Exception as e: # user to report error 87 | results.insert( 88 | 0, 89 | v0.Item( 90 | id=__prettyname__, 91 | icon=iconPath, 92 | text="Something went wrong! Press [ENTER] to copy error and report it", 93 | actions=[ 94 | v0.ClipAction( 95 | f"Copy error - report it to {__homepage__[8:]}", 96 | f"{sys.exc_info()}", 97 | ) 98 | ], 99 | ), 100 | ) 101 | 102 | return results 103 | 104 | 105 | def get_as_item(k: str, v: dict): 106 | return v0.Item( 107 | id=__prettyname__, 108 | icon=iconPath, 109 | text=v["description"], 110 | subtext=v["date-published"], 111 | completion="", 112 | actions=[ 113 | v0.UrlAction("Open in xkcd.com", f"https://www.xkcd.com/{k}"), 114 | v0.ClipAction("Copy URL", f"https://www.xkcd.com/{k}"), 115 | ], 116 | ) 117 | 118 | 119 | def update_date_file(): 120 | now = (datetime.now() - datetime(1970, 1, 1)).total_seconds() 121 | with open(LAST_UPDATE_PATH, "w") as f: 122 | f.write(str(now)) 123 | 124 | 125 | def update_xkcd_db(): 126 | return subprocess.call(["xkcd-dl", "-u"]) 127 | -------------------------------------------------------------------------------- /.archive/xkcd/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/xkcd/image.png -------------------------------------------------------------------------------- /.archive/xkcd/install-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | ## do this if there's any error with the installation and you want to report a bug 4 | # set -x 5 | 6 | # supplementary funs ----------------------------------------------------------- 7 | function announce 8 | { 9 | echo 10 | echo "**********************************************************************" 11 | echo -e "$@" 12 | echo "**********************************************************************" 13 | echo 14 | } 15 | 16 | function announce_err 17 | { 18 | announce "[ERROR] $*" 19 | } 20 | 21 | function install_pkg 22 | { 23 | announce "Installing \"$*\"" 24 | pip3 install --user --upgrade "$*" 25 | announce "Installed $*" 26 | } 27 | 28 | function is_installed 29 | { 30 | if [ "$(which "$*" 2>&1 1>/dev/null)" = "1" ] 31 | then 32 | return 1 33 | else 34 | return 0 35 | fi 36 | } 37 | 38 | # Check prereqs ---------------------------------------------------------------- 39 | ret=$(is_installed albert) 40 | if [ "$ret" = "1" ] 41 | then 42 | announce_err "Please install albert first. Exiting" 43 | return 1 44 | fi 45 | ret=$(is_installed git) 46 | if [ "$ret" = "1" ] 47 | then 48 | announce_err "Please install git first. Exiting" 49 | return 1 50 | fi 51 | 52 | DST="$HOME/.local/share/albert/org.albert.extension.python/modules" 53 | if [[ ! -d "$DST" ]] 54 | then 55 | announce_err "Local extensions directory doesn't exist. Please check your albert installation. Exiting" 56 | return 1 57 | fi 58 | 59 | # Install ---------------------------------------------------------------------- 60 | install_pkg git+https://github.com/tasdikrahman/xkcd-dl 61 | install_pkg fuzzywuzzy 62 | 63 | # Seesm like the xkcd-dl beautifulsoup4 version is outdated 64 | install_pkg beautifulsoup4 65 | 66 | PLUGIN_DIR="$DST/xkcd" 67 | if [ -d "$PLUGIN_DIR" ] 68 | then 69 | rm -rf "$PLUGIN_DIR" 70 | fi 71 | announce "Cloning and installing xkcd-albert-plugin -> $PLUGIN_DIR" 72 | git clone https://github.com/bergercookie/xkcd-albert-plugin "$PLUGIN_DIR" 73 | announce "Installed xkcd-albert-plugin -> $PLUGIN_DIR" 74 | 75 | announce "Plugin ready - Enable it from the Albert settings" 76 | -------------------------------------------------------------------------------- /.archive/xkcd/misc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.archive/xkcd/misc/demo.gif -------------------------------------------------------------------------------- /.archive/youtube/youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/telegram_notify_comments.yml: -------------------------------------------------------------------------------- 1 | name: Telegram Notifications 2 | 3 | on: 4 | 5 | issue_comment: 6 | types: [created] 7 | 8 | jobs: 9 | notify: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Send notifications to Telegram 15 | run: > 16 | curl -s 17 | -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage 18 | -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} 19 | -d text="${MESSAGE}" 20 | -d parse_mode=HTML 21 | -d disable_web_page_preview=true 22 | >> /dev/null 23 | env: 24 | MESSAGE: "${{ github.event.comment.user.login }} on ${{ github.event.repository.name }}#${{ github.event.issue.number }}: ${{ github.event.issue.title }}%0A${{ github.event.comment.body }}" 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/telegram_notify_issues.yml: -------------------------------------------------------------------------------- 1 | name: Telegram Notifications 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened] 6 | 7 | jobs: 8 | notify: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Send notifications to Telegram 14 | run: > 15 | curl -s 16 | -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_NOTIFIER_BOT_TOKEN }}/sendMessage 17 | -d chat_id=${{ secrets.TELEGRAM_ALBERT_CHAT_ID }} 18 | -d text="${MESSAGE}" 19 | -d parse_mode=HTML 20 | -d disable_web_page_preview=true 21 | >> /dev/null 22 | env: 23 | MESSAGE: "New issue:%0A${{ github.event.repository.name }}#${{ github.event.issue.number }}: ${{ github.event.issue.title }}" 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /.idea 3 | /.vscode 4 | /.venv 5 | albert.pyi -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/.gitmodules -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to this repository 2 | 3 | ### Do you have an issue? 4 | 5 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/albertlauncher/albert/issues). 6 | * Create a new issue using the templates provided. 7 | * Ping the authors of the related plugin. 8 | 9 | ### Do you want to contribute code? 10 | 11 | * Add a copyright notice, otherwise the code is in public domain. 12 | * You agree to publish your contribution under the MIT license. 13 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 14 | * Changes that do not add anything substantial to the stability, functionality, or testability will generally not be accepted. 15 | 16 | Thanks! :heart: 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Official Albert Python plugin repository 2 | 3 | These plugins are shipped with the app. 4 | 5 | Visit the website to learn [how to write plugins](https://albertlauncher.github.io/gettingstarted/extension/). 6 | 7 | Credits go to our contributors: 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /arch_wiki/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | import json 5 | from pathlib import Path 6 | from time import sleep 7 | from urllib import request, parse 8 | 9 | from albert import * 10 | 11 | md_iid = "3.0" 12 | md_version = '2.0' 13 | md_name = "Arch Linux Wiki" 14 | md_description = "Search Arch Linux Wiki articles" 15 | md_license = "MIT" 16 | md_url = "https://github.com/albertlauncher/python/tree/main/arch_wiki" 17 | md_authors = "@manuelschneid3r" 18 | 19 | 20 | class Plugin(PluginInstance, TriggerQueryHandler): 21 | 22 | baseurl = 'https://wiki.archlinux.org/api.php' 23 | search_url = "https://wiki.archlinux.org/index.php?search=%s" 24 | user_agent = "org.albert.extension.python.archwiki" 25 | iconUrls = [f"file:{Path(__file__).parent}/arch.svg"] 26 | 27 | def __init__(self): 28 | PluginInstance.__init__(self) 29 | TriggerQueryHandler.__init__(self) 30 | 31 | def defaultTrigger(self): 32 | return 'awiki ' 33 | 34 | def handleTriggerQuery(self, query): 35 | stripped = query.string.strip() 36 | if stripped: 37 | 38 | # avoid rate limiting 39 | for _ in range(50): 40 | sleep(0.01) 41 | if not query.isValid: 42 | return 43 | 44 | results = [] 45 | 46 | params = { 47 | 'action': 'opensearch', 48 | 'search': stripped, 49 | 'limit': "max", 50 | 'redirects': 'resolve', 51 | 'utf8': 1, 52 | 'format': 'json' 53 | } 54 | get_url = "%s?%s" % (self.baseurl, parse.urlencode(params)) 55 | req = request.Request(get_url, headers={'User-Agent': self.user_agent}) 56 | 57 | with request.urlopen(req) as response: 58 | data = json.loads(response.read().decode()) 59 | for i in range(0, len(data[1])): 60 | title = data[1][i] 61 | summary = data[2][i] 62 | url = data[3][i] 63 | 64 | results.append(StandardItem(id=self.id(), 65 | text=title, 66 | subtext=summary if summary else url, 67 | iconUrls=self.iconUrls, 68 | actions=[ 69 | Action("open", "Open article", lambda u=url: openUrl(u)), 70 | Action("copy", "Copy URL", lambda u=url: setClipboardText(u)) 71 | ])) 72 | if results: 73 | query.add(results) 74 | else: 75 | query.add(StandardItem(id=self.id(), 76 | text="Search '%s'" % query.string, 77 | subtext="No results. Start online search on Arch Wiki", 78 | iconUrls=self.iconUrls, 79 | actions=[Action("search", "Open search", 80 | lambda s=query.string: openUrl(self.search_url % s))])) 81 | 82 | else: 83 | query.add(StandardItem(id=self.id(), 84 | text=md_name, 85 | iconUrls=self.iconUrls, 86 | subtext="Enter a query to search on the Arch Wiki")) 87 | -------------------------------------------------------------------------------- /arch_wiki/arch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aur/arch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bitwarden/bw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /coingecko/coingecko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/coingecko/coingecko.png -------------------------------------------------------------------------------- /color/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | """ 5 | Displays a color parsed from a code, which may be in one of these formats: 6 | 7 | * #RGB (each of R, G, and B is a single hex digit) 8 | * #RRGGBB 9 | * #AARRGGBB 10 | * #RRRGGGBBB 11 | * #RRRRGGGGBBBB 12 | 13 | Note: This extension started as a prototype to test the internal color pixmap generator. However it may serve as a \ 14 | starting point for people having a real need for color workflows. PR's welcome. 15 | """ 16 | 17 | from albert import * 18 | from string import hexdigits 19 | 20 | md_iid = "3.0" 21 | md_version = "2.0" 22 | md_name = "Color" 23 | md_description = "Display color for color codes" 24 | md_license = "MIT" 25 | md_url = "https://github.com/albertlauncher/python/tree/main/color" 26 | md_authors = "@manuelschneid3r" 27 | 28 | 29 | class Plugin(PluginInstance, GlobalQueryHandler): 30 | 31 | def __init__(self): 32 | PluginInstance.__init__(self) 33 | GlobalQueryHandler.__init__(self) 34 | 35 | def defaultTrigger(self): 36 | return '#' 37 | 38 | def handleGlobalQuery(self, query): 39 | rank_items = [] 40 | s = query.string.strip() 41 | if s: 42 | if s.startswith('#'): # remove hash 43 | s = s[1:] 44 | 45 | # check length and hex 46 | if any([len(s) == l for l in [3, 6, 8, 9, 12]]) and all(c in hexdigits for c in s): 47 | rank_items.append( 48 | RankItem( 49 | StandardItem( 50 | id=self.id(), 51 | text=s, 52 | subtext="The color for this code.", 53 | iconUrls=[f"gen:?background=%23{s}"], 54 | ), 55 | 1 56 | ) 57 | ) 58 | 59 | return rank_items 60 | 61 | def configWidget(self): 62 | return [{ 'type': 'label', 'text': __doc__.strip() }] 63 | -------------------------------------------------------------------------------- /copyq/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017-2024 Manuel Schneider 3 | # Copyright (c) 2023 Oskar Haarklou Veileborg (@BarrensZeppelin) 4 | 5 | import json 6 | import subprocess 7 | 8 | from albert import * 9 | 10 | md_iid = "3.0" 11 | md_version = "2.0" 12 | md_name = "CopyQ" 13 | md_description = "Access CopyQ clipboard" 14 | md_license = "BSD-2-Clause" 15 | md_url = "https://github.com/albertlauncher/python/tree/main/copyq" 16 | md_authors = ["@ManuelSchneid3r", "@BarrensZeppelin"] 17 | md_bin_dependencies = ["copyq"] 18 | 19 | 20 | copyq_script_getAll = r""" 21 | var result=[]; 22 | for ( var i = 0; i < size(); ++i ) { 23 | var obj = {}; 24 | obj.row = i; 25 | obj.mimetypes = str(read("?", i)).split("\n"); 26 | obj.mimetypes.pop(); 27 | obj.text = str(read(i)); 28 | result.push(obj); 29 | } 30 | JSON.stringify(result); 31 | """ 32 | 33 | copyq_script_getMatches = r""" 34 | var result=[]; 35 | var match = "%s"; 36 | for ( var i = 0; i < size(); ++i ) { 37 | if (str(read(i)).search(new RegExp(match, "i")) !== -1) { 38 | var obj = {}; 39 | obj.row = i; 40 | obj.mimetypes = str(read("?", i)).split("\n"); 41 | obj.mimetypes.pop(); 42 | obj.text = str(read(i)); 43 | result.push(obj); 44 | } 45 | } 46 | JSON.stringify(result); 47 | """ 48 | 49 | 50 | class Plugin(PluginInstance, TriggerQueryHandler): 51 | 52 | def __init__(self): 53 | PluginInstance.__init__(self) 54 | TriggerQueryHandler.__init__(self) 55 | 56 | def defaultTrigger(self): 57 | return "cp " 58 | 59 | def handleTriggerQuery(self, query): 60 | items = [] 61 | script = copyq_script_getMatches % query.string if query.string else copyq_script_getAll 62 | proc = subprocess.run(["copyq", "-"], input=script.encode(), stdout=subprocess.PIPE) 63 | json_arr = json.loads(proc.stdout.decode()) 64 | 65 | for json_obj in json_arr: 66 | row = json_obj["row"] 67 | text = json_obj["text"] 68 | if not text: 69 | text = "No text" 70 | else: 71 | text = " ".join(filter(None, text.replace("\n", " ").split(" "))) 72 | 73 | act = lambda s=script, r=row: ( 74 | lambda: runDetachedProcess(["copyq", s % r]) 75 | ) 76 | items.append( 77 | StandardItem( 78 | id=self.id(), 79 | iconUrls=["xdg:copyq"], 80 | text=text, 81 | subtext="%s: %s" % (row, ", ".join(json_obj["mimetypes"])), 82 | actions=[ 83 | Action("paste", "Paste", act("select(%s); sleep(60); paste();")), 84 | Action("copy", "Copy", act("select(%s);")), 85 | Action("remove", "Remove", act("remove(%s);")), 86 | ], 87 | ) 88 | ) 89 | 90 | query.add(items) 91 | -------------------------------------------------------------------------------- /dice_roll/icons/d10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d100.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d12.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/d8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dice_roll/icons/dice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docker/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | from pathlib import Path 5 | 6 | import docker 7 | from albert import * 8 | 9 | md_iid = "3.0" 10 | md_version = "4.0" 11 | md_name = "Docker" 12 | md_description = "Manage docker images and containers" 13 | md_license = "MIT" 14 | md_url = "https://github.com/albertlauncher/python/tree/main/docker" 15 | md_authors = "@manuelschneid3r" 16 | md_bin_dependencies = "docker" 17 | md_lib_dependencies = "docker" 18 | 19 | 20 | class Plugin(PluginInstance, TriggerQueryHandler): 21 | # Global query handler not applicable, queries take seconds sometimes 22 | 23 | def __init__(self): 24 | PluginInstance.__init__(self) 25 | TriggerQueryHandler.__init__(self) 26 | self.icon_urls_running = [f"file:{Path(__file__).parent}/running.png"] 27 | self.icon_urls_stopped = [f"file:{Path(__file__).parent}/stopped.png"] 28 | self.client = None 29 | 30 | def synopsis(self, query): 31 | return "" 32 | 33 | def defaultTrigger(self): 34 | return "d " 35 | 36 | def handleTriggerQuery(self, query): 37 | items = [] 38 | 39 | if not self.client: 40 | try: 41 | self.client = docker.from_env() 42 | except Exception as e: 43 | items.append(StandardItem( 44 | id='except', 45 | text="Failed starting docker client", 46 | subtext=str(e), 47 | iconUrls=self.icon_urls_running, 48 | )) 49 | return items 50 | 51 | try: 52 | for container in self.client.containers.list(all=True): 53 | if query.string in container.name: 54 | # Create dynamic actions 55 | if container.status == 'running': 56 | actions = [Action("stop", "Stop container", lambda c=container: c.stop()), 57 | Action("restart", "Restart container", lambda c=container: c.restart())] 58 | else: 59 | actions = [Action("start", "Start container", lambda c=container: c.start())] 60 | actions.extend([ 61 | Action("logs", "Logs", 62 | lambda c=container.id: runTerminal("docker logs -f %s ; exec $SHELL" % c)), 63 | Action("remove", "Remove (forced, with volumes)", 64 | lambda c=container: c.remove(v=True, force=True)), 65 | Action("copy-id", "Copy id to clipboard", 66 | lambda cid=container.id: setClipboardText(cid)) 67 | ]) 68 | 69 | items.append(StandardItem( 70 | id=container.id, 71 | text="%s (%s)" % (container.name, ", ".join(container.image.tags)), 72 | subtext="Container: %s" % container.id, 73 | iconUrls=self.icon_urls_running if container.status == 'running' else self.icon_urls_stopped, 74 | actions=actions 75 | )) 76 | 77 | for image in reversed(self.client.images.list()): 78 | for tag in sorted(image.tags, key=len): # order by resulting score 79 | if query.string in tag: 80 | items.append(StandardItem( 81 | id=image.short_id, 82 | text=", ".join(image.tags), 83 | subtext="Image: %s" % image.id, 84 | iconUrls=self.icon_urls_stopped, 85 | actions=[ 86 | # Action("run", "Run with command: %s" % query.string, 87 | # lambda i=image, s=query.string: client.containers.run(i, s)), 88 | Action("rmi", "Remove image", lambda i=image: i.remove()) 89 | ] 90 | )) 91 | except Exception as e: 92 | warning(str(e)) 93 | self.client = None 94 | 95 | query.add(items) 96 | -------------------------------------------------------------------------------- /docker/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/docker/running.png -------------------------------------------------------------------------------- /docker/stopped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/docker/stopped.png -------------------------------------------------------------------------------- /duckduckgo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | """ 5 | Inline DuckDuckGo web search using the 'duckduckgo-search' library. 6 | """ 7 | 8 | from albert import * 9 | from pathlib import Path 10 | from duckduckgo_search import DDGS 11 | from itertools import islice 12 | from time import sleep 13 | 14 | md_iid = "3.0" 15 | md_version = "2.0" 16 | md_name = 'DuckDuckGo' 17 | md_description = 'Inline DuckDuckGo web search' 18 | md_license = "MIT" 19 | md_url = 'https://github.com/albertlauncher/python/tree/main/duckduckgo' 20 | md_lib_dependencies = "duckduckgo-search" 21 | md_authors = "@manuelschneid3r" 22 | 23 | 24 | class Plugin(PluginInstance, TriggerQueryHandler): 25 | 26 | def __init__(self): 27 | PluginInstance.__init__(self) 28 | TriggerQueryHandler.__init__(self) 29 | self.ddg = DDGS() 30 | self.iconUrls = [f"file:{Path(__file__).parent}/duckduckgo.svg"] 31 | 32 | def defaultTrigger(self): 33 | return "ddg " 34 | 35 | def handleTriggerQuery(self, query): 36 | 37 | stripped = query.string.strip() 38 | if stripped: 39 | 40 | # dont flood 41 | for _ in range(25): 42 | sleep(0.01) 43 | if not query.isValid: 44 | return 45 | 46 | for r in islice(self.ddg.text(stripped, safesearch='off'), 10): 47 | query.add( 48 | StandardItem( 49 | id=self.id(), 50 | text=r['title'], 51 | subtext=r['body'], 52 | iconUrls=self.iconUrls, 53 | actions=[Action("open", "Open link", lambda u=r['href']: openUrl(u))] 54 | ) 55 | ) 56 | -------------------------------------------------------------------------------- /goldendict/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017-2024 Manuel Schneider 3 | 4 | import os 5 | import shutil 6 | 7 | from albert import * 8 | 9 | md_iid = "3.0" 10 | md_version = "2.0" 11 | md_name = "GoldenDict" 12 | md_description = "Quick access to GoldenDict" 13 | md_license = "MIT" 14 | md_url = "https://github.com/albertlauncher/python/tree/main/goldendict" 15 | md_authors = "@manuelschneid3r" 16 | 17 | 18 | class Plugin(PluginInstance, TriggerQueryHandler): 19 | 20 | def __init__(self): 21 | PluginInstance.__init__(self) 22 | TriggerQueryHandler.__init__(self) 23 | 24 | commands = [ 25 | '/var/lib/flatpak/exports/bin/org.goldendict.GoldenDict', # flatpak 26 | '/var/lib/flatpak/exports/bin/io.github.xiaoyifang.goldendict_ng', # flatpak ng 27 | 'goldendict', # native 28 | 'goldendict-ng', # native ng 29 | ] 30 | 31 | executables = [e for e in [shutil.which(c) for c in commands] if e] 32 | 33 | if not executables: 34 | raise RuntimeError(f'None of the GoldenDict distributions found.') 35 | 36 | self.executable = executables[0] 37 | self.iconUrls = [f'xdg:{os.path.basename(self.executable)}'] 38 | 39 | if len(executables) > 1: 40 | warning(f"Multiple GoldenDict commands found: {', '.join(executables)}") 41 | warning(f"Using {self.executable}") 42 | 43 | def defaultTrigger(self): 44 | return "gd " 45 | 46 | def handleTriggerQuery(self, query): 47 | q = query.string.strip() 48 | query.add( 49 | StandardItem( 50 | id=md_name, 51 | text=md_name, 52 | subtext=f"Look up '{q}' in GoldenDict", 53 | iconUrls=self.iconUrls, 54 | actions=[Action(md_name, md_name, lambda e=self.executable: runDetachedProcess([e, q]))], 55 | ) 56 | ) 57 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/androidstudio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/clion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/datagrip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/dataspell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/goland.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/idea.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/phpstorm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/pycharm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/rider.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/rubymine.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/webstorm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jetbrains_projects/icons/writerside.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /kill/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2022 Manuel Schneider 3 | # Copyright (c) 2022 Benedict Dudel 4 | # Copyright (c) 2022 Pete Hamlin 5 | 6 | 7 | import os 8 | from signal import SIGKILL, SIGTERM 9 | 10 | from albert import * 11 | 12 | md_iid = "3.0" 13 | md_version = "2.0" 14 | md_name = "Kill Process" 15 | md_description = "Kill processes" 16 | md_license = "MIT" 17 | md_url = "https://github.com/albertlauncher/python/tree/main/kill" 18 | md_authors = ["@Pete-Hamlin", "@BenedictDwudel", "@ManuelSchneid3r"] 19 | 20 | 21 | class Plugin(PluginInstance, TriggerQueryHandler): 22 | def __init__(self): 23 | PluginInstance.__init__(self) 24 | TriggerQueryHandler.__init__(self) 25 | 26 | def defaultTrigger(self): 27 | return "kill " 28 | 29 | def handleTriggerQuery(self, query): 30 | if not query.isValid: 31 | return 32 | results = [] 33 | uid = os.getuid() 34 | for dir_entry in os.scandir("/proc"): 35 | try: 36 | if dir_entry.name.isdigit() and dir_entry.stat().st_uid == uid: 37 | proc_command = ( 38 | open(os.path.join(dir_entry.path, "comm"), "r").read().strip() 39 | ) 40 | if query.string in proc_command: 41 | debug(proc_command) 42 | proc_cmdline = ( 43 | open(os.path.join(dir_entry.path, "cmdline"), "r") 44 | .read() 45 | .strip() 46 | .replace("\0", " ") 47 | ) 48 | results.append( 49 | StandardItem( 50 | id="kill", 51 | iconUrls=["xdg:process-stop"], 52 | text=proc_command, 53 | subtext=proc_cmdline, 54 | actions=[ 55 | Action( 56 | "terminate", 57 | "Terminate process", 58 | lambda pid=int(dir_entry.name): os.kill( 59 | pid, SIGTERM 60 | ), 61 | ), 62 | Action( 63 | "kill", 64 | "Kill process", 65 | lambda pid=int(dir_entry.name): os.kill( 66 | pid, SIGKILL 67 | ), 68 | ), 69 | ], 70 | ) 71 | ) 72 | except FileNotFoundError: # TOCTOU dirs may disappear 73 | continue 74 | except IOError: # TOCTOU dirs may disappear 75 | continue 76 | query.add(results) 77 | -------------------------------------------------------------------------------- /locate/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2022-2024 Manuel Schneider 3 | 4 | """ 5 | `locate` wrapper. Note that it is up to you to ensure that the locate database is \ 6 | up to date. Pass params as necessary. The input is split using a shell lexer. 7 | """ 8 | 9 | 10 | import shlex 11 | import subprocess 12 | from pathlib import Path 13 | 14 | from albert import * 15 | 16 | md_iid = "3.0" 17 | md_version = "2.0" 18 | md_name = "Locate" 19 | md_description = "Find and open files using locate" 20 | md_license = "MIT" 21 | md_url = "https://github.com/albertlauncher/python/tree/main/locate" 22 | md_bin_dependencies = "locate" 23 | md_authors = "@manuelschneid3r" 24 | 25 | 26 | class Plugin(PluginInstance, TriggerQueryHandler): 27 | 28 | def __init__(self): 29 | PluginInstance.__init__(self) 30 | TriggerQueryHandler.__init__(self) 31 | 32 | self.iconUrls = [ 33 | "xdg:preferences-system-search", 34 | "xdg:system-search", 35 | "xdg:search", 36 | "xdg:text-x-generic", 37 | f"file:{Path(__file__).parent}/locate.svg" 38 | ] 39 | 40 | def synopsis(self, query): 41 | return "" 42 | 43 | def defaultTrigger(self): 44 | return "'" 45 | 46 | def handleTriggerQuery(self, query): 47 | if len(query.string) > 2: 48 | 49 | try: 50 | args = shlex.split(query.string) 51 | except ValueError: 52 | return 53 | 54 | result = subprocess.run(['locate', *args], stdout=subprocess.PIPE, text=True) 55 | if not query.isValid: 56 | return 57 | lines = sorted(result.stdout.splitlines(), reverse=True) 58 | if not query.isValid: 59 | return 60 | 61 | for path in lines: 62 | query.add( 63 | StandardItem( 64 | id=path, 65 | text=Path(path).name, 66 | subtext=path, 67 | iconUrls=self.iconUrls, 68 | actions=[ 69 | Action("open", "Open", lambda p=path: openUrl("file://%s" % p)) 70 | ] 71 | ) 72 | ) 73 | else: 74 | query.add( 75 | StandardItem( 76 | id="updatedb", 77 | text="Update locate database", 78 | subtext="Type at least three chars for a search", 79 | iconUrls=self.iconUrls, 80 | actions=[ 81 | Action("update", "Update", lambda: runTerminal("sudo updatedb")) 82 | ] 83 | ) 84 | ) 85 | -------------------------------------------------------------------------------- /pacman/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | import subprocess 5 | from time import sleep 6 | import pathlib 7 | 8 | from albert import Action, StandardItem, PluginInstance, TriggerQueryHandler, runTerminal, openUrl 9 | 10 | md_iid = "3.0" 11 | md_version = "2.0" 12 | md_name = "PacMan" 13 | md_description = "Search, install and remove packages" 14 | md_license = "MIT" 15 | md_url = "https://github.com/albertlauncher/python/tree/main/pacman" 16 | md_authors = "@ManuelSchneid3r" 17 | md_bin_dependencies = ["pacman", "expac"] 18 | 19 | 20 | class Plugin(PluginInstance, TriggerQueryHandler): 21 | 22 | pkgs_url = "https://www.archlinux.org/packages/" 23 | 24 | def __init__(self): 25 | PluginInstance.__init__(self) 26 | TriggerQueryHandler.__init__(self) 27 | self.iconUrls = [ 28 | "xdg:archlinux-logo", 29 | "xdg:system-software-install", 30 | f"file:{pathlib.Path(__file__).parent}/arch.svg" 31 | ] 32 | 33 | def synopsis(self, query): 34 | return "" 35 | 36 | def defaultTrigger(self): 37 | return "pac " 38 | 39 | def handleTriggerQuery(self, query): 40 | stripped = query.string.strip() 41 | 42 | # Update item on empty queries 43 | if not stripped: 44 | query.add(StandardItem( 45 | id="%s-update" % self.id, 46 | text="Pacman package manager", 47 | subtext="Enter the package you are looking for or hit enter to update.", 48 | iconUrls=self.iconUrls, 49 | actions=[ 50 | Action("up-nc", "Update packages (no confirm)", 51 | lambda: runTerminal("sudo pacman -Syu --noconfirm")), 52 | Action("up", "Update packages", lambda: runTerminal("sudo pacman -Syu")), 53 | Action("up-cache", "Update pacman cache", lambda: runTerminal("sudo pacman -Sy")) 54 | ] 55 | )) 56 | return 57 | 58 | # avoid rate limiting 59 | for _ in range(50): 60 | sleep(0.01) 61 | if not query.isValid: 62 | return 63 | 64 | # Get data. Results are sorted, so we can merge in O(n) 65 | proc_s = subprocess.Popen(["expac", "-Ss", "%n\t%v\t%r\t%d\t%u\t%E", stripped], 66 | stdout=subprocess.PIPE, universal_newlines=True) 67 | proc_q = subprocess.Popen(["expac", "-Qs", "%n", stripped], stdout=subprocess.PIPE, universal_newlines=True) 68 | proc_q.wait() 69 | 70 | items = [] 71 | local_pkgs = set(proc_q.stdout.read().split('\n')) 72 | remote_pkgs = [tuple(line.split('\t')) for line in proc_s.stdout.read().split('\n')[:-1]] # newline at end 73 | 74 | for pkg_name, pkg_vers, pkg_repo, pkg_desc, pkg_purl, pkg_deps in remote_pkgs: 75 | if stripped not in pkg_name: 76 | continue 77 | 78 | pkg_installed = True if pkg_name in local_pkgs else False 79 | 80 | actions = [] 81 | if pkg_installed: 82 | actions.extend([ 83 | Action("rem", "Remove", lambda n=pkg_name: runTerminal("sudo pacman -Rs %s" % n)), 84 | Action("reinst", "Reinstall", lambda n=pkg_name: runTerminal("sudo pacman -S %s" % n)) 85 | ]) 86 | else: 87 | actions.append(Action("inst", "Install", lambda n=pkg_name: runTerminal("sudo pacman -S %s" % n))) 88 | 89 | actions.append(Action("pkg_url", "Show on packages.archlinux.org", 90 | lambda r=pkg_repo, n=pkg_name: openUrl(f"{self.pkgs_url}{r}/x86_64/{n}/"))) 91 | if pkg_purl: 92 | actions.append(Action("proj_url", "Show project website", lambda u=pkg_purl: openUrl(u))) 93 | 94 | item = StandardItem( 95 | id="%s_%s_%s" % (self.id, pkg_repo, pkg_name), 96 | iconUrls=self.iconUrls, 97 | text="%s %s [%s]" % (pkg_name, pkg_vers, pkg_repo), 98 | subtext=f"{pkg_desc} [Installed]" if pkg_installed else f"{pkg_desc}", 99 | inputActionText="%s%s" % (query.trigger, pkg_name), 100 | actions=actions 101 | ) 102 | items.append(item) 103 | 104 | if items: 105 | query.add(items) 106 | else: 107 | query.add(StandardItem( 108 | id="%s-empty" % self.id, 109 | text="Search on archlinux.org", 110 | subtext="No results found in the local database", 111 | iconUrls=self.iconUrls, 112 | actions=[Action("search", "Search on archlinux.org", lambda: openUrl(f"{self.pkgs_url}?q={stripped}"))] 113 | )) 114 | -------------------------------------------------------------------------------- /pacman/arch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pomodoro/pomodoro.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python_eval/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017-2014 Manuel Schneider 3 | 4 | from pathlib import Path 5 | 6 | from albert import * 7 | 8 | md_iid = "3.0" 9 | md_version = "2.0" 10 | md_name = "Python Eval" 11 | md_description = "Evaluate Python code" 12 | md_license = "BSD-3" 13 | md_url = "https://github.com/albertlauncher/python/tree/main/python_eval" 14 | md_authors = "@manuelschneid3r" 15 | 16 | 17 | class Plugin(PluginInstance, TriggerQueryHandler): 18 | 19 | def __init__(self): 20 | PluginInstance.__init__(self) 21 | TriggerQueryHandler.__init__(self) 22 | self.iconUrls = [f"file:{Path(__file__).parent}/python.svg"] 23 | 24 | def synopsis(self, query): 25 | return "" 26 | 27 | def defaultTrigger(self): 28 | return "py " 29 | 30 | def handleTriggerQuery(self, query): 31 | stripped = query.string.strip() 32 | if stripped: 33 | try: 34 | result = eval(stripped) 35 | except Exception as ex: 36 | result = ex 37 | 38 | result_str = str(result) 39 | 40 | query.add(StandardItem( 41 | id=self.id(), 42 | text=result_str, 43 | subtext=type(result).__name__, 44 | inputActionText=query.trigger + result_str, 45 | iconUrls=self.iconUrls, 46 | actions = [ 47 | Action("copy", "Copy result to clipboard", lambda r=result_str: setClipboardText(r)), 48 | Action("exec", "Execute python code", lambda r=result_str: exec(stripped)), 49 | ] 50 | )) 51 | -------------------------------------------------------------------------------- /python_eval/python.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syncthing/syncthing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tex_to_unicode/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2022 Jonah Lawrence 3 | # Copyright (c) 2024 Manuel Schneider 4 | 5 | import re 6 | import unicodedata 7 | from pathlib import Path 8 | from pylatexenc.latex2text import LatexNodes2Text 9 | 10 | from albert import * 11 | 12 | md_iid = "3.0" 13 | md_version = "2.0" 14 | md_name = "TeX to Unicode" 15 | md_description = "Convert TeX mathmode commands to unicode characters" 16 | md_license = "MIT" 17 | md_url = "https://github.com/albertlauncher/python/tree/main/tex_to_unicode" 18 | md_authors = ["@DenverCoder1", "@manuelschneid3r"] 19 | md_lib_dependencies = "pylatexenc" 20 | 21 | 22 | class Plugin(PluginInstance, TriggerQueryHandler): 23 | 24 | def __init__(self): 25 | PluginInstance.__init__(self) 26 | TriggerQueryHandler.__init__(self) 27 | self.COMBINING_LONG_SOLIDUS_OVERLAY = "\u0338" 28 | self.iconUrls = [f"file:{Path(__file__).parent}/tex.svg"] 29 | 30 | def _create_item(self, text: str, subtext: str, can_copy: bool): 31 | actions = [] 32 | if can_copy: 33 | actions.append( 34 | Action( 35 | "copy", 36 | "Copy result to clipboard", 37 | lambda t=text: setClipboardText(t), 38 | ) 39 | ) 40 | return StandardItem( 41 | id=self.id(), 42 | text=text, 43 | subtext=subtext, 44 | iconUrls=self.iconUrls, 45 | actions=actions, 46 | ) 47 | 48 | def defaultTrigger(self): 49 | return "tex " 50 | 51 | def handleTriggerQuery(self, query): 52 | stripped = query.string.strip() 53 | 54 | if not stripped: 55 | return 56 | 57 | if not stripped.startswith("\\"): 58 | stripped = "\\" + stripped 59 | 60 | # Remove double backslashes (newlines) 61 | stripped = stripped.replace("\\\\", " ") 62 | 63 | # pylatexenc doesn't support \not 64 | stripped = stripped.replace("\\not", "@NOT@") 65 | 66 | n = LatexNodes2Text() 67 | result = n.latex_to_text(stripped) 68 | 69 | if not result: 70 | query.add(self._create_item(stripped, "Type some TeX math", False)) 71 | return 72 | 73 | # success 74 | result = unicodedata.normalize("NFC", result) 75 | result = re.sub(r"@NOT@\s*(\S)", "\\1" + self.COMBINING_LONG_SOLIDUS_OVERLAY, result) 76 | result = result.replace("@NOT@", "") 77 | result = unicodedata.normalize("NFC", result) 78 | query.add(self._create_item(result, "Result", True)) 79 | -------------------------------------------------------------------------------- /tex_to_unicode/tex.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /translators/google_translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/translators/google_translate.png -------------------------------------------------------------------------------- /unit_converter/icons/currency.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /unit_converter/icons/current.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/length.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/lengthtime.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/luminosity.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/mass.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /unit_converter/icons/printing_unit.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/substance.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/temperature.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /unit_converter/icons/time.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /unit_converter/icons/unit_converter.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /virtualbox/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | """ 4 | This plugin is based on [virtualbox-python](https://pypi.org/project/virtualbox/) and needs the 'vboxapi' module which 5 | is part of the VirtualBox SDK. Some distributions package the SDK, e.g. Arch has 6 | [virtualbox-sdk](https://archlinux.org/packages/extra/x86_64/virtualbox-sdk/). 7 | """ 8 | 9 | import virtualbox 10 | from virtualbox.library import LockType, MachineState 11 | 12 | from albert import * 13 | 14 | md_iid = "3.0" 15 | md_version = "2.0" 16 | md_name = "VirtualBox" 17 | md_description = "Manage your VirtualBox machines" 18 | md_license = "MIT" 19 | md_url = "https://github.com/albertlauncher/python/tree/main/virtualbox" 20 | md_authors = "@manuelschneid3r" 21 | md_lib_dependencies = ['virtualbox'] 22 | 23 | 24 | def startVm(vm): 25 | try: 26 | with virtualbox.Session() as session: 27 | progress = vm.launch_vm_process(session, 'gui', []) 28 | progress.wait_for_completion() 29 | except Exception as e: 30 | warning(str(e)) 31 | 32 | 33 | def acpiPowerVm(vm): 34 | with vm.create_session(LockType.shared) as session: 35 | session.console.power_button() 36 | 37 | 38 | def stopVm(vm): 39 | with vm.create_session(LockType.shared) as session: 40 | session.console.power_down() 41 | 42 | 43 | def saveVm(vm): 44 | with vm.create_session(LockType.shared) as session: 45 | session.machine.save_state() 46 | 47 | 48 | def discardSavedVm(vm): 49 | with vm.create_session(LockType.shared) as session: 50 | session.machine.discard_save_state(True) 51 | 52 | 53 | def resumeVm(vm): 54 | with vm.create_session(LockType.shared) as session: 55 | session.console.resume() 56 | 57 | 58 | def pauseVm(vm): 59 | with vm.create_session(LockType.shared) as session: 60 | session.console.pause() 61 | 62 | 63 | class Plugin(PluginInstance, TriggerQueryHandler): 64 | 65 | def __init__(self): 66 | PluginInstance.__init__(self) 67 | TriggerQueryHandler.__init__(self) 68 | self.iconUrls = ["xdg:virtualbox", ":unknown"] 69 | 70 | def defaultTrigger(self): 71 | return 'vbox ' 72 | 73 | def synopsis(self, query): 74 | return "" 75 | 76 | def configWidget(self): 77 | return [ 78 | { 79 | 'type': 'label', 80 | 'text': __doc__.strip(), 81 | 'widget_properties': { 82 | 'textFormat': 'Qt::MarkdownText' 83 | } 84 | } 85 | ] 86 | 87 | def handleTriggerQuery(self, query): 88 | items = [] 89 | pattern = query.string.strip().lower() 90 | try: 91 | for vm in filter(lambda vm: pattern in vm.name.lower(), virtualbox.VirtualBox().machines): 92 | actions = [] 93 | if vm.state == MachineState.powered_off or vm.state == MachineState.aborted: # 1 # 4 94 | actions.append(Action("startvm", "Start virtual machine", lambda m=vm: startVm(m))) 95 | if vm.state == MachineState.saved: # 2 96 | actions.append(Action("restorevm", "Start saved virtual machine", lambda m=vm: startVm(m))) 97 | actions.append(Action("discardvm", "Discard saved state", lambda m=vm: discardSavedVm(m))) 98 | if vm.state == MachineState.running: # 5 99 | actions.append(Action("savevm", "Save virtual machine", lambda m=vm: saveVm(m))) 100 | actions.append(Action("poweroffvm", "Power off via ACPI event (Power button)", lambda m=vm: acpiPowerVm(m))) 101 | actions.append(Action("stopvm", "Turn off virtual machine", lambda m=vm: stopVm(m))) 102 | actions.append(Action("pausevm", "Pause virtual machine", lambda m=vm: pauseVm(m))) 103 | if vm.state == MachineState.paused: # 6 104 | actions.append(Action("resumevm", "Resume virtual machine", lambda m=vm: resumeVm(m))) 105 | 106 | items.append( 107 | StandardItem( 108 | id=vm.__uuid__, 109 | text=vm.name, 110 | subtext="{vm.state}".format(vm=vm), 111 | inputActionText=vm.name, 112 | iconUrls=self.iconUrls, 113 | actions=actions 114 | ) 115 | ) 116 | except Exception as e: 117 | warning(str(e)) 118 | 119 | query.add(items) 120 | -------------------------------------------------------------------------------- /wikipedia/wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertlauncher/python/53c4bdda1c4edcef226159deef4279739414fda0/wikipedia/wikipedia.png -------------------------------------------------------------------------------- /x_window_switcher/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import subprocess 4 | from collections import namedtuple 5 | from albert import * 6 | 7 | md_iid = "3.0" 8 | md_version = "0.6.0" 9 | md_name = "X Window Switcher" 10 | md_description = "Switch X11 Windows" 11 | md_license = "MIT" 12 | md_url = "https://github.com/albertlauncher/python/tree/main/x_window_switcher" 13 | md_bin_dependencies = "wmctrl" 14 | md_authors = ["Ed Perez", "Manuel S.", "dshoreman", "nopsqi"] 15 | 16 | Window = namedtuple("Window", ["wid", "desktop", "wm_class", "host", "wm_name"]) 17 | 18 | 19 | class Plugin(PluginInstance, TriggerQueryHandler): 20 | def __init__(self): 21 | PluginInstance.__init__(self) 22 | TriggerQueryHandler.__init__(self) 23 | 24 | # Check for X session and wmctrl availability 25 | try: 26 | subprocess.check_call(["wmctrl", "-m"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 27 | except FileNotFoundError: 28 | raise Exception("wmctrl not found. Please install wmctrl.") 29 | except subprocess.CalledProcessError: 30 | raise Exception("Unable to communicate with X11 window manager. This plugin requires a running X session.") 31 | 32 | def defaultTrigger(self): 33 | return 'w ' 34 | 35 | def handleTriggerQuery(self, query): 36 | try: 37 | for line in subprocess.check_output(['wmctrl', '-l', '-x']).splitlines(): 38 | win = Window(*parseWindow(line)) 39 | 40 | if win.desktop == "-1": 41 | continue 42 | 43 | win_instance, win_class = win.wm_class.replace(' ', '-').split('.', 1) 44 | 45 | m = Matcher(query.string) 46 | if not query.string or m.match(win_instance + ' ' + win_class + ' ' + win.wm_name): 47 | query.add(StandardItem( 48 | id="%s%s" % (md_name, win.wm_class), 49 | iconUrls=["xdg:%s" % win_instance], 50 | text="%s - Desktop %s" % (win_class.replace('-', ' '), win.desktop), 51 | subtext=win.wm_name, 52 | actions=[Action("switch", 53 | "Switch Window", 54 | lambda w=win: runDetachedProcess(["wmctrl", '-i', '-a', w.wid])), 55 | Action("move", 56 | "Move window to this desktop", 57 | lambda w=win: runDetachedProcess(["wmctrl", '-i', '-R', w.wid])), 58 | Action("close", 59 | "Close the window gracefully.", 60 | lambda w=win: runDetachedProcess(["wmctrl", '-c', w.wid]))] 61 | )) 62 | except subprocess.CalledProcessError as e: 63 | warning(f"Error executing wmctrl: {str(e)}") 64 | 65 | 66 | def parseWindow(line): 67 | win_id, desktop, rest = line.decode().split(None, 2) 68 | win_class, rest = rest.split(' ', 1) 69 | host, title = rest.strip().split(None, 1) 70 | 71 | return [win_id, desktop, win_class, host, title] 72 | -------------------------------------------------------------------------------- /zeal/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2024 Manuel Schneider 3 | 4 | import albert 5 | 6 | md_iid = "3.0" 7 | md_version = "3.0" 8 | md_name = "Zeal" 9 | md_description = "Search in Zeal docs" 10 | md_license = "MIT" 11 | md_url = "https://github.com/albertlauncher/python/tree/main/zeal" 12 | md_authors = "@manuelschneid3r" 13 | md_bin_dependencies = ['zeal'] 14 | 15 | def createItem(query: str): 16 | return albert.StandardItem( 17 | id=md_name, 18 | text=md_name, 19 | subtext=f"Search '{query}' in Zeal", 20 | iconUrls=["xdg:zeal"], 21 | actions=[albert.Action("zeal", "Search in Zeal", 22 | lambda q=query: albert.runDetachedProcess(['zeal', q]))] 23 | ) 24 | 25 | class FBH(albert.FallbackHandler): 26 | 27 | def id(self): 28 | return "zeal_fbh" 29 | 30 | def name(self): 31 | return md_name 32 | 33 | def description(self): 34 | return md_description 35 | 36 | def fallbacks(self, s): 37 | return [createItem(s)] if s else [] 38 | 39 | 40 | class Plugin(albert.PluginInstance, albert.TriggerQueryHandler): 41 | 42 | def __init__(self): 43 | albert.PluginInstance.__init__(self) 44 | albert.TriggerQueryHandler.__init__(self) 45 | self.fbh = FBH() 46 | 47 | def defaultTrigger(self): 48 | return "z " 49 | 50 | def extensions(self): 51 | return [self, self.fbh] 52 | 53 | def handleTriggerQuery(self, query): 54 | if stripped := query.string.strip(): 55 | query.add(createItem(stripped)) 56 | --------------------------------------------------------------------------------