| @@ -1,11 +1,10 @@ | |||
| __all__ = ["get_background"] | |||
| import bisect | |||
| import functools | |||
| import json | |||
| import logging | |||
| import random | |||
| import re | |||
| import urllib.parse | |||
| from dataclasses import dataclass | |||
| from datetime import UTC, date, datetime, timedelta | |||
| from typing import Self | |||
| @@ -18,16 +17,16 @@ from .cookies import get_cookies | |||
| logger = logging.getLogger(__name__) | |||
| THUMB_SIZES = [20, 40, 60, 120, 250, 330, 500, 960, 1280, 1920, 3840] | |||
| @dataclass(frozen=True) | |||
| class BackgroundInfo: | |||
| filename: str | |||
| @dataclass(frozen=True) | |||
| class BackgroundURLs: | |||
| url: str | |||
| descurl: str | |||
| width: int | |||
| height: int | |||
| @dataclass(frozen=True) | |||
| @@ -54,27 +53,6 @@ def _get_commons_site() -> Site: | |||
| return cache.bot.wiki.add_site(project="wikimedia", lang="commons") | |||
| def _load_file(site: Site, filename: str) -> BackgroundInfo | None: | |||
| prefix = "File:" | |||
| try: | |||
| data = site.api_query( | |||
| action="query", | |||
| prop="imageinfo", | |||
| iiprop="url|size|canonicaltitle", | |||
| titles=prefix + filename, | |||
| ) | |||
| res = list(data["query"]["pages"].values())[0]["imageinfo"][0] | |||
| name = res["canonicaltitle"] | |||
| assert isinstance(name, str), name | |||
| except Exception: | |||
| logger.exception(f"Failed to get info for file {prefix + filename!r}") | |||
| return None | |||
| name = name.removeprefix(prefix).replace(" ", "_") | |||
| return BackgroundInfo( | |||
| name, res["url"], res["descriptionurl"], res["width"], res["height"] | |||
| ) | |||
| def _get_fresh_from_potd() -> BackgroundInfo | None: | |||
| site = _get_commons_site() | |||
| date = datetime.now(UTC).strftime("%Y-%m-%d") | |||
| @@ -93,7 +71,7 @@ def _get_fresh_from_potd() -> BackgroundInfo | None: | |||
| if not filename: | |||
| logger.exception(f"Failed to extract POTD from {page.title!r}") | |||
| return None | |||
| return _load_file(site, filename) | |||
| return BackgroundInfo(filename) | |||
| def _get_fresh_from_list() -> BackgroundInfo | None: | |||
| @@ -114,19 +92,7 @@ def _get_fresh_from_list() -> BackgroundInfo | None: | |||
| except IndexError: | |||
| logger.exception(f"Failed to find any images on {page.title!r}") | |||
| return None | |||
| return _load_file(site, filename) | |||
| def _build_url(screen: ScreenInfo, background: BackgroundInfo) -> str: | |||
| width = screen.width | |||
| if background.width / background.height > screen.width / screen.height: | |||
| width = int(background.width / background.height * screen.height) | |||
| i = bisect.bisect_right(THUMB_SIZES, width) | |||
| width = THUMB_SIZES[i] if i < len(THUMB_SIZES) else background.width | |||
| if width >= background.width: | |||
| return background.url | |||
| url = background.url.replace("/commons/", "/commons/thumb/") | |||
| return f"{url}/{width}px-{urllib.parse.quote(background.filename)}" | |||
| return BackgroundInfo(filename) | |||
| _BACKGROUNDS = { | |||
| @@ -134,20 +100,48 @@ _BACKGROUNDS = { | |||
| "list": _get_fresh_from_list, | |||
| } | |||
| _BACKGROUND_CACHE: dict[str, BackgroundInfo | None] = {} | |||
| _BACKGROUND_INFO_CACHE: dict[str, BackgroundInfo | None] = {} | |||
| _LAST_BACKGROUND_UPDATES: dict[str, date] = { | |||
| key: datetime.min.date() for key in _BACKGROUNDS | |||
| } | |||
| def _get_background(selected: str) -> BackgroundInfo | None: | |||
| def _get_background_info(selected: str) -> BackgroundInfo | None: | |||
| next_day = _LAST_BACKGROUND_UPDATES[selected] + timedelta(days=1) | |||
| max_age = datetime(next_day.year, next_day.month, next_day.day, tzinfo=UTC) | |||
| if datetime.now(UTC) > max_age: | |||
| update_func = _BACKGROUNDS.get(selected, _get_fresh_from_list) | |||
| _BACKGROUND_CACHE[selected] = update_func() | |||
| _BACKGROUND_INFO_CACHE[selected] = update_func() | |||
| _LAST_BACKGROUND_UPDATES[selected] = datetime.now(UTC).date() | |||
| return _BACKGROUND_CACHE[selected] | |||
| return _BACKGROUND_INFO_CACHE[selected] | |||
| @functools.lru_cache(maxsize=256) | |||
| def _get_background_urls( | |||
| background: BackgroundInfo, screen_width: int | |||
| ) -> BackgroundURLs | None: | |||
| site = _get_commons_site() | |||
| prefix = "File:" | |||
| try: | |||
| data = site.api_query( | |||
| action="query", | |||
| prop="imageinfo", | |||
| iiprop="url|canonicaltitle", | |||
| iiurlwidth=screen_width, | |||
| titles=prefix + background.filename, | |||
| ) | |||
| res = list(data["query"]["pages"].values())[0]["imageinfo"][0] | |||
| name = res["canonicaltitle"] | |||
| assert isinstance(name, str), name | |||
| url = res["thumburl"] | |||
| descurl = res["descriptionurl"] | |||
| except Exception: | |||
| logger.exception( | |||
| f"Failed to get info for file {prefix + background.filename!r}" | |||
| ) | |||
| return None | |||
| name = name.removeprefix(prefix).replace(" ", "_") | |||
| return BackgroundURLs(url=url, descurl=descurl) | |||
| def get_background(selected: str) -> tuple[str | None, str | None]: | |||
| @@ -161,9 +155,9 @@ def get_background(selected: str) -> tuple[str | None, str | None]: | |||
| else: | |||
| screen = ScreenInfo() | |||
| background = _get_background(selected) | |||
| if background: | |||
| bg_url = _build_url(screen, background) | |||
| return bg_url, background.descurl | |||
| else: | |||
| return None, None | |||
| bg_info = _get_background_info(selected) | |||
| if bg_info is not None: | |||
| bg_urls = _get_background_urls(bg_info, screen.width) | |||
| if bg_urls is not None: | |||
| return bg_urls.url, bg_urls.descurl | |||
| return None, None | |||