Spaces:
Paused
Paused
Download app/buttons.py from Mega-Evolutions/ButtonBot: direct link, hf CLI and curl.
- Browser
- Download file 4.76 kB
-
https://huggingface.co/spaces/Mega-Evolutions/ButtonBot/resolve/main/app/buttons.py
- Command line
-
hf download hf://spaces/Mega-Evolutions/ButtonBot/app/buttons.py
-
curl -L -o buttons.py https://huggingface.co/spaces/Mega-Evolutions/ButtonBot/resolve/main/app/buttons.py
4.76 kB
| """Button model: rows of {'t': text, 'u': url} (URL buttons) or {'t', 'raw'} (other bots' buttons, kept as-is).""" | |
| from __future__ import annotations | |
| import re | |
| from typing import Optional | |
| MAX_ROW = 8 | |
| MAX_TOTAL = 100 | |
| _URL_RX = re.compile(r"(?:https?://|tg://|t\.me/|telegram\.me/)\S+", re.I) | |
| def norm_url(u: str) -> Optional[str]: | |
| u = (u or "").strip() | |
| if re.match(r"(?i)^(t\.me|telegram\.me)/", u): | |
| u = "https://" + u | |
| if not re.match(r"(?i)^(https?://|tg://)\S+$", u) or len(u) > 2000: | |
| return None | |
| return u | |
| def parse_buttons_text(text: str) -> list: | |
| """One row per line, 'Text - link'. Several buttons in a row: separate them with ' | '.""" | |
| rows: list = [] | |
| total = 0 | |
| for line in (text or "").strip().splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| row = [] | |
| for part in re.split(r"\s+\|\s+|\s*&&\s*", line): | |
| part = part.strip() | |
| if not part: | |
| continue | |
| found = list(_URL_RX.finditer(part)) | |
| if not found: | |
| raise ValueError(f"No link found in: {part}") | |
| m = found[-1] | |
| label = part[: m.start()].strip().rstrip("-–—:|=").strip() | |
| if not label: | |
| raise ValueError(f"No button text before the link in: {part}") | |
| url = norm_url(m.group(0)) | |
| if not url: | |
| raise ValueError(f"That link doesn't look valid: {m.group(0)}") | |
| row.append({"t": label, "u": url}) | |
| if len(row) > MAX_ROW: | |
| raise ValueError(f"At most {MAX_ROW} buttons fit in one row.") | |
| if row: | |
| rows.append(row) | |
| total += len(row) | |
| if total > MAX_TOTAL: | |
| raise ValueError(f"At most {MAX_TOTAL} buttons per post.") | |
| return rows | |
| def copy_rows(rows: list) -> list: | |
| return [[dict(b) for b in row] for row in (rows or [])] | |
| def count(rows: list) -> int: | |
| return sum(len(r) for r in rows or []) | |
| def missing_links(rows: list) -> int: | |
| return sum(1 for r in rows or [] for b in r if not b.get("u") and not b.get("raw")) | |
| def link_positions(rows: list) -> list: | |
| """(row, column) of every button that has a link of its own. Other bots' buttons (reactions...) are left out: | |
| they can't be edited.""" | |
| return [(r, c) for r, row in enumerate(rows or []) for c, b in enumerate(row) if b.get("u") and not b.get("raw")] | |
| def valid(rows: list, r: int, c: int) -> bool: | |
| return 0 <= r < len(rows) and 0 <= c < len(rows[r]) | |
| def add_button(rows: list, btn: dict, new_row: bool = True) -> list: | |
| rows = copy_rows(rows) | |
| if new_row or not rows or len(rows[-1]) >= MAX_ROW: | |
| rows.append([btn]) | |
| else: | |
| rows[-1].append(btn) | |
| return rows | |
| def set_field(rows: list, r: int, c: int, **fields) -> list: | |
| rows = copy_rows(rows) | |
| if valid(rows, r, c): | |
| rows[r][c].update(fields) | |
| return rows | |
| def delete_button(rows: list, r: int, c: int) -> list: | |
| rows = copy_rows(rows) | |
| if valid(rows, r, c): | |
| rows[r].pop(c) | |
| rows = [row for row in rows if row] | |
| return rows | |
| def move_button(rows: list, r: int, c: int, direction: str) -> tuple: | |
| """direction: l | r | u | d. Returns (rows, new_r, new_c).""" | |
| rows = copy_rows(rows) | |
| if not valid(rows, r, c): | |
| return rows, r, c | |
| b = rows[r][c] | |
| if direction == "l": | |
| if c > 0: | |
| rows[r][c - 1], rows[r][c] = rows[r][c], rows[r][c - 1] | |
| c -= 1 | |
| elif direction == "r": | |
| if c < len(rows[r]) - 1: | |
| rows[r][c + 1], rows[r][c] = rows[r][c], rows[r][c + 1] | |
| c += 1 | |
| elif direction == "u": | |
| if r > 0 and len(rows[r - 1]) < MAX_ROW: | |
| rows[r].pop(c) | |
| pos = min(c, len(rows[r - 1])) | |
| rows[r - 1].insert(pos, b) | |
| nr, nc = r - 1, pos | |
| if not rows[r]: | |
| rows.pop(r) | |
| r, c = nr, nc | |
| elif direction == "d": | |
| if r < len(rows) - 1: | |
| if len(rows[r + 1]) < MAX_ROW: | |
| rows[r].pop(c) | |
| pos = min(c, len(rows[r + 1])) | |
| rows[r + 1].insert(pos, b) | |
| nr, nc = r + 1, pos | |
| if not rows[r]: | |
| rows.pop(r) | |
| nr -= 1 | |
| r, c = nr, nc | |
| elif len(rows[r]) > 1: # last row with company: split off into a new row below | |
| rows[r].pop(c) | |
| rows.append([b]) | |
| r, c = len(rows) - 1, 0 | |
| return rows, r, c | |
| def grid_label(b: dict) -> str: | |
| t = (b.get("t") or "").strip() or "…" | |
| if len(t) > 26: | |
| t = t[:25] + "…" | |
| if b.get("raw"): | |
| return "🔒 " + t | |
| if not b.get("u"): | |
| return "⚠️ " + t | |
| return t | |