126 lines
3.8 KiB
Python
Executable file
126 lines
3.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
# Setup logging
|
|
logging.basicConfig(
|
|
level=logging.INFO, # default level
|
|
format="%(levelname)s: %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Optional: pyperclip fallback
|
|
try:
|
|
import pyperclip
|
|
HAVE_PYPERCLIP = True
|
|
except ImportError:
|
|
HAVE_PYPERCLIP = False
|
|
|
|
EMOJI_URL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
|
|
|
class EmojiCache:
|
|
"""Handles downloading and caching the emoji list in XDG_CACHE_HOME."""
|
|
def __init__(self):
|
|
xdg_cache = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
self.cache_dir = xdg_cache / "emoji-picker"
|
|
self.cache_file = self.cache_dir / "emoji-list.txt"
|
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
logger.debug(f"Cache directory: {self.cache_dir}")
|
|
|
|
def fetch(self):
|
|
"""Download or return cached emoji list."""
|
|
if not self.cache_file.exists():
|
|
self.download()
|
|
else:
|
|
logger.debug(f"Using cached emoji list at {self.cache_file}")
|
|
return self.cache_file
|
|
|
|
def download(self):
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
logger.error("requests module is required to fetch emoji list")
|
|
sys.exit(1)
|
|
|
|
logger.info("Downloading emoji list...")
|
|
data = requests.get(EMOJI_URL).json()
|
|
lines = []
|
|
for e in data:
|
|
emoji = e.get("emoji", "")
|
|
description = e.get("description", "")
|
|
category = e.get("category", "")
|
|
aliases = e.get("aliases", [])
|
|
tags = e.get("tags", [])
|
|
|
|
line = emoji + " - " + description + " - " + category
|
|
line = self.add_unique(aliases, line)
|
|
line = self.add_unique(tags, line)
|
|
lines.append(line)
|
|
|
|
self.cache_file.write_text("\n".join(lines), encoding="utf-8")
|
|
logger.info(f"Emoji list cached at {self.cache_file}")
|
|
|
|
@staticmethod
|
|
def add_unique(words, line):
|
|
for word in words:
|
|
if word.lower() not in line.lower():
|
|
line += " " + word
|
|
return line
|
|
|
|
class EmojiPicker:
|
|
"""Shows menu with tofi and copies selected emoji to clipboard."""
|
|
def __init__(self, emoji_file):
|
|
self.emoji_file = Path(emoji_file)
|
|
|
|
def pick(self):
|
|
try:
|
|
with self.emoji_file.open("r", encoding="utf-8") as f:
|
|
choices = f.read().splitlines()
|
|
|
|
result = subprocess.run(
|
|
[
|
|
"tofi",
|
|
"--fuzzy-match", "true",
|
|
"--require-match", "true",
|
|
"--history", "true",
|
|
"--history-file", str(Path.home() / ".cache/emoji-picker/history.txt")
|
|
],
|
|
input="\n".join(choices),
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
chosen_line = result.stdout.strip()
|
|
if not chosen_line:
|
|
logger.debug("No selection made")
|
|
return
|
|
|
|
emoji = chosen_line.split()[0] # just the emoji
|
|
self.copy_to_clipboard(emoji)
|
|
logger.info(f"Copied {emoji} to clipboard")
|
|
|
|
except FileNotFoundError:
|
|
logger.error("tofi not found!")
|
|
|
|
@staticmethod
|
|
def copy_to_clipboard(text):
|
|
if HAVE_PYPERCLIP:
|
|
pyperclip.copy(text)
|
|
else:
|
|
try:
|
|
subprocess.run(["wl-copy"], input=text.encode(), check=True)
|
|
except FileNotFoundError:
|
|
logger.error("wl-copy not found and pyperclip not available")
|
|
|
|
def main():
|
|
cache = EmojiCache()
|
|
emoji_file = cache.fetch()
|
|
picker = EmojiPicker(emoji_file)
|
|
picker.pick()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|