# requires: aiohttp pyngrok
# meta developer: @H_SunMods
# meta banner: https://r2.fakecrime.bio/uploads/965a3206-4609-4dff-beb0-6831f8b90e12.jpg
# current ver
__version__ = (0, 1, 0)
import json
import socket
import asyncio
import secrets
import logging
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from aiohttp import ClientSession, ClientTimeout, web
from herokutl.types import Message
from pyngrok import conf, ngrok
from .. import loader, utils
from ..inline.types import InlineCall
logging.getLogger("pyngrok").setLevel(logging.WARNING)
logging.getLogger("pyngrok.process").setLevel(logging.WARNING)
logging.getLogger("pyngrok.process.ngrok").setLevel(logging.WARNING)
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",
"ngrok_missing": "Set a ngrok_token",
"ngrok_error": "Ngrok start 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 не запущен",
"ngrok_missing": "Укажи ngrok_token",
"ngrok_error": "Ошибка запуска ngrok: {}",
"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(〜^∇^)〜 Это не ошибка, пожалуйста, не обращайтесь в поддержку."
}
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(
"ngrok_token",
None,
"Token from ngrok.com | Токен полученый на ngrok.com",
validator=loader.validators.Hidden(),
),
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 = ""
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):
html = await self.read_remote_asset(html_raw)
css = await self.read_remote_asset(css_raw)
js = await self.read_remote_asset(js_raw)
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("