Spaces:
Running
Running
File size: 6,896 Bytes
58f00c9 6c3a31d b737d7a 58f00c9 6c3a31d 58f00c9 6c3a31d 58f00c9 6c3a31d 58f00c9 6c3a31d 58f00c9 21cde0c 58f00c9 a670808 | 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 152 153 154 155 156 157 158 159 160 161 162 163 164 | """Validation + persistence for every user-editable setting.
Used by both the slash commands and the inline "send me the value" flow, so the
two can never disagree. Functions return ``(ok, html_message)``.
``db`` is whatever keeps the setting: the ``Database`` for the user's own settings, a ``FormatSettings`` for a saved format
(the template and the caption live in formats, not in the user's settings).
"""
from __future__ import annotations
import re
from typing import Optional, Tuple
from .db import DEFAULT_CAPTION, Database
from .media import parse_track_tokens, validate_track_tokens
from .parser import (
CAPTION_VARS,
DEFAULT_TEMPLATE,
TEMPLATE_VARS,
finalize_name,
render_template,
sample_values,
sanitize,
)
from .panels import fmt_tokens
from .utils import clip, esc
CLEAR_WORDS = {"off", "none", "clear", "reset", "-", "remove"}
META_KINDS = {
"mt_title": "title", "mt_author": "author", "mt_artist": "artist",
"mt_audio": "audio", "mt_subtitle": "subtitle", "mt_video": "video",
}
META_COMMANDS = {
"settitle": "mt_title", "setauthor": "mt_author", "setartist": "mt_artist",
"setaudio": "mt_audio", "setsubtitle": "mt_subtitle", "setvideo": "mt_video",
}
_TOKEN_RE = re.compile(r"\{(\w+)\}")
def preview_template(template: str) -> str:
sample, values = sample_values()
out = finalize_name(sanitize(render_template(template, values)), ".mkv")
return f"Preview for <code>{esc(sample)}</code>:\n<code>{esc(out)}</code>"
def _is_clear(text: str) -> bool:
return text.strip().lower() in CLEAR_WORDS
async def apply_setting(db: Database, uid: int, kind: str, text: str) -> Tuple[bool, str]:
text = (text or "").strip()
if kind == "template":
if not text:
return False, "β The template can't be empty."
if _is_clear(text):
text = DEFAULT_TEMPLATE
if len(text) > 200:
return False, "β That template is too long (200 characters max)."
unknown = sorted(set(_TOKEN_RE.findall(text)) - set(TEMPLATE_VARS))
await db.update_settings(uid, template=text)
msg = "β
Template saved.\n" + preview_template(text)
if unknown:
msg += "\nβ οΈ Unknown variables (kept as plain text): " + ", ".join("{%s}" % u for u in unknown)
return True, msg
if kind == "caption":
if not text:
return False, "β The caption can't be empty. Send <code>reset</code> to restore the default."
if _is_clear(text):
text = DEFAULT_CAPTION
if len(text) > 800:
return False, "β That caption is too long (800 characters max, formatting and link addresses included)."
unknown = sorted(set(_TOKEN_RE.findall(text)) - set(CAPTION_VARS))
await db.update_settings(uid, caption=text)
msg = "β
Caption template saved."
if unknown:
msg += "\nβ οΈ Unknown variables (kept as plain text): " + ", ".join("{%s}" % u for u in unknown)
return True, msg
if kind == "remove":
if _is_clear(text):
await db.update_settings(uid, remove_words=[])
return True, "β
Remove-words list cleared."
words = [w.strip() for w in re.split(r"[,\n]", text) if w.strip()]
if not words:
return False, "β Send at least one word, separated by commas."
if len(words) > 50 or any(len(w) > 60 for w in words):
return False, "β Up to 50 words of 60 characters each."
await db.update_settings(uid, remove_words=words)
return True, "β
I'll strip: " + esc(", ".join(words))
if kind in META_KINDS:
field = META_KINDS[kind]
if not text:
return False, "β Send a value, or <code>off</code> to clear it."
if _is_clear(text):
await db.update_settings(uid, meta={field: ""})
return True, f"β
{field.title()} cleared."
if len(text) > 200:
return False, "β That's too long (200 characters max)."
await db.update_settings(uid, meta={field: text})
note = ""
if not (await db.get_settings(uid))["metadata_on"]:
note = "\nβΉοΈ Metadata is Off β switch it On in /metadata for this to apply."
return True, f"β
{field.title()} set to <code>{esc(clip(text, 80))}</code>.{note}"
if kind in ("keep_audio", "keep_subtitle"):
label = "Keep Audio" if kind == "keep_audio" else "Keep Subtitle"
if _is_clear(text):
await db.update_settings(uid, **{kind: []})
return True, f"β
{label} cleared β all tracks are kept."
tokens = parse_track_tokens(text)
if not tokens:
return False, "β Send language names (<code>English Japanese</code>) or numbers (<code>2 1</code>)."
problem = validate_track_tokens(tokens)
if problem:
return False, "β " + esc(problem)
if len(tokens) > 12:
return False, "β That's too many tracks (12 max)."
await db.update_settings(uid, **{kind: tokens})
msg = f"β
{label}: {esc(fmt_tokens(tokens))}"
if not (await db.get_settings(uid))["metadata_on"]:
await db.update_settings(uid, metadata_on=True)
msg += "\nβΉοΈ Metadata was Off, so I turned it On. Use /metadata to switch it off."
return True, msg
raise ValueError(f"unknown setting kind {kind!r}")
def parse_channel(text: str) -> Tuple[bool, Optional[int], str]:
"""``-1001234567890`` -> id; ``off`` -> None."""
text = text.strip()
if _is_clear(text):
return True, None, ""
if re.fullmatch(r"-?\d{5,20}", text):
return True, int(text), ""
return False, None, "β Send the numeric chat id, like <code>-1001234567890</code> (not @username)."
_CHANNEL_LINK = re.compile(r"(?:https?://)?(?:www\.)?t\.me/c/(\d{5,15})(?:/\d+){0,2}/?", re.I)
CHANNEL_ID_HINT = "β Send the channel id, like <code>-1001234567890</code> (a channel id starts with -100), or <code>off</code> to clear it."
def parse_format_channel(text: str) -> Tuple[bool, Optional[int], str]:
"""The Channel option of a manual format: ``-1001234567890`` -> that id, ``off`` -> ``None``.
A bare channel id (``1234567890``) and a link to a post of a private channel (``t.me/c/1234567890/5``) work too: both get
the ``-100`` in front that Telegram puts on channel ids.
"""
text = (text or "").strip()
if _is_clear(text):
return True, None, ""
link = _CHANNEL_LINK.fullmatch(text)
if link:
return True, -1_000_000_000_000 - int(link.group(1)), ""
if re.fullmatch(r"-?\d{5,20}", text):
value = int(text)
if value > 0:
return True, -1_000_000_000_000 - value, ""
if value <= -1_000_000_000_001:
return True, value, ""
return False, None, CHANNEL_ID_HINT
|