Spaces:
Paused
Paused
File size: 4,756 Bytes
61d6e2a 2e79f82 61d6e2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | """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
|