Skip to content

Commit

Permalink
Merge pull request #15 from chkpwd/feat/improve-icon-downloading
Browse files Browse the repository at this point in the history
feat: add icon support
  • Loading branch information
chkpwd authored Nov 26, 2024
2 parents 90da1b6 + 62874fe commit 673c348
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 119 deletions.
22 changes: 22 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: main - search arg",
"type": "debugpy",
"env": {
"alfred_workflow_data": "~/.local/share/ente-totp/icons"
},
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": [
"search",
"cloudflare"
]
}
]
}
48 changes: 24 additions & 24 deletions info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@
<key>queuemode</key>
<integer>1</integer>
<key>runningsubtext</key>
<string>importing....</string>
<string>Importing TOTP accounts and downloading icons....</string>
<key>script</key>
<string>python3 main.py import</string>
<key>scriptargtype</key>
Expand Down Expand Up @@ -446,79 +446,79 @@ python3 build.py</string>
<key>046A5FD1-F7FF-430A-A92C-62D5AC3C5328</key>
<dict>
<key>xpos</key>
<real>965</real>
<real>965.0</real>
<key>ypos</key>
<real>445</real>
<real>445.0</real>
</dict>
<key>3101A957-02C3-4D0C-8B13-1C3721459261</key>
<dict>
<key>xpos</key>
<real>280</real>
<real>280.0</real>
<key>ypos</key>
<real>650</real>
<real>650.0</real>
</dict>
<key>4C82069F-5857-4F72-9807-0A05DACC1F7C</key>
<dict>
<key>xpos</key>
<real>540</real>
<real>540.0</real>
<key>ypos</key>
<real>505</real>
<real>505.0</real>
</dict>
<key>4D86E603-A8EB-4EE2-BB90-12BECBE1D574</key>
<dict>
<key>xpos</key>
<real>540</real>
<real>540.0</real>
<key>ypos</key>
<real>380</real>
<real>380.0</real>
</dict>
<key>5B0C442D-7335-4973-A2EE-E58281C60266</key>
<dict>
<key>xpos</key>
<real>965</real>
<real>965.0</real>
<key>ypos</key>
<real>710</real>
<real>710.0</real>
</dict>
<key>7F4B5FD3-5072-47C9-8129-E130EF0D2E59</key>
<dict>
<key>xpos</key>
<real>960</real>
<real>960.0</real>
<key>ypos</key>
<real>305</real>
<real>305.0</real>
</dict>
<key>7F6F4EBD-9981-4B82-B039-7183EA9EEB15</key>
<dict>
<key>xpos</key>
<real>540</real>
<real>540.0</real>
<key>ypos</key>
<real>435</real>
<real>435.0</real>
</dict>
<key>A8D008AC-C61A-4037-A423-E33492A4CC62</key>
<dict>
<key>xpos</key>
<real>40</real>
<real>40.0</real>
<key>ypos</key>
<real>440</real>
<real>440.0</real>
</dict>
<key>C7B89588-34B4-49B4-9848-43975E80F16B</key>
<dict>
<key>xpos</key>
<real>260</real>
<real>260.0</real>
<key>ypos</key>
<real>450</real>
<real>450.0</real>
</dict>
<key>ECD812F2-4D1E-41CA-B344-81B25B4A6357</key>
<dict>
<key>xpos</key>
<real>540</real>
<real>540.0</real>
<key>ypos</key>
<real>565</real>
<real>565.0</real>
</dict>
<key>F80F0C10-7220-4984-8EED-67425751FBC3</key>
<dict>
<key>xpos</key>
<real>960</real>
<real>960.0</real>
<key>ypos</key>
<real>585</real>
<real>585.0</real>
</dict>
</dict>
<key>userconfigurationconfig</key>
Expand Down Expand Up @@ -647,7 +647,7 @@ python3 build.py</string>
<key>variablesdontexport</key>
<array/>
<key>version</key>
<string>2.0.0</string>
<string>2.1.0</string>
<key>webaddress</key>
<string>https://github.com/chkpwd/alfred-ente-auth</string>
</dict>
Expand Down
27 changes: 24 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
import os
import sys
Expand All @@ -18,10 +19,19 @@
from src.totp_accounts_manager import format_totp_result # noqa: E402
from src.utils import ( # noqa: E402
fuzzy_search_accounts,
sanitize_service_name,
output_alfred_message,
str_to_bool,
)

from src.constants import ( # noqa: E402
CACHE_ENV_VAR,
ICONS_FOLDER,
)

from src.icon_downloader import get_icon # noqa: E402


logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
Expand Down Expand Up @@ -52,11 +62,22 @@
output_alfred_message("Failed to export TOTP data", str(e))
else:
try:
import_result = ente_export_to_keychain(ente_export_path)
service_names_list: list[str] = []
result = ente_export_to_keychain(ente_export_path)

variables = result.variables
totp_accounts = json.loads(variables[CACHE_ENV_VAR])

for k, _ in totp_accounts.items():
try:
get_icon(sanitize_service_name(k), ICONS_FOLDER)
except Exception as e:
logger.warning(f"Failed to download icon: {e}")

output_alfred_message(
"Imported TOTP data",
f"Successfully imported {import_result.count} TOTP accounts to keychain and Alfred cache.",
import_result.variables,
f"Successfully imported {result.count} TOTP accounts and downloaded icons.",
variables=variables,
)
except Exception as e:
logger.exception(
Expand Down
12 changes: 11 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ authors = ["Bryan Jones <[email protected]>"]
license = "MIT"
readme = "README.md"
package-mode = false
version = "2.0.0"
version = "2.1.0"

[tool.poetry.dependencies]
python = "^3.11"
pyotp = "^2.9.0"
keyring = "^25.5.0"
requests = "^2.3.2"
simplepycons = "^1!13.18.0"
requests = "^2.32.3"

[build-system]
requires = ["poetry-core>=1.8"]
Expand Down
11 changes: 11 additions & 0 deletions src/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from os import environ
from pathlib import Path

# Keychain service and account for storing the TOTP accounts
KEYCHAIN_SERVICE = "ente-totp-alfred-workflow"
KEYCHAIN_ACCOUNT = "totp_secrets"

# Use an environment variable to cache the JSON data to reduce keychain calls
CACHE_ENV_VAR = "TOTP_CACHE"

ICONS_FOLDER = Path(environ["alfred_workflow_data"]) / "service_icons"
140 changes: 68 additions & 72 deletions src/icon_downloader.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,78 @@
import logging
import os
import pathlib
from pathlib import Path
from urllib.parse import urljoin

import requests

LOGO_DEV_API_URL = "https://img.logo.dev/{domain}?token={api_key}"
LOGO_DEV_API_KEY = "pk_T0ZUG4poQGqfGcFoeCpRww"
ICONS_FOLDER = pathlib.Path.home() / ".local/share/ente-totp/icons"


def sanitize_service_name(service_name):
return service_name.split("-")[0].strip().replace(" ", "")


def download_icon(service_name):
# Downloads an icon for a given service name.

sanitized_name = sanitize_service_name(service_name)

if not LOGO_DEV_API_KEY:
logging.warning("LOGO_DEV_API_KEY is not set. Skipping icon download.")
return "icon.png"

# Determine the domain for the icon service
domain = (
sanitized_name.lower()
if "." in sanitized_name
else f"{sanitized_name.lower()}.com"
)
icon_url = LOGO_DEV_API_URL.format(domain=domain, api_key=LOGO_DEV_API_KEY)
icon_path = ICONS_FOLDER / f"{sanitized_name.replace('.', '_').lower()}.png"

if not icon_path.exists():
try:
logging.warning(
f"Attempting to download icon for {sanitized_name} from {icon_url}"
)
response = requests.get(icon_url, timeout=5)
logging.warning(
f"Response status for {sanitized_name}: {response.status_code}"
)

if response.status_code == 200:
ICONS_FOLDER.mkdir(parents=True, exist_ok=True)
with open(icon_path, "wb") as icon_file:
icon_file.write(response.content)
logging.warning(
f"Icon downloaded successfully for {sanitized_name} at {icon_path}"
)
else:
logging.warning(
f"Failed to download icon for {sanitized_name}. Status code: {response.status_code}"
from simplepycons import all_icons

logger = logging.getLogger(__name__)


def get_ente_custom_icon(name: str):
icons_database_url = "https://raw.githubusercontent.com/ente-io/ente/refs/heads/main/auth/assets/custom-icons/_data/custom-icons.json"
ente_custom_icons_db = "https://raw.githubusercontent.com/ente-io/ente/refs/heads/main/auth/assets/custom-icons/icons/"

try:
response = requests.get(icons_database_url)

if response.status_code == 200:
ente_custom_icons = response.json()
matching_icon = [
icon["slug"] if icon.get("slug") else icon["title"].lower()
for icon in ente_custom_icons.get("icons", [])
if name.lower()
in [
icon["title"].lower(),
icon.get("slug", "").lower(),
*[name.lower() for name in icon.get("altNames", [])],
]
]
# matching_icon: next(icon["slug"] for icon in iter(ente_custom_icons) if name.lower() in )
if matching_icon:
response = requests.get(
urljoin(ente_custom_icons_db, f"{matching_icon[0]}.svg")
)
return "icon.png"
except requests.RequestException as e:
logging.warning(f"Request failed for {sanitized_name}: {e}")
return "icon.png"
response.raise_for_status()
return response.text
else:
logger.error(f"Failed to fetch custom icons: {response.status_code}")
except requests.RequestException as e:
logger.error(f"Error while fetching custom icons: {e}")


def get_simplepycons_icon(name: str):
"""
Gets an icon for a given service name.
Returns:
str: Path to the icon, or the default icon if retrieving the object fails.
"""

try:
icon = all_icons[name] # type: ignore
except KeyError:
logger.warning(f"Icon for '{name}' not found in Simplepycons.")
else:
logging.warning(f"Icon already exists for {sanitized_name} at {icon_path}")
icon = icon.customize_svg_as_str(fill=icon.primary_color)
return str(icon)

return str(icon_path)

def get_icon(service: str, icons_dir: Path):
icons_dir.mkdir(parents=True, exist_ok=True)
icon_path = icons_dir / f"{service}.svg"

def get_icon_path(service_name):
# Gets the path to the icon for a given service, downloading it if necessary.
sanitized_name = sanitize_service_name(service_name)
sanitized_service_name = sanitized_name.replace(" ", "").lower()
icon_path = ICONS_FOLDER / f"{sanitized_service_name}.png"

if icon_path.exists():
logging.warning(f"Using downloaded icon for {sanitized_name}")
return str(icon_path)

logging.warning(f"No icon found for {sanitized_name}, attempting to download.")
return download_icon(sanitized_name)
ente_custom_icon_url = get_ente_custom_icon(service)
simplepycons_icon = get_simplepycons_icon(service)

icon = (
ente_custom_icon_url
if ente_custom_icon_url
else simplepycons_icon
if simplepycons_icon
else None
)

def download_icons(services):
ICONS_FOLDER.mkdir(parents=True, exist_ok=True)
for service in services:
download_icon(service)
if icon:
with open(icon_path, mode="w") as icon_file:
icon_file.write(icon)
logger.debug(f"Icon imported successfully for {service} at {icon_path}")
Loading

0 comments on commit 673c348

Please sign in to comment.