# requires: aiohttp
# meta developer: @H_SunMods
# meta banner: https://r2.fakecrime.bio/uploads/965a3206-4609-4dff-beb0-6831f8b90e12.jpg
# current ver
__version__ = (0, 1, 1)
import json
import socket
import asyncio
import secrets
import logging
import re
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from aiohttp import ClientSession, ClientTimeout, web
from herokutl.types import Message
from .. import loader, utils
from ..inline.types import InlineCall
html_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/index.html"
css_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/style.css"
js_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/NoChess/raw_assets/javascript.js"
asset_root_raw = "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/NoChess"
botfather_photo_url = "https://r2.fakecrime.bio/uploads/d3e16245-15a2-43f1-b176-493b4d9f1f21.jpg"
@loader.tds
class NoChess(loader.Module):
"""NoChess - web module that allows u to launch a web page either as a functional HTML page or as a Telegram Mini-App. This is an add-on for Chess module by @nullmod"""
strings = {
"name": "NoChess",
"starting": "( ノ・ェ・ )ノ Starting NoChess...",
"online": "(*˘︶˘*) NoChess is running",
"already_running": "ʕᵕᴥᵕʔ NoChess is already running",
"stopped": "・゚・(。>д<。)・゚・ NoChess stopped",
"not_running": "(✿╹◡╹) NoChess is not running",
"tunnel_error": "Serveo tunnel error: {}",
"asset_read_error": "Failed to load web assets: {}",
"open_button": "Open mini-app",
"stop_button": "Stop",
"about_text": "Important read:\nSometimes the server won't lift cause there's enough processes running, for example on HikkaHost, for this I just rebooted the server\nNext is that cma setups the app by a template and it's rly crooked, so you'll have to set some web app config settings yourself\nAnd also:\n 1. First launch will start straight with a site link, not as a web app\n 2. Use nochess, and then cma to setup the web app\n 3. After that restart the process by typing nochess -kill and nochess again\nYeah it's hacky as hell, but I was so over doing stuff that I started dumping some routine like working with files on ai, which I didn't like so I decided to quick-release the module before it's too late\nWell and maybe soon I'll make an update, right now it's some pre-alpha version, that's why the version name is like this, later I'll change it to 1.0.0, if people actually dig the module as an idea",
"cma_start": "( ノ・ェ・ )ノ Creating mini app in BotFather...",
"cma_need_url": "Set mini app web URL first or run .nochess to get it.",
"cma_done": "(*˘︶˘*) Done.",
"cma_error": "Error: {}",
"RuntimeError": "inline bot username not found",
"not_supported_platform": "(┬┬_┬┬) Unfortunately, it is impossible to install this module on this platform.\n\n(〜^∇^)〜 This is not an error, please do not contact support."
}
strings_ru = {
"_cls_doc": "NoChess - Веб модуль который позволяет запускать веб-пейдж,как HTML страницу с функционалом,так же в виде Telegram Mini-App. Является дополнением к модулю Chess от @nullmod",
"starting": "( ノ・ェ・ )ノ Запуск NoChess...",
"online": "(*˘︶˘*) NoChess запущен",
"already_running": "ʕᵕᴥᵕʔ NoChess уже запущен",
"stopped": "・゚・(。>д<。)・゚・ NoChess остановлен",
"not_running": "(✿╹◡╹) NoChess не запущен",
"tunnel_error": "Ошибка туннеля Serveo: {}",
"asset_read_error": "Не удалось загрузить веб-ассеты: {}",
"open_button": "Открыть мини-приложение",
"stop_button": "Остановить",
"about_text": "Важно к прочтению:\nИногда сервер не может подниматься из за того что запущено достаточно процессов, например на HikkaHost,для этого я просто перезагружал сервер.\nДалее это то что cma сетапает приложение по шаблону и оч криво, поэтому вам придется выставлять некоторые настройки конфигурации веб приложения самим.\nА еще:\n 1. Первый запуск будет запускаться сразу ссылкой на сайт, а не как веб приложение.\n 2. Используйте nochess, а потом cma чтобы настроить веб приложение.\n 3. После чего перезапустите процесс написав nochess -kill и повторно nochess.\nДа это костыли, но мне уже настолько было в падлу что то делать что я уже стал спихивать рутину по типу работы с файлами на ии, что мне не понравилось и я решил быстро релизать модуль пока не стало поздно.\nНу и может быть в скором времени я уже сделаю апдейт, на данный момент это какая то пре-альфа версия, поэтому и название версии такое, в дальнейшем изменю на 1.0.0, если модуль вообще понравиться людям как идея.",
"cma_start": "( ノ・ェ・ )ノ Создаю эпку через BotFather...",
"cma_need_url": "Сначала укажи URL мини-эпки или запусти .nochess, чтобы получить его",
"cma_done": "(*˘︶˘*) Готово",
"cma_error": "Ошибка: {}",
"RuntimeError": "юз инлайн бота не найден",
"not_supported_platform": "(┬┬_┬┬) К сожалению, на эту платформу невозможно установить этот модуль.\n\n(〜^∇^)〜 Это не ошибка, пожалуйста, не обращайтесь в поддержку."
}
strings_de = {
"_cls_doc": "NoChess - Webmodul zum Starten einer Webseite als HTML oder Telegram Mini-App. Erweiterung für Chess von @nullmod",
"starting": "( ノ・ェ・ )ノ Starte NoChess...",
"online": "(*˘︶˘*) NoChess läuft",
"already_running": "ʕᵕᴥᵕʔ NoChess läuft bereits",
"stopped": "・゚・(。>д<。)・゚・ NoChess gestoppt",
"not_running": "(✿╹◡╹) NoChess läuft nicht",
"tunnel_error": "Serveo-Tunnel-Fehler: {}",
"asset_read_error": "Fehler beim Laden der Web-Assets: {}",
"open_button": "Mini-App öffnen",
"stop_button": "Stopp",
"about_text": "Wichtig zu lesen:\nManchmal startet der Server nicht, weil zu viele Prozesse laufen. cma richtet die App über eine Vorlage ein, musst einige Web-App-Einstellungen selbst setzen.\nUnd außerdem:\n 1. Erster Start beginnt direkt mit einem Seitenlink, nicht als Web-App\n 2. Verwende nochess und dann cma zum Einrichten\n 3. Danach Prozess neustarten mit nochess -kill und nochmal nochess\nPre-Alpha, später 1.0.0 falls die Leute die Idee mögen.",
"cma_start": "( ノ・ェ・ )ノ Erstelle Mini-App im BotFather...",
"cma_need_url": "Setze zuerst die Mini-App-Web-URL oder führe .nochess aus.",
"cma_done": "(*˘︶˘*) Fertig.",
"cma_error": "Fehler: {}",
"RuntimeError": "Inline-Bot-Benutzername nicht gefunden",
"not_supported_platform": "(┬┬_┬┬) Leider unmöglich, dieses Modul auf dieser Plattform zu installieren.\n\n(〜^∇^)〜 Kein Fehler, Support nicht kontaktieren."
}
strings_ua = {
"_cls_doc": "NoChess - Веб модуль для запуску веб-сторінки як HTML або Telegram Mini-App. Доповнення до Chess від @nullmod",
"starting": "( ノ・ェ・ )ノ Запуск NoChess...",
"online": "(*˘︶˘*) NoChess запущено",
"already_running": "ʕᵕᴥᵕʔ NoChess вже запущено",
"stopped": "・゚・(。>д<。)・゚・ NoChess зупинено",
"not_running": "(✿╹◡╹) NoChess не запущено",
"tunnel_error": "Помилка тунелю Serveo: {}",
"asset_read_error": "Не вдалося завантажити веб-ассети: {}",
"open_button": "Відкрити міні-застосунок",
"stop_button": "Зупинити",
"about_text": "Важливо прочитати:\nІноді сервер не запускається через забагато процесів. cma налаштовує за шаблоном — налаштуйте веб-застосунок самостійно.\nА ще:\n 1. Перший запуск — одразу посилання на сайт, не веб-застосунок\n 2. Використовуйте nochess, потім cma для налаштування\n 3. Перезапустіть процес: nochess -kill, потім nochess\nПре-альфа, пізніше 1.0.0 якщо ідея сподобається.",
"cma_start": "( ノ・ェ・ )ノ Створюю міні-застосунок через BotFather...",
"cma_need_url": "Спочатку вкажи URL або запусти .nochess",
"cma_done": "(*˘︶˘*) Готово.",
"cma_error": "Помилка: {}",
"RuntimeError": "юзернейм інлайн-бота не знайдено",
"not_supported_platform": "(┬┬_┬┬) На жаль, неможливо встановити цей модуль на цю платформу.\n\n(〜^∇^)〜 Це не помилка, не звертайтесь у підтримку."
}
strings_jp = {
"_cls_doc": "NoChess - HTMLまたはTelegram Mini-Appとしてページを起動するモジュール。Chess(@nullmod)のアドオン",
"starting": "( ノ・ェ・ )ノ NoChessを起動中...",
"online": "(*˘︶˘*) NoChessは実行中です",
"already_running": "ʕᵕᴥᵕʔ NoChessはすでに実行中です",
"stopped": "・゚・(。>д<。)・゚・ NoChessを停止しました",
"not_running": "(✿╹◡╹) NoChessは実行されていません",
"tunnel_error": "Serveoトンネルエラー: {}",
"asset_read_error": "Webアセットの読み込みに失敗: {}",
"open_button": "ミニアプリを開く",
"stop_button": "停止",
"about_text": "重要なお知らせ:\nプロセスが多すぎてサーバーが起動しないことがあります。cmaはテンプレートでアプリをセットアップしますが歪なので自分で設定してください。\nさらに:\n 1. 最初の起動はWebアプリではなくサイトリンクで開始\n 2. nochessを使い、cmaで設定\n 3. nochess -killしてから再度nochess\nプレアルファ版、後で1.0.0に変更予定。",
"cma_start": "( ノ・ェ・ )ノ BotFatherでミニアプリを作成中...",
"cma_need_url": "最初にミニアプリのURLを設定するか、.nochessを実行してください",
"cma_done": "(*˘︶˘*) 完了。",
"cma_error": "エラー: {}",
"RuntimeError": "インラインボットのユーザー名が見つかりません",
"not_supported_platform": "(┬┬_┬┬) このプラットフォームにはインストールできません。\n\n(〜^∇^)〜 エラーではありません。サポートに連絡しないでください。"
}
strings_neofit = {
"_cls_doc": "NoChess — web module fer launchin' a page as HTML or Telegram Mini-App. Add-on fer Chess by @nullmod",
"starting": "( ノ・ェ・ )ノ Spinnin' up NoChess...",
"online": "(*˘︶˘*) NoChess is live, fam",
"already_running": "ʕᵕᴥᵕʔ NoChess already cookin'",
"stopped": "・゚・(。>д<。)・゚・ NoChess iced",
"not_running": "(✿╹◡╹) NoChess ain't up",
"tunnel_error": "Serveo tunnel bricked: {}",
"asset_read_error": "Couldn't snag web assets: {}",
"open_button": "Pop the mini-app",
"stop_button": "Cut it",
"about_text": "RTFM:\nBox won't lift sometimes 'cause too many procs — just reboot. cma uses a jank template so tweak config yerself.\nAlso:\n 1. First run = site link, not web app\n 2. Hit nochess then cma to rig it\n 3. Bounce the proc with nochess -kill + nochess\nPre-alpha slop, gonna bump to 1.0.0 if peeps vibe.",
"cma_start": "( ノ・ェ・ )ノ Forgin' mini app via BotFather...",
"cma_need_url": "Drop a mini-app URL first or run .nochess",
"cma_done": "(*˘︶˘*) Ship it.",
"cma_error": "L + ratio: {}",
"RuntimeError": "inline bot handle MIA",
"not_supported_platform": "(┬┬_┬┬) No shot installin' here.\n\n(〜^∇^)〜 Not a bug, don't ping support."
}
strings_tiktok = {
"_cls_doc": "NoChess — веб-модуль запускает страничку как HTML или мини-апп в телеге. Аддон к Chess от @nullmod",
"starting": "( ノ・ェ・ )ノ Газуем NoChess...",
"online": "(*˘︶˘*) NoChess на стиле",
"already_running": "ʕᵕᴥᵕʔ NoChess уже тащит",
"stopped": "・゚・(。>д<。)・゚・ NoChess слился",
"not_running": "(✿╹◡╹) NoChess не в теме",
"tunnel_error": "Serveo тунель крашнулся: {}",
"asset_read_error": "Не смог забрать ассеты: {}",
"open_button": "Открыть мини-апп",
"stop_button": "Стопэ",
"about_text": "Читни сюда:\nБывает серв не поднимается — процов дофига, ребутаю. cma сетапит криво, конфиг руками.\nИ ещё:\n 1. Первый запуск — сразу ссылка на сайт, не апп\n 2. Юзай nochess, потом cma\n 3. Дропни через nochess -kill и снова nochess\nПре-альфа дичь, потом 1.0.0 если залетит.",
"cma_start": "( ノ・ェ・ )ノ Делаю мини-апп через BotFather...",
"cma_need_url": "Сначала кинь URL или жмякни .nochess",
"cma_done": "(*˘︶˘*) Запилил.",
"cma_error": "Ой фейл: {}",
"RuntimeError": "юз бота не нашли",
"not_supported_platform": "(┬┬_┬┬) Сорян, на эту платформу модуль не встанет.\n\n(〜^∇^)〜 Не ошибка, в саппорт не пиши."
}
strings_leet = {
"_cls_doc": "NoChess — w3b m0dul3 t0 l4unch p4g3 4s HTML 0r T3l3gr4m M1n1-4pp. 4dd-0n f0r Ch355 by @nullm0d",
"starting": "( ノ・ェ・ )ノ B00t1ng N0Ch355...",
"online": "(*˘︶˘*) N0Ch355 15 1n th3 m4tr1x",
"already_running": "ʕᵕᴥᵕʔ N0Ch355 4lr34dy 0n",
"stopped": "・゚・(。>д<。)・゚・ N0Ch355 t3rm1n4t3d",
"not_running": "(✿╹◡╹) N0Ch355 0ffl1n3",
"tunnel_error": "S3rv30 tunn3l f41l: {}",
"asset_read_error": "F41l3d t0 f3tch w3b 4553t5: {}",
"open_button": "0p3n m1n1-4pp",
"stop_button": "K1ll",
"about_text": "R34D TH15:\nB0x w0n't l1ft cuz 2 m4ny pr0c5 — r3b00t. cm4 j4nk t3mpl4t3, c0nf1g m4nu4lly.\n4l50:\n 1. F1r5t run = 51t3 l1nk, n0t w3b 4pp\n 2. U53 n0ch355 + cm4\n 3. B0unc3 w1th n0ch355 -k1ll + n0ch355\nPr3-4lph4, bump1n t0 1.0.0 1f p33p5 v1b3.",
"cma_start": "( ノ・ェ・ )ノ C0njur1n9 m1n1 4pp v14 B0tF4th3r...",
"cma_need_url": "Dr0p 4 URL f1r5t 0r run .n0ch355",
"cma_done": "(*˘︶˘*) 5h1pp3d.",
"cma_error": "F41l: {}",
"RuntimeError": "1nl1n3 b0t h4ndl3 n0t f0und",
"not_supported_platform": "(┬┬_┬┬) N0 5h0t 1n5t4ll1n' h3r3.\n\n(〜^∇^)〜 N0t 4 bu9, d0n't p1n9 5upp0rt."
}
strings_uwu = {
"_cls_doc": "NoChess — web moduwe tuwu waunch a page as HTML owr Tewegwam Minyi-App. Add-on fowr Chess by @nuwwmod~",
"starting": "( ノ・ェ・ )ノ Spinning up NoChess-chan...",
"online": "(*˘︶˘*) NoChess is wunning, nyaa~",
"already_running": "ʕᵕᴥᵕʔ NoChess awweady wunning, hehe",
"stopped": "・゚・(。>д<。)・゚・ NoChess went bye-bye",
"not_running": "(✿╹◡╹) NoChess is sweepy...",
"tunnel_error": "Serveo tunnew-bun oopsie: {}",
"asset_read_error": "Couwdn't fetch the pwetty assets: {}",
"open_button": "Open minyi-app~",
"stop_button": "Stahp pwease",
"about_text": "Pwease wead cawefuwwy:\nSewvew won't wake up cuz too many pwocesses. cma setups fwom wonky tempwate, tweak config yuwsewf.\nAwso:\n 1. Fiwst waunch = site wink, not web app\n 2. Use nochess, den cma\n 3. Westawt wiff nochess -kill + nochess\nPwe-awpha, watew 1.0.0 if peopwe wike~. ",
"cma_start": "( ノ・ェ・ )ノ Making minyi-app in BotFather-chan...",
"cma_need_url": "Set URL fiwst owr wun .nochess, pwease~",
"cma_done": "(*˘︶˘*) Aww done, nya!",
"cma_error": "Oopsie woopsie: {}",
"RuntimeError": "inyine bot usewnyame nyot found",
"not_supported_platform": "(┬┬_┬┬) Unfowtunyatewy, can't instaww hewe.\n\n(〜^∇^)〜 Nyot an ewwow, don't contact suppowt."
}
async def client_ready(self):
platform = utils.get_named_platform()
if platform in ("HikkaHost"):
raise loader.LoadError(self.strings("not_supported_platform"))
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"serveo_subdomain",
"",
"Custom serveo subdomain (leave empty for random) | Кастомный поддомен serveo (оставь пустым для случайного)",
validator=loader.validators.String(),
),
loader.ConfigValue(
"mini_app_url",
None,
"Mini app direct url | Директ ссылка на ваше мини приложение",
validator=loader.validators.String(),
),
loader.ConfigValue(
"block_light",
"#D8E3E7",
"Light board block color | Цвет светлых полей на доске",
validator=loader.validators.String()
),
loader.ConfigValue("block_dark",
"#7699AF",
"Dark board block color | Цвет тёмных полей на доске",
validator=loader.validators.String()
),
loader.ConfigValue(
"select_block",
"#FF5A5A",
"Selected block color | Цвет для выделения полей на доске",
validator=loader.validators.String()
),
loader.ConfigValue(
"move_pieces_color",
"#58B4FF",
"Move highlight color | Цвет подсвечиваниях перехода на другую позицию",
validator=loader.validators.String()
),
loader.ConfigValue(
"result_win",
"#00BE16",
"Winner color | Блок цвета победителя",
validator=loader.validators.String()
),
loader.ConfigValue(
"result_lose",
"#BE0000",
"Loser color | Блок цвета проигравшего",
validator=loader.validators.String()
),
loader.ConfigValue(
"result_draw",
"#434343",
"Draw color | Блок цвета при ничьей",
validator=loader.validators.String()
),
loader.ConfigValue(
"arrow_color",
"#BD3667",
"Arrow color | Цвет стрелки",
validator=loader.validators.String()
),
)
self.runner = None
self.tunnel_url = None
self.access_token = None
self.games_cache = []
self.games_dump = ""
self._serveo_proc = None
self._assets_lock = asyncio.Lock()
self._assets_html = None
self._assets_css = None
self._assets_js = None
def theme_config_dict(self):
return {
"block_light": self.config["block_light"],
"block_dark": self.config["block_dark"],
"select_block": self.config["select_block"],
"move_pieces_color": self.config["move_pieces_color"],
"result_win": self.config["result_win"],
"result_lose": self.config["result_lose"],
"result_draw": self.config["result_draw"],
"arrow_color": self.config["arrow_color"],
}
async def refresh_games_cache(self):
chess = self.lookup("chess")
if not chess or not getattr(chess, "games", None):
self.games_cache = []
self.games_dump = ""
return
chunks = []
items = list(chess.games.items())
def sort_key(item):
key = str(item[0])
return (0, int(key)) if key.isdigit() else (1, key)
for _, game in sorted(items, key=sort_key, reverse=True):
node = None
if isinstance(game, dict):
game_obj = game.get("game", {})
if isinstance(game_obj, dict):
node = game_obj.get("root_node") or game_obj.get("node")
if node is None:
node = game.get("root_node") or game.get("node")
if node is None and hasattr(game, "game"):
game_obj = getattr(game, "game", None)
if isinstance(game_obj, dict):
node = game_obj.get("root_node") or game_obj.get("node")
if node is None and hasattr(game, "root_node"):
node = getattr(game, "root_node", None)
if node is None and hasattr(game, "node"):
node = getattr(game, "node", None)
if node:
chunks.append(str(node).strip())
self.games_cache = [x for x in chunks if x]
self.games_dump = "\n\n".join(self.games_cache)
async def get_me_json(self):
me = await self.client.get_me()
fallback_photo = "https://i.pinimg.com/736x/6e/0a/0c/6e0a0cf688b30ba9de81b81bb32e49f9.jpg"
full_name = (getattr(me, "first_name", "") or "") + (
(" " + getattr(me, "last_name", "")) if getattr(me, "last_name", None) else ""
)
return {
"id": getattr(me, "id", None),
"username": getattr(me, "username", None),
"first_name": getattr(me, "first_name", None),
"last_name": getattr(me, "last_name", None),
"name": full_name.strip() or str(getattr(me, "id", "Unknown")),
"photo": fallback_photo,
"enemy_photo": fallback_photo,
}
def check_access(self, request):
token = request.query.get("token") or request.cookies.get("nochess_token")
return bool(self.access_token and token == self.access_token)
def ensure_access_token(self):
if self.access_token:
return self.access_token
self.access_token = self.get("access_token")
if not self.access_token:
self.access_token = secrets.token_urlsafe(32)
self.set("access_token", self.access_token)
return self.access_token
async def read_remote_asset(self, url):
timeout = ClientTimeout(total=15)
async with ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
if response.status != 200:
raise RuntimeError(f"HTTP {response.status}: {url}")
return await response.text()
async def load_web_assets(self):
async with self._assets_lock:
if self._assets_html is not None:
return self._assets_html, self._assets_css, self._assets_js
html, css, js = await asyncio.gather(
self.read_remote_asset(html_raw),
self.read_remote_asset(css_raw),
self.read_remote_asset(js_raw),
)
self._assets_html = html
self._assets_css = css
self._assets_js = js
return html, css, js
def localication_script(self):
return (
""
)
def inject_runtime_config(self, html, css, js):
asset_root = asset_root_raw.rstrip("/")
if asset_root:
css = css.replace("url('bg.png')", f"url('{asset_root}/other/bg.png')")
theme_json = json.dumps(self.theme_config_dict(), ensure_ascii=False)
bootstrap = (
""
)
html = html.replace('', f"")
html = html.replace('', bootstrap + f"")
return html
async def handle_home(self, request):
try:
html, css, js = await self.load_web_assets()
except Exception as error:
return web.Response(
text=self.strings["asset_read_error"].format(utils.escape_html(str(error))),
status=500,
)
html = self.inject_runtime_config(html, css, js)
html = html.replace("