Spaces:
Paused
Paused
Download app/picker.py from Mega-Evolutions/ButtonBot: direct link, hf CLI and curl.
- Browser
- Download file 4.13 kB
-
https://huggingface.co/spaces/Mega-Evolutions/ButtonBot/resolve/main/app/picker.py
- Command line
-
hf download hf://spaces/Mega-Evolutions/ButtonBot/app/picker.py
-
curl -L -o picker.py https://huggingface.co/spaces/Mega-Evolutions/ButtonBot/resolve/main/app/picker.py
4.13 kB
| """The channel chooser used by every command that needs one of the connected channels. | |
| The names are written out in the message as a numbered list (A to Z, 20 to a page); the buttons only carry the numbers | |
| (5 to a row), and â—€ â–¶ underneath move to the previous / next 20. Pressing "3" means the third name of the list. | |
| Each button carries the channel's id, never its position, so a list that changed meanwhile can't pick the wrong one. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from collections import Counter | |
| from typing import Callable, Optional, Sequence | |
| from telethon import Button | |
| from .tgutil import esc, short | |
| PER_PAGE = 20 # channel names in one message | |
| PER_ROW = 5 # number buttons in one row | |
| NAME_MAX = 60 # a longer name is cut in the list | |
| PAGE_CB = "cpg" # callback prefix of the â—€ â–¶ buttons: cpg:<screen>:<argument>:<page> | |
| _DIGITS = re.compile(r"(\d+)") | |
| def natural_key(title: Optional[str]) -> tuple: | |
| """A to Z, numbers by value ("Anime 2" before "Anime 10"), upper and lower case alike.""" | |
| parts = _DIGITS.split(" ".join((title or "").split()).casefold()) | |
| return tuple((0, int(p), "") if p.isdigit() else (1, 0, p) for p in parts if p != "") | |
| def ordered(chans: Sequence) -> list: | |
| """The channels in the order they are listed: by name, ascending (the id breaks ties).""" | |
| return sorted(chans, key=lambda c: (natural_key(c.title), c.id)) | |
| def page_count(total: int) -> int: | |
| return max(1, -(-total // PER_PAGE)) | |
| def clamp(page: int, total: int) -> int: | |
| return max(0, min(int(page), page_count(total) - 1)) | |
| def names_of(chans: Sequence, *, with_username: bool = False) -> list: | |
| """Display names (not yet escaped). Two channels with the same name get their @username (or id) added.""" | |
| keys = [" ".join((c.title or "").split()).casefold() for c in chans] | |
| twins = Counter(keys) | |
| out = [] | |
| for c, key in zip(chans, keys): | |
| name = short(c.title or "", NAME_MAX) or "Channel" | |
| if with_username and c.username: | |
| name += f" (@{c.username})" | |
| elif twins[key] > 1: | |
| name += f" (@{c.username})" if c.username else f" ({c.id})" | |
| out.append(name) | |
| return out | |
| def render( | |
| chans: Sequence, | |
| page: int, | |
| *, | |
| head: str, | |
| kind: str, | |
| arg: str = "", | |
| choose: Optional[Callable] = None, | |
| extra_rows: Sequence = (), | |
| with_username: bool = False, | |
| hint: Optional[str] = "Tap the number of the channel.", | |
| ) -> tuple: | |
| """(text, button rows) of one page. | |
| head the question, already HTML | |
| kind, arg which screen this is - the â—€ â–¶ buttons carry them so the same screen can be drawn again | |
| choose channel -> callback data of its number button (None: a list to read, no number buttons) | |
| extra_rows buttons under the arrows (Cancel ...) | |
| hint the line under the list that says what the number buttons do (None: leave it out) | |
| """ | |
| chans = ordered(chans) | |
| total = len(chans) | |
| pages = page_count(total) | |
| page = clamp(page, total) | |
| first = page * PER_PAGE | |
| shown = chans[first : first + PER_PAGE] | |
| names = names_of(chans, with_username=with_username)[first : first + PER_PAGE] | |
| lines = [f"{first + i}. {esc(name)}" for i, name in enumerate(names, 1)] | |
| text = f"{head}\n\n" + "\n".join(lines) | |
| if pages > 1: | |
| text += f"\n\nPage {page + 1} of {pages} - channels {first + 1} to {first + len(shown)} of {total}" | |
| if choose is not None and hint: | |
| text += f"\n\n{hint}" | |
| rows: list = [] | |
| if choose is not None: | |
| nums = [Button.inline(str(first + i), choose(c)) for i, c in enumerate(shown, 1)] | |
| rows += [nums[i : i + PER_ROW] for i in range(0, len(nums), PER_ROW)] | |
| if pages > 1: # always both arrows: on the first page â—€ goes to the last page, on the last page â–¶ to the first | |
| rows.append( | |
| [ | |
| Button.inline("â—€", f"{PAGE_CB}:{kind}:{arg}:{(page - 1) % pages}"), | |
| Button.inline("â–¶", f"{PAGE_CB}:{kind}:{arg}:{(page + 1) % pages}"), | |
| ] | |
| ) | |
| rows += [list(r) for r in extra_rows] | |
| return text, rows | |