| @@ -1,11 +1,10 @@ | |||||
| __all__ = ["get_background"] | __all__ = ["get_background"] | ||||
| import bisect | |||||
| import functools | |||||
| import json | import json | ||||
| import logging | import logging | ||||
| import random | import random | ||||
| import re | import re | ||||
| import urllib.parse | |||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||
| from datetime import UTC, date, datetime, timedelta | from datetime import UTC, date, datetime, timedelta | ||||
| from typing import Self | from typing import Self | ||||
| @@ -18,16 +17,16 @@ from .cookies import get_cookies | |||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||
| THUMB_SIZES = [20, 40, 60, 120, 250, 330, 500, 960, 1280, 1920, 3840] | |||||
| @dataclass(frozen=True) | @dataclass(frozen=True) | ||||
| class BackgroundInfo: | class BackgroundInfo: | ||||
| filename: str | filename: str | ||||
| @dataclass(frozen=True) | |||||
| class BackgroundURLs: | |||||
| url: str | url: str | ||||
| descurl: str | descurl: str | ||||
| width: int | |||||
| height: int | |||||
| @dataclass(frozen=True) | @dataclass(frozen=True) | ||||
| @@ -54,27 +53,6 @@ def _get_commons_site() -> Site: | |||||
| return cache.bot.wiki.add_site(project="wikimedia", lang="commons") | 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: | def _get_fresh_from_potd() -> BackgroundInfo | None: | ||||
| site = _get_commons_site() | site = _get_commons_site() | ||||
| date = datetime.now(UTC).strftime("%Y-%m-%d") | date = datetime.now(UTC).strftime("%Y-%m-%d") | ||||
| @@ -93,7 +71,7 @@ def _get_fresh_from_potd() -> BackgroundInfo | None: | |||||
| if not filename: | if not filename: | ||||
| logger.exception(f"Failed to extract POTD from {page.title!r}") | logger.exception(f"Failed to extract POTD from {page.title!r}") | ||||
| return None | return None | ||||
| return _load_file(site, filename) | |||||
| return BackgroundInfo(filename) | |||||
| def _get_fresh_from_list() -> BackgroundInfo | None: | def _get_fresh_from_list() -> BackgroundInfo | None: | ||||
| @@ -114,19 +92,7 @@ def _get_fresh_from_list() -> BackgroundInfo | None: | |||||
| except IndexError: | except IndexError: | ||||
| logger.exception(f"Failed to find any images on {page.title!r}") | logger.exception(f"Failed to find any images on {page.title!r}") | ||||
| return None | 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 = { | _BACKGROUNDS = { | ||||
| @@ -134,20 +100,48 @@ _BACKGROUNDS = { | |||||
| "list": _get_fresh_from_list, | "list": _get_fresh_from_list, | ||||
| } | } | ||||
| _BACKGROUND_CACHE: dict[str, BackgroundInfo | None] = {} | |||||
| _BACKGROUND_INFO_CACHE: dict[str, BackgroundInfo | None] = {} | |||||
| _LAST_BACKGROUND_UPDATES: dict[str, date] = { | _LAST_BACKGROUND_UPDATES: dict[str, date] = { | ||||
| key: datetime.min.date() for key in _BACKGROUNDS | 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) | next_day = _LAST_BACKGROUND_UPDATES[selected] + timedelta(days=1) | ||||
| max_age = datetime(next_day.year, next_day.month, next_day.day, tzinfo=UTC) | max_age = datetime(next_day.year, next_day.month, next_day.day, tzinfo=UTC) | ||||
| if datetime.now(UTC) > max_age: | if datetime.now(UTC) > max_age: | ||||
| update_func = _BACKGROUNDS.get(selected, _get_fresh_from_list) | 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() | _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]: | 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: | else: | ||||
| screen = ScreenInfo() | 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 | |||||