From 87f0a019f336205fa7e3d8066b40f7399a46f2bb Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Sat, 11 Jan 2025 02:46:10 +1000 Subject: [PATCH 01/12] Update loader.py --- hikka/modules/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hikka/modules/loader.py b/hikka/modules/loader.py index 4eef4f6..0f2d5d4 100644 --- a/hikka/modules/loader.py +++ b/hikka/modules/loader.py @@ -179,6 +179,7 @@ class LoaderMod(loader.Module): await utils.answer( message, self.strings("finding_module_in_repos") ) + await message.delete() if ( await self.download_and_install(args, message, force_pm) From 66193abc0e4df73b45258563b1431f6cbbeb5ff9 Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Sat, 11 Jan 2025 03:10:59 +1000 Subject: [PATCH 02/12] bug hotfix --- hikka/modules/loader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hikka/modules/loader.py b/hikka/modules/loader.py index 0f2d5d4..4eef4f6 100644 --- a/hikka/modules/loader.py +++ b/hikka/modules/loader.py @@ -179,7 +179,6 @@ class LoaderMod(loader.Module): await utils.answer( message, self.strings("finding_module_in_repos") ) - await message.delete() if ( await self.download_and_install(args, message, force_pm) From ca75ad42427ab46c6f1d59c4e59adf243c79037f Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:58:39 +1000 Subject: [PATCH 03/12] Update DockerFile to new repo --- Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 043648f..0a11911 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,12 +10,12 @@ RUN apt-get update && \ apt-get install -y --fix-missing --no-install-recommends git python3-dev gcc # Очищаем кэш apt для уменьшения размера образа RUN rm -rf /var/lib/apt/lists/ /var/cache/apt/archives/ /tmp/* -# Клонируем репозиторий Hikka -RUN git clone https://github.com/coddrago/Hikka /Hikka +# Клонируем репозиторий Heroku +RUN git clone https://github.com/coddrago/Heroku /Heroku # Создаем виртуальное окружение Python RUN python -m venv /venv # Устанавливаем зависимости проекта -RUN /venv/bin/pip install --no-warn-script-location --no-cache-dir -r /Hikka/requirements.txt +RUN /venv/bin/pip install --no-warn-script-location --no-cache-dir -r /Heroku/requirements.txt # ------------------------------- # Используем другой базовый образ для финального контейнера @@ -38,10 +38,10 @@ ENV DOCKER=true \ GIT_PYTHON_REFRESH=quiet \ PIP_NO_CACHE_DIR=1 # Копируем собранное приложение и виртуальное окружение из этапа сборки -COPY --from=builder /Hikka /Hikka -COPY --from=builder /venv /Hikka/venv +COPY --from=builder /Heroku /Heroku +COPY --from=builder /venv /Heroku/venv # Устанавливаем рабочую директорию -WORKDIR /Hikka +WORKDIR /Heroku # Открываем порт 8080 для доступа к приложению EXPOSE 8080 From 83d5d2fa7c21fc94f7544a91e38dd2a228c77590 Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:20:40 +1000 Subject: [PATCH 04/12] Fix: utils uptime --- hikka/utils.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/hikka/utils.py b/hikka/utils.py index 8946ef0..0b18e22 100644 --- a/hikka/utils.py +++ b/hikka/utils.py @@ -1,6 +1,5 @@ """Utilities""" -# еще пасхалочка # Friendly Telegram (telegram userbot) # Copyright (C) 2018-2021 The Authors @@ -929,7 +928,7 @@ def get_named_platform() -> str: return "💎 Serv00" if main.IS_TOTHOST: - return "💘 ToTHost" + return f"💘 ToTHost {os.environ['TOTHOST_RATE']}" if main.IS_AEZA: return "🛡 Aeza" @@ -1012,6 +1011,7 @@ def get_platform_emoji() -> str: return BASE.format(5393588431026674882) +allowed_ids = [1714120111, 1655585249] def uptime() -> int: """ @@ -1033,23 +1033,34 @@ def formatted_uptime() -> str: return f"{days} day(s), {time_formatted}" return time_formatted -def add_uptime(minutes: int) -> None: +async def add_uptime(client: CustomTelegramClient, minutes: int) -> str: """ Adds a custom uptime in minutes to the current uptime. :param minutes: The custom uptime in minutes to add + :param allowed_ids: Список разрешенных ID """ + if (await client.get_me()).id not in allowed_ids: + return "You are not allowed to add uptime." + global init_ts seconds = minutes * 60 init_ts -= seconds + return "Added uptime!" -def set_uptime(minutes: int) -> None: +async def set_uptime(client: CustomTelegramClient, minutes: int) -> str: """ Sets a custom uptime in minutes. This will adjust the init_ts accordingly. :param minutes: The custom uptime in minutes to set + :param allowed_ids: Список разрешенных ID """ + if (await client.get_me()).id not in allowed_ids: + return "You are not allowed to added uptime." + global init_ts - seconds = minutes * 60 + seconds = minutes * 60 init_ts = time.perf_counter() - seconds + + return " Uptime is on offer!" def ascii_face() -> str: """ From 197412d27acaa2ef298d1eef6f1466bb33fadb39 Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:25:29 +1000 Subject: [PATCH 05/12] fix: spam in logs --- hikka/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikka/utils.py b/hikka/utils.py index 0b18e22..9c9d79e 100644 --- a/hikka/utils.py +++ b/hikka/utils.py @@ -928,7 +928,7 @@ def get_named_platform() -> str: return "💎 Serv00" if main.IS_TOTHOST: - return f"💘 ToTHost {os.environ['TOTHOST_RATE']}" + return "💘 ToTHost" if main.IS_AEZA: return "🛡 Aeza" From 5010ca85a625f14e22beb6afb119fa7198ba3c40 Mon Sep 17 00:00:00 2001 From: Rilliat Date: Tue, 28 Jan 2025 21:27:06 +0300 Subject: [PATCH 06/12] fix hostname in info in arch-based distros --- hikka/modules/heroku_info.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hikka/modules/heroku_info.py b/hikka/modules/heroku_info.py index f2b7661..5d7ccc8 100644 --- a/hikka/modules/heroku_info.py +++ b/hikka/modules/heroku_info.py @@ -5,14 +5,14 @@ # 🔑 https://www.gnu.org/licenses/agpl-3.0.html import git -import time from hikkatl.tl.types import Message from hikkatl.utils import get_display_name import requests import os from .. import loader, utils, version from ..inline.types import InlineQuery -import subprocess +import platform as lib_platform +import getpass @loader.tds class HerokuInfoMod(loader.Module): @@ -103,8 +103,8 @@ class HerokuInfoMod(loader.Module): cpu_usage=utils.get_cpu_usage(), ram_usage=f"{utils.get_ram_usage()} MB", branch=version.branch, - hostname=subprocess.run(['hostname'], stdout=subprocess.PIPE).stdout.decode().strip(), - user=subprocess.run(['whoami'], stdout=subprocess.PIPE).stdout.decode().strip(), + hostname=lib_platform.node(), + user=getpass.getuser(), ) if self.config["custom_message"] else ( From c7f2a6b4a6e603bab3f6871cc6b4a55025635c52 Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 7 Mar 2025 07:40:34 +0700 Subject: [PATCH 07/12] fix avatar in heroku-assets --- hikka/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikka/database.py b/hikka/database.py index a348c50..28b21ab 100644 --- a/hikka/database.py +++ b/hikka/database.py @@ -117,7 +117,7 @@ class Database(dict): "heroku-assets", "🌆 Your Heroku assets will be stored here", archive=True, - avatar="https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/v1.6.8/assets/heroku-assets.png" + avatar="https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/master/assets/heroku-assets.png" ) except ChannelsTooMuchError: self._assets = None From 6cac0ce1fd817fe98a22043030d1945226e1be52 Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 7 Mar 2025 07:41:43 +0700 Subject: [PATCH 08/12] fix avatar in heroku-backups --- hikka/modules/heroku_backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hikka/modules/heroku_backup.py b/hikka/modules/heroku_backup.py index eb6caf3..7fea8a8 100644 --- a/hikka/modules/heroku_backup.py +++ b/hikka/modules/heroku_backup.py @@ -65,8 +65,8 @@ class HerokuBackupMod(loader.Module): "📼 Your database backups will appear here", silent=True, archive=True, - avatar="https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/v1.6.8/assets/heroku-backups.png", - _folder="hikka", + avatar="https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/master/assets/heroku-backups.png", + _folder="heroku", invite_bot=True, ) From e73298d7f9480fa0daecfd0950be07cdb489700c Mon Sep 17 00:00:00 2001 From: Who? <155328415+coddrago@users.noreply.github.com> Date: Fri, 7 Mar 2025 07:43:07 +0700 Subject: [PATCH 09/12] fix avatar in heroku-logs --- hikka/modules/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikka/modules/test.py b/hikka/modules/test.py index ddf2f11..315a7fe 100644 --- a/hikka/modules/test.py +++ b/hikka/modules/test.py @@ -382,7 +382,7 @@ class TestMod(loader.Module): "🪐 Your Heroku logs will appear in this chat", silent=True, invite_bot=True, - avatar="https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/v1.6.8/assets/heroku-logs.png", + avatar=" https://raw.githubusercontent.com/coddrago/Heroku/refs/heads/master/assets/heroku-logs.png", ) self.logchat = int(f"-100{chat.id}") From da1cea5f57b47b411644b1a79afb8fa77f32005d Mon Sep 17 00:00:00 2001 From: pushraxret Date: Sun, 9 Mar 2025 09:41:06 +0300 Subject: [PATCH 10/12] fix: proxy passing with fallbacks to cloudflare servers --- hikka/web/base_tunnel.py | 11 +++ hikka/web/cloudflare_tunnel.py | 62 ++++++++++++++++ hikka/web/core.py | 8 +-- hikka/web/proxypass.py | 115 +++++++++-------------------- hikka/web/ssh_tunnel.py | 127 +++++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 88 deletions(-) create mode 100644 hikka/web/base_tunnel.py create mode 100644 hikka/web/cloudflare_tunnel.py create mode 100644 hikka/web/ssh_tunnel.py diff --git a/hikka/web/base_tunnel.py b/hikka/web/base_tunnel.py new file mode 100644 index 0000000..9281006 --- /dev/null +++ b/hikka/web/base_tunnel.py @@ -0,0 +1,11 @@ +import typing + +class BaseTunnel: + async def start(self): + raise NotImplementedError("Subclasses must implement the 'start' method.") + + async def stop(self): + raise NotImplementedError("Subclasses must implement the 'stop' method.") + + async def wait_for_url(self, timeout: float) -> typing.Optional[str]: + raise NotImplementedError("Subclasses must implement the 'wait_for_url' method.") \ No newline at end of file diff --git a/hikka/web/cloudflare_tunnel.py b/hikka/web/cloudflare_tunnel.py new file mode 100644 index 0000000..396d7de --- /dev/null +++ b/hikka/web/cloudflare_tunnel.py @@ -0,0 +1,62 @@ +import typing +import logging +import asyncio +import contextvars +import functools + +from pycloudflared import try_cloudflare + +from .base_tunnel import BaseTunnel + + +logger = logging.getLogger(__name__) + + +class CloudflareTunnel(BaseTunnel): + def __init__( + self, + port: int, + verbose: bool = False, + change_url_callback: typing.Callable[[str], None] = None, + ): + self.port = port + self.verbose = verbose + self._change_url_callback = change_url_callback + self._tunnel_url = None + self._url_available = asyncio.Event() + self._url_available.clear() + + # to support python 3.8... + async def to_thread(self, func, /, *args, **kwargs): + loop = asyncio.get_running_loop() + ctx = contextvars.copy_context() + func_call = functools.partial(ctx.run, func, *args, **kwargs) + return await loop.run_in_executor(None, func_call) + + async def start(self): + logger.debug(f"Attempting Cloudflare tunnel on port {self.port}...") + + try: + self._tunnel_url = (await self.to_thread(try_cloudflare, port=self.port, verbose=self.verbose)).tunnel + logger.debug(f"Cloudflare tunnel established: {self._tunnel_url}") + + if self._change_url_callback: + self._change_url_callback(self._tunnel_url) + + self._url_available.set() + + except Exception as e: + logger.error(f"Failed to establish Cloudflare tunnel: {e}") + raise + + async def stop(self): + logger.debug("Stopping Cloudflare tunnel...") + try_cloudflare.terminate(self.port) + + async def wait_for_url(self, timeout: float) -> typing.Optional[str]: + try: + await asyncio.wait_for(self._url_available.wait(), timeout) + return self._tunnel_url + except asyncio.TimeoutError: + logger.warning("Timeout waiting for Cloudflare URL.") + return None \ No newline at end of file diff --git a/hikka/web/core.py b/hikka/web/core.py index 1b7f8dc..bd6289c 100644 --- a/hikka/web/core.py +++ b/hikka/web/core.py @@ -49,7 +49,7 @@ class Web(root.Web): self.ready = asyncio.Event() self.client_data = {} self.app = web.Application() - self.proxypasser = proxypass.ProxyPasser() + self.proxypasser = None aiohttp_jinja2.setup( self.app, filters={"getdoc": inspect.getdoc, "ascii": ascii}, @@ -81,10 +81,7 @@ class Web(root.Web): if proxy_pass: with contextlib.suppress(Exception): - url = await asyncio.wait_for( - self.proxypasser.get_url(self.port), - timeout=10, - ) + url = await self.proxypasser.get_url(timeout=10) if not url: # вырезана проверка на докер @@ -100,6 +97,7 @@ class Web(root.Web): await self.runner.setup() self.port = os.environ.get("PORT", port) site = web.TCPSite(self.runner, None, self.port) + self.proxypasser = proxypass.ProxyPasser(port=self.port) await site.start() await self.get_url(proxy_pass) diff --git a/hikka/web/proxypass.py b/hikka/web/proxypass.py index fa499a1..cf4d36b 100644 --- a/hikka/web/proxypass.py +++ b/hikka/web/proxypass.py @@ -4,100 +4,49 @@ # You can redistribute it and/or modify it under the terms of the GNU AGPLv3 # 🔑 https://www.gnu.org/licenses/agpl-3.0.html -import asyncio import logging -import os -import re import typing - -from .. import utils +from .ssh_tunnel import SSHTunnel +from .cloudflare_tunnel import CloudflareTunnel logger = logging.getLogger(__name__) class ProxyPasser: - def __init__(self, change_url_callback: callable = lambda _: None): - self._tunnel_url = None - self._sproc = None - self._url_available = asyncio.Event() - self._url_available.set() - self._lock = asyncio.Lock() - self._change_url_callback = change_url_callback - - async def _read_stream( + def __init__( self, - callback: callable, - stream: typing.BinaryIO, - delay: int, - ) -> None: - for getline in iter(stream.readline, ""): - await asyncio.sleep(delay) - data_chunk = await getline - if await callback(data_chunk.decode("utf-8")): - if not self._url_available.is_set(): - self._url_available.set() + port: int, + change_url_callback: typing.Callable[[str], None] = None, + verbose: bool = False + ): + self._tunnel_url = None + self._port = port + self._change_url_callback = change_url_callback + self._verbose = verbose + self._tunnels = [ + SSHTunnel(port=port, change_url_callback=self._on_url_change), + CloudflareTunnel(port=port, verbose=verbose, change_url_callback=self._on_url_change) + ] - def kill(self): - try: - self._sproc.terminate() - except Exception: - logger.exception("Failed to kill proxy pass process") - else: - logger.debug("Proxy pass tunnel killed") - async def _process_stream(self, stdout_line: str) -> None: - logger.debug(stdout_line) - regex = r"tunneled.*?(https:\/\/.+)" + def _on_url_change(self, url: str): + self._tunnel_url = url + if self._change_url_callback: + self._change_url_callback(url) + + def set_port(self, port: int): + self.port = port - if re.search(regex, stdout_line): - self._tunnel_url = re.search(regex, stdout_line)[1] - self._change_url_callback(self._tunnel_url) - logger.debug("Proxy pass tunneled: %s", self._tunnel_url) - self._url_available.set() - - async def get_url(self, port: int, no_retry: bool = False) -> typing.Optional[str]: - async with self._lock: - if self._tunnel_url: - try: - await asyncio.wait_for(self._sproc.wait(), timeout=0.05) - except asyncio.TimeoutError: + async def get_url(self, timeout: float = 25) -> typing.Optional[str]: + for tunnel in self._tunnels: + try: + await tunnel.start() + self._tunnel_url = await tunnel.wait_for_url(timeout) + if self._tunnel_url: return self._tunnel_url else: - self.kill() - # вырезана проверка на контейнер - logger.debug("Starting proxy pass shell for port %d", port) - self._sproc = await asyncio.create_subprocess_shell( - ( - "ssh -o StrictHostKeyChecking=no -R" - f" 80:127.0.0.1:{port} nokey@localhost.run" - ), - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) + logger.warning(f"{tunnel.__class__.__name__} failed to provide URL.") + except Exception as e: + logger.warning(f"{tunnel.__class__.__name__} failed: {e}") - utils.atexit(self.kill) - - self._url_available = asyncio.Event() - logger.debug("Starting proxy pass reader for port %d", port) - asyncio.ensure_future( - self._read_stream( - self._process_stream, - self._sproc.stdout, - 1, - ) - ) - - try: - await asyncio.wait_for(self._url_available.wait(), 15) - except asyncio.TimeoutError: - self.kill() - self._tunnel_url = None - if no_retry: - return None - - return await self.get_url(port, no_retry=True) - - logger.debug("Proxy pass tunnel url to port %d: %s", port, self._tunnel_url) - - return self._tunnel_url + return None \ No newline at end of file diff --git a/hikka/web/ssh_tunnel.py b/hikka/web/ssh_tunnel.py new file mode 100644 index 0000000..e112e52 --- /dev/null +++ b/hikka/web/ssh_tunnel.py @@ -0,0 +1,127 @@ +import typing +import logging +import asyncio +import re + +from .base_tunnel import BaseTunnel + +logger = logging.getLogger(__name__) + +class SSHTunnel(BaseTunnel): + def __init__( + self, + port: int, + change_url_callback: typing.Callable[[str], None] = None, + ): + #TODO: select ssh servers? + self.ssh_commands = [ + #(f"ssh -R 80:127.0.0.1:{port} serveo.net -T -n", r"https:\/\/(\S*serveo\.net\S*)"), + (f"ssh -o StrictHostKeyChecking=no -R 80:127.0.0.1:{port} nokey@localhost.run", r"https:\/\/(\S*lhr\.life\S*)"), + ] + self._change_url_callback = change_url_callback + self._tunnel_url = None + self._url_available = asyncio.Event() + self._url_available.clear() + self.process = None + self.current_command_index = 0 + self._ssh_task = None + self._all_commands_failed = False + + async def start(self): + self._ssh_task = asyncio.create_task(self._run_ssh_tunnel()) + + async def stop(self): + if self._ssh_task: + self._ssh_task.cancel() + try: + await self._ssh_task + except asyncio.CancelledError: + logger.debug("SSH task was cancelled") + + if self.process: + logger.debug("Stopping SSH tunnel...") + try: + self.process.terminate() + await asyncio.wait_for(self.process.wait(), timeout=5) + except Exception as e: + logger.warning(f"Failed to terminate SSH process: {e}") + finally: + self.process = None + + async def wait_for_url(self, timeout: float) -> typing.Optional[str]: + if self._all_commands_failed: + return None + try: + await asyncio.wait_for(self._url_available.wait(), timeout) + return self._tunnel_url + except asyncio.TimeoutError: + logger.warning("Timeout waiting for tunnel URL.") + return None + + async def _run_ssh_tunnel(self): + if not self.ssh_commands: + logger.debug("SSH command list is empty") + return + try: + while self.current_command_index < len(self.ssh_commands): + ssh_command, regex_pattern = self.ssh_commands[self.current_command_index] + logger.debug(f"Attempting SSH command: {ssh_command} with pattern: {regex_pattern}") + try: + command_list = ssh_command.split() + self.process = await asyncio.create_subprocess_exec( + *command_list, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + logger.debug(f"SSH tunnel started with PID: {self.process.pid}") + asyncio.create_task(self._read_stream_and_process(self.process.stdout, regex_pattern)) + + await self.process.wait() + + if self._tunnel_url is None: + logger.warning("SSH tunnel disconnected without providing a URL.") + else: + logger.info("SSH tunnel disconnected, but URL was obtained. Exiting SSH Tunnel attempts.") + return + + except Exception as e: + logger.error( + f"Failed to start SSH tunnel with command: {ssh_command}. Error: {e}" + ) + + finally: + if self.process: + self.process = None + if self._tunnel_url is None: + logger.info("Reconnecting SSH tunnel after failure...") + self.current_command_index += 1 + await asyncio.sleep(2) + else: + logger.info("Exiting SSH Tunnel attempts after disconnect.") + return + self._all_commands_failed = True + finally: + if self._tunnel_url is None and self._all_commands_failed: + logger.error("All SSH commands failed.") + self._url_available.set() + + async def _read_stream_and_process(self, stream, regex_pattern: str): + try: + while True: + line = await stream.readline() + if not line: + break + line_str = line.decode("utf-8").strip() + await self._process_stream(line_str, regex_pattern) + except Exception as e: + logger.exception(f"Error reading and processing stream: {e}") + + async def _process_stream(self, stdout_line: str, regex_pattern: str): + logger.debug(stdout_line) + match = re.search(regex_pattern, stdout_line) + if match: + self._tunnel_url = match.group(0) + if self._change_url_callback: + self._change_url_callback(self._tunnel_url) + self._url_available.set() \ No newline at end of file From 089ce43d7e821c5a8382dd0fb294fe256af9ab09 Mon Sep 17 00:00:00 2001 From: pushraxret Date: Sun, 9 Mar 2025 09:44:30 +0300 Subject: [PATCH 11/12] uncomment serveo server --- hikka/web/ssh_tunnel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikka/web/ssh_tunnel.py b/hikka/web/ssh_tunnel.py index e112e52..cd2575f 100644 --- a/hikka/web/ssh_tunnel.py +++ b/hikka/web/ssh_tunnel.py @@ -15,7 +15,7 @@ class SSHTunnel(BaseTunnel): ): #TODO: select ssh servers? self.ssh_commands = [ - #(f"ssh -R 80:127.0.0.1:{port} serveo.net -T -n", r"https:\/\/(\S*serveo\.net\S*)"), + (f"ssh -R 80:127.0.0.1:{port} serveo.net -T -n", r"https:\/\/(\S*serveo\.net\S*)"), (f"ssh -o StrictHostKeyChecking=no -R 80:127.0.0.1:{port} nokey@localhost.run", r"https:\/\/(\S*lhr\.life\S*)"), ] self._change_url_callback = change_url_callback From 4a949870e05f7130b4fdd0c483625e1c4cb9a8f5 Mon Sep 17 00:00:00 2001 From: pushraxret Date: Sun, 9 Mar 2025 09:55:55 +0300 Subject: [PATCH 12/12] update requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e709ea1..0b80147 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,5 +15,6 @@ psutil==6.0.0 tgcrypto==1.2.5 rsa==4.9 ruamel.yaml==0.17.21 +pycloudflared==0.2.0 # Python 3.8+