From ea8e38061e6a4ff98dc8f8aa8df72eec16e2c38b Mon Sep 17 00:00:00 2001 From: Hiddify <114227601+hiddify-com@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:42:04 +0000 Subject: [PATCH] v1 async --- hiddifypanel_bot/basebot.py | 275 ------------------ hiddifypanel_bot/basebot/__init__.py | 5 + hiddifypanel_bot/basebot/bot.py | 43 +++ .../basebot/call_action_filter.py | 15 + .../basebot/custom_message_types.py | 19 ++ hiddifypanel_bot/basebot/data_storage.py | 34 +++ hiddifypanel_bot/basebot/middleware.py | 126 ++++++++ hiddifypanel_bot/basebot/role_filter.py | 18 ++ hiddifypanel_bot/basebot/roles.py | 10 + hiddifypanel_bot/basebot/storage_filter.py | 34 +++ hiddifypanel_bot/cli.py | 3 + hiddifypanel_bot/hiddifyapi/api.py | 48 +-- hiddifypanel_bot/modules/admin/add_user.py | 78 ++--- hiddifypanel_bot/modules/admin/search_user.py | 8 +- hiddifypanel_bot/modules/admin/welcome.py | 10 +- hiddifypanel_bot/modules/guest/invalid.py | 14 +- hiddifypanel_bot/modules/guest/welcome.py | 4 +- hiddifypanel_bot/modules/user/welcome.py | 22 +- hiddifypanel_bot/utils/tghelper.py | 8 +- requirements.txt | 3 +- 20 files changed, 414 insertions(+), 363 deletions(-) delete mode 100644 hiddifypanel_bot/basebot.py create mode 100644 hiddifypanel_bot/basebot/__init__.py create mode 100644 hiddifypanel_bot/basebot/bot.py create mode 100644 hiddifypanel_bot/basebot/call_action_filter.py create mode 100644 hiddifypanel_bot/basebot/custom_message_types.py create mode 100644 hiddifypanel_bot/basebot/data_storage.py create mode 100644 hiddifypanel_bot/basebot/middleware.py create mode 100644 hiddifypanel_bot/basebot/role_filter.py create mode 100644 hiddifypanel_bot/basebot/roles.py create mode 100644 hiddifypanel_bot/basebot/storage_filter.py diff --git a/hiddifypanel_bot/basebot.py b/hiddifypanel_bot/basebot.py deleted file mode 100644 index 0e823fa..0000000 --- a/hiddifypanel_bot/basebot.py +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/python - -import telebot -import uuid -import logging -from .hiddifyapi.api import HiddifyApi -from telebot import TeleBot, ExceptionHandler, custom_filters,types -from telebot.storage import StateMemoryStorage -from telebot.types import Message -from telebot import handler_backends -import os -from dotenv import load_dotenv -from enum import Enum - -load_dotenv() -logger = telebot.logger -telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console. - -HIDDIFYPANEL_USER_LINK = os.getenv("HIDDIFYPANEL_USER_LINK") -HIDDIFYPANEL_ADMIN_LINK = os.getenv("HIDDIFYPANEL_ADMIN_LINK") -BOT_TOKEN = os.getenv("BOT_TOKEN") - -state_storage = StateMemoryStorage() # you can init here another storage - - - -class Role(Enum): - SUPER_ADMIN = "super_admin" - ADMIN = "admin" - AGENT = "agent" - USER = "user" - UNKNOWN="unknown" - - -class MyExceptionHandler(ExceptionHandler): - def handle(self, exception): - logger.error(exception) - - -bot: TeleBot = TeleBot( - BOT_TOKEN, - exception_handler=MyExceptionHandler(), - use_class_middlewares=True, - state_storage=state_storage, -) - -bot.remove_webhook() - - -class DataStorage: - def __init__(self, user_id, chat_id): - self.user_id = user_id - self.chat_id = chat_id - if not self.state: # make sure that data exist - self.state="init" - - @property - def state(self)->str: - return bot.get_state(self.user_id, self.chat_id) - @state.setter - def state(self, state: str): - bot.set_state(self.user_id, state, self.chat_id) - - - def __getitem__(self, key): - """Retrieve a value from the data storage by key using the [] syntax.""" - with bot.retrieve_data(self.user_id, self.chat_id) as storage: - if not storage: - return None - return storage.get(key) - def all(self): - with bot.retrieve_data(self.user_id, self.chat_id) as storage: - return storage - - def __setitem__(self, key, value): - """Set a value in the data storage by key using the [] syntax.""" - bot.add_data(self.user_id, self.chat_id, **{key:value}) - def set(self,**kwargs): - bot.add_data(self.user_id, self.chat_id, **kwargs) - -class BaseHMessage: - hapi: HiddifyApi - db: DataStorage - chat_id: int - user_id: int - role:Role - lang: str - -class HMessage(types.Message,BaseHMessage): - pass - -class HCallbackQuery(types.CallbackQuery): - message:HMessage - -class HInlineQuery(types.InlineQuery,BaseHMessage): - pass - -class Middleware(handler_backends.BaseMiddleware): - def __init__(self): - self.update_types = ['message', 'edited_message', 'channel_post', 'edited_channel_post', 'inline_query', 'chosen_inline_result', 'callback_query', 'shipping_query', 'pre_checkout_query', 'poll', 'poll_answer', 'my_chat_member', 'chat_member', 'chat_join_request', 'message_reaction', 'message_reaction_count', 'chat_boost', 'removed_chat_boost', 'business_connection', 'business_message', 'edited_business_message', 'deleted_business_messages'] - pass - def set_basic_elements(self,obj, data): - user_id=chat_id=deflang=None - base=obj - if isinstance(obj, types.CallbackQuery): - if not obj.message: - return - base=obj.message - - chat_id = base.chat.id - user_id = obj.from_user.id - deflang=base.from_user.language_code - elif isinstance(obj, types.InlineQuery): - user_id=obj.from_user.id - deflang=obj.from_user.language_code - base=obj - else: - base=obj - chat_id = base.chat.id - user_id = base.from_user.id - deflang=base.from_user.language_code - base.chat_id=chat_id - base.user_id=user_id - # data['lang']="en" - base.db=db=DataStorage(user_id, chat_id) - lang=db['lang'] - if not lang: - lang=db['lang']=deflang - base.lang=lang - base.role=db['role'] - self.set_hapi(base) - return base - def set_user_data(self,base): - db=base.db - if isinstance(base,Message) and base.text and base.text.startswith("/start"): - params = base.text.split() - try: - if len(params)>=2 and "admin" in params[1]: - uid = params[1].split("_")[1] - hapi = HiddifyApi(HIDDIFYPANEL_ADMIN_LINK, uid) - - db['info']=hapi.get_admin_info() - db['guid']=uid - role=db['info']['mode'] - if role=='super_admin':role=Role.SUPER_ADMIN - if role=='admin':role=Role.ADMIN - if role=='agent':role=Role.AGENT - db['admin_link']=f'{HIDDIFYPANEL_ADMIN_LINK.lstrip("/")}/{uid}/' - base.role=db['role']=role - elif len(params)>=2: - uid=params[1] - base.hapi = HiddifyApi(HIDDIFYPANEL_USER_LINK, uid) - db['info']=base.hapi.get_user_info() - db['guid']=uid - base.role=db['role'] = Role.USER - except Exception as e: - logger.error(e) - db['guid']="" - base.role=db['role'] = Role.UNKNOWN - self.set_hapi(base) - def set_hapi(self,base): - if base.role in [Role.SUPER_ADMIN,Role.ADMIN,Role.AGENT]: - base.hapi = HiddifyApi(HIDDIFYPANEL_ADMIN_LINK, base.db['guid']) - else: - base.hapi = HiddifyApi(HIDDIFYPANEL_USER_LINK, base.db['guid']) - - def pre_process(self, obj, data): - base=self.set_basic_elements(obj,data) - if not base:return - self.set_user_data(base) - - if base.chat_id: - bot.send_chat_action(base.chat_id,"typing") - if isinstance(obj,Message): - from .utils import tghelper - tghelper.set_reaction(base) - - def post_process(self, message, data, exception): - pass - - -class StorageFilter(custom_filters.AdvancedCustomFilter): - """ - Filter to check Text message. - - .. code-block:: python3 - :caption: Example on using this filter: - - @bot.message_handler(text=['account']) - # your function - """ - - key = 'db' - - def check(self, obj, val): - """ - :meta private: - """ - if isinstance(obj,Message): - message=obj - elif hasattr(obj,'message'): - message=obj.message - else: - return - if not isinstance(val, dict): - raise ValueError("Invalid Usage") - - storage=message.db - for k,v in val.items(): - l=v if isinstance(v, list) else [v] - data=storage[k] - for filter in l: - if isinstance(filter,custom_filters.SimpleCustomFilter): - if filter.check(data): - return True - else: - if isinstance(filter,list) or isinstance(filter,set): - return data in filter - else: - return data==filter - return False - - -class RoleFilter(StorageFilter): - """ - Filter to check Text message. - - .. code-block:: python3 - :caption: Example on using this filter: - - @bot.message_handler(text=['account']) - # your function - """ - - key = 'role' - - def check(self, obj, val): - data={Role.USER} - if val==Role.SUPER_ADMIN: - data={Role.SUPER_ADMIN} - elif val==Role.ADMIN: - data={Role.SUPER_ADMIN,Role.ADMIN} - elif val==Role.AGENT: - data={Role.SUPER_ADMIN,Role.ADMIN,Role.AGENT} - return super().check(obj,{"role":data}) - - - -class CallbackMatchFilter(custom_filters.AdvancedCustomFilter): - - - key = 'call_action' - - def check(self, obj, val): - if not isinstance(obj,types.CallbackQuery): - return - if not obj.data:return - action= obj.data.split(":")[0] - if isinstance(val ,list) or isinstance(val ,set): - return action in val - return action==val - -def callback(func=None,**kwargs): - if func is None: func=lambda call:True - return bot.callback_query_handler_orig(func,**kwargs) -bot.callback_query_handler_orig=bot.callback_query_handler -bot.callback_query_handler=callback - -bot.add_custom_filter(custom_filters.StateFilter(bot)) -bot.add_custom_filter(StorageFilter()) -bot.add_custom_filter(custom_filters.TextMatchFilter()) -bot.add_custom_filter(CallbackMatchFilter()) -bot.add_custom_filter(RoleFilter()) - -bot.setup_middleware(Middleware()) diff --git a/hiddifypanel_bot/basebot/__init__.py b/hiddifypanel_bot/basebot/__init__.py new file mode 100644 index 0000000..07f2911 --- /dev/null +++ b/hiddifypanel_bot/basebot/__init__.py @@ -0,0 +1,5 @@ +from .roles import Role +from .data_storage import DataStorage +from . import middleware +from .bot import bot +from .custom_message_types import HCallbackQuery,HMessage,HInlineQuery \ No newline at end of file diff --git a/hiddifypanel_bot/basebot/bot.py b/hiddifypanel_bot/basebot/bot.py new file mode 100644 index 0000000..b3761d3 --- /dev/null +++ b/hiddifypanel_bot/basebot/bot.py @@ -0,0 +1,43 @@ +import logging +import os +from telebot.async_telebot import AsyncTeleBot,logger +from telebot import TeleBot, ExceptionHandler,types +from telebot import asyncio_filters +from telebot.asyncio_storage import StateMemoryStorage +from .storage_filter import StorageFilter +from .call_action_filter import CallActionFilter +from .role_filter import RoleFilter +from .middleware import Middleware +logger.setLevel(logging.INFO) # Outputs debug messages to console. + + +BOT_TOKEN = os.getenv("BOT_TOKEN") + +state_storage = StateMemoryStorage() # you can init here another storage + + +class MyExceptionHandler(ExceptionHandler): + def handle(self, exception): + logger.error(exception) + raise exception + +bot: AsyncTeleBot = AsyncTeleBot( + BOT_TOKEN, + # exception_handler=MyExceptionHandler(), + + state_storage=state_storage, +) + +def callback(func=None,**kwargs): + if func is None: func=lambda call:True + return bot.callback_query_handler_orig(func,**kwargs) +bot.callback_query_handler_orig=bot.callback_query_handler +bot.callback_query_handler=callback + + +bot.add_custom_filter(asyncio_filters.StateFilter(bot)) +bot.add_custom_filter(asyncio_filters.TextMatchFilter()) +bot.add_custom_filter(StorageFilter()) +bot.add_custom_filter(CallActionFilter()) +bot.add_custom_filter(RoleFilter()) +bot.setup_middleware(Middleware(bot)) \ No newline at end of file diff --git a/hiddifypanel_bot/basebot/call_action_filter.py b/hiddifypanel_bot/basebot/call_action_filter.py new file mode 100644 index 0000000..799dc7c --- /dev/null +++ b/hiddifypanel_bot/basebot/call_action_filter.py @@ -0,0 +1,15 @@ +from telebot import asyncio_filters,types +class CallActionFilter(asyncio_filters.AdvancedCustomFilter): + + + key = 'call_action' + + async def check(self, obj, val): + if not isinstance(obj,types.CallbackQuery): + return + if not obj.data:return + action= obj.data.split(":")[0] + if isinstance(val ,list) or isinstance(val ,set): + return action in val + return action==val + diff --git a/hiddifypanel_bot/basebot/custom_message_types.py b/hiddifypanel_bot/basebot/custom_message_types.py new file mode 100644 index 0000000..313287c --- /dev/null +++ b/hiddifypanel_bot/basebot/custom_message_types.py @@ -0,0 +1,19 @@ +from telebot import types +from . import DataStorage,Role +from hiddifypanel_bot.hiddifyapi import HiddifyApi +class BaseHMessage: + hapi: "HiddifyApi" + db: DataStorage + chat_id: int + user_id: int + role:Role + lang: str + +class HMessage(types.Message,BaseHMessage): + pass + +class HCallbackQuery(types.CallbackQuery): + message:HMessage + +class HInlineQuery(types.InlineQuery,BaseHMessage): + pass diff --git a/hiddifypanel_bot/basebot/data_storage.py b/hiddifypanel_bot/basebot/data_storage.py new file mode 100644 index 0000000..0c09208 --- /dev/null +++ b/hiddifypanel_bot/basebot/data_storage.py @@ -0,0 +1,34 @@ + +class DataStorage: + def __init__(self, bot, user_id, chat_id): + self.user_id = user_id + self.chat_id = chat_id + self.bot=bot + + + async def get_state(self) -> str: + return await self.bot.get_state(self.user_id, self.chat_id) + + async def set_state(self, state: str): + await self.bot.set_state(self.user_id, state, self.chat_id) + + async def __getitem__(self, key,defv=None): + async with self.bot.retrieve_data(self.user_id, self.chat_id) as storage: + if not storage: + return defv + return storage.get(key,defv) + + async def all(self): + async with self.bot.retrieve_data(self.user_id, self.chat_id) as storage: + return storage + + async def get(self, key,defv=None): + return self.__getitem__(key,defv) + + async def __setitem__(self, key, value): + await self.bot.add_data(self.user_id, self.chat_id, **{key: value}) + + async def set(self, k=None, v=None,**kwargs): + if k is not None: + kwargs[k]=v + await self.bot.add_data(self.user_id, self.chat_id, **kwargs) \ No newline at end of file diff --git a/hiddifypanel_bot/basebot/middleware.py b/hiddifypanel_bot/basebot/middleware.py new file mode 100644 index 0000000..7c8f01f --- /dev/null +++ b/hiddifypanel_bot/basebot/middleware.py @@ -0,0 +1,126 @@ +from asyncio.log import logger +import os +from telebot import asyncio_handler_backends +from telebot import types +from .data_storage import DataStorage +from .roles import Role +from hiddifypanel_bot.hiddifyapi import HiddifyApi + +class Middleware(asyncio_handler_backends.BaseMiddleware): + def __init__(self, bot): + self.bot = bot + self.update_types = [ + "message", + "edited_message", + "channel_post", + "edited_channel_post", + "inline_query", + "chosen_inline_result", + "callback_query", + "shipping_query", + "pre_checkout_query", + "poll", + "poll_answer", + "my_chat_member", + "chat_member", + "chat_join_request", + "message_reaction", + "message_reaction_count", + "chat_boost", + "removed_chat_boost", + "business_connection", + "business_message", + "edited_business_message", + "deleted_business_messages", + ] + + async def set_basic_elements(self, obj, data): + user_id = chat_id = deflang = None + base = obj + if isinstance(obj, types.CallbackQuery): + if not obj.message: + return + base = obj.message + + chat_id = base.chat.id + user_id = obj.from_user.id + deflang = base.from_user.language_code + elif isinstance(obj, types.InlineQuery): + user_id = obj.from_user.id + deflang = obj.from_user.language_code + base = obj + else: + base = obj + chat_id = base.chat.id + user_id = base.from_user.id + deflang = base.from_user.language_code + base.chat_id = chat_id + base.user_id = user_id + # data['lang']="en" + base.db = db = DataStorage(self.bot, user_id, chat_id) + if not await db.get_state():await db.set_state("init") + lang = await db["lang"] + if not lang: + await db.set("lang", deflang) + lang =deflang + base.lang = lang + base.role = await db["role"] + base.hapi = HiddifyApi(await base.db["guid"]) + return base + + async def set_user_data(self, base): + db = base.db + if isinstance(base, types.Message) and base.text and base.text.startswith("/start"): + params = base.text.split() + try: + if len(params) >= 2 and "admin" in params[1]: + uid = params[1].split("_")[1] + hapi = HiddifyApi(uid) + info=hapi.get_admin_info() + role = info["mode"] + if role == "super_admin": + role = Role.SUPER_ADMIN + if role == "admin": + role = Role.ADMIN + if role == "agent": + role = Role.AGENT + + await db.set(info = info, + guid=uid, + admin_link = hapi.get_admin_link(), + role=role) + + + elif len(params) >= 2: + uid = params[1] + base.hapi = HiddifyApi(uid) + await db.set( + info = base.hapi.get_user_info(), + guid = uid, + role=Role.USER) + base.role = Role.USER + except Exception as e: + logger.error(e) + + await db.set(role = Role.UNKNOWN,guid="") + + base.hapi = HiddifyApi(await base.db["guid"]) + + + async def pre_process(self, obj, data): + base = await self.set_basic_elements(obj, data) + if not base: + return + await self.set_user_data(base) + + if base.chat_id: + await self.bot.send_chat_action(base.chat_id, "typing") + if isinstance(obj, types.Message): + from hiddifypanel_bot.utils import tghelper + + await tghelper.set_reaction(base) + + async def post_process(self, message, data, exception): + pass + + # Update other methods in Middleware to be async as well diff --git a/hiddifypanel_bot/basebot/role_filter.py b/hiddifypanel_bot/basebot/role_filter.py new file mode 100644 index 0000000..27b3ca7 --- /dev/null +++ b/hiddifypanel_bot/basebot/role_filter.py @@ -0,0 +1,18 @@ +from telebot import asyncio_filters,types +from .storage_filter import StorageFilter +from . import Role +class RoleFilter(StorageFilter): + + key = 'role' + + async def check(self, obj, val): + data={Role.USER} + if val==Role.SUPER_ADMIN: + data={Role.SUPER_ADMIN} + elif val==Role.ADMIN: + data={Role.SUPER_ADMIN,Role.ADMIN} + elif val==Role.AGENT: + data={Role.SUPER_ADMIN,Role.ADMIN,Role.AGENT} + return await super().check(obj,{"role":data}) + + diff --git a/hiddifypanel_bot/basebot/roles.py b/hiddifypanel_bot/basebot/roles.py new file mode 100644 index 0000000..a0c3585 --- /dev/null +++ b/hiddifypanel_bot/basebot/roles.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class Role(Enum): + SUPER_ADMIN = "super_admin" + ADMIN = "admin" + AGENT = "agent" + USER = "user" + UNKNOWN="unknown" + diff --git a/hiddifypanel_bot/basebot/storage_filter.py b/hiddifypanel_bot/basebot/storage_filter.py new file mode 100644 index 0000000..460089b --- /dev/null +++ b/hiddifypanel_bot/basebot/storage_filter.py @@ -0,0 +1,34 @@ +from telebot import asyncio_filters,types +from . import bot +class StorageFilter(asyncio_filters.AdvancedCustomFilter): + + key = 'db' + + async def check(self, obj, val): + """ + :meta private: + """ + if isinstance(obj,types.Message): + message=obj + elif hasattr(obj,'message'): + message=obj.message + else: + return + if not isinstance(val, dict): + raise ValueError("Invalid Usage") + + storage=message.db + for k,v in val.items(): + l=v if isinstance(v, list) else [v] + data=await storage[k] + for filter in l: + if isinstance(filter,asyncio_filters.SimpleCustomFilter): + if filter.check(data): + return True + else: + if isinstance(filter,list) or isinstance(filter,set): + return data in filter + else: + return data==filter + return False + diff --git a/hiddifypanel_bot/cli.py b/hiddifypanel_bot/cli.py index 0b966b6..452217e 100644 --- a/hiddifypanel_bot/cli.py +++ b/hiddifypanel_bot/cli.py @@ -11,6 +11,9 @@ from . import utils from . import basebot def main(): # pragma: no cover + from dotenv import load_dotenv + load_dotenv() + utils.setup_translation() # import i18n # print(i18n.t("start",locale='fa')) diff --git a/hiddifypanel_bot/hiddifyapi/api.py b/hiddifypanel_bot/hiddifyapi/api.py index 17b283f..3706091 100644 --- a/hiddifypanel_bot/hiddifyapi/api.py +++ b/hiddifypanel_bot/hiddifyapi/api.py @@ -1,4 +1,5 @@ from __future__ import annotations +import os import requests from datetime import datetime @@ -16,33 +17,44 @@ class HiddifyApiError(Exception): """Custom exception for HiddifyApi errors.""" -class HiddifyApi: - def __init__(self, api_url: str, api_key: str): - self.base_url: str = api_url.rstrip("/") +HIDDIFYPANEL_USER_LINK = os.getenv("HIDDIFYPANEL_USER_LINK").rstrip("/") +HIDDIFYPANEL_ADMIN_LINK = os.getenv("HIDDIFYPANEL_ADMIN_LINK").rstrip("/") + +class HiddifyApi: + def __init__(self, api_key: str): self.api_key = api_key requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) - def _make_request(self, method: str, endpoint: str, api_key=None, **kwargs) -> requests.Response: + def get_admin_link(self): + return f"{HIDDIFYPANEL_ADMIN_LINK}/{self.api_key}/" + + def get_user_link(self): + return f"{HIDDIFYPANEL_USER_LINK}/{self.api_key}/" + + def _make_request(self, method: str, endpoint: str, api_key=None, admin=True, **kwargs) -> requests.Response: """Make an HTTP request to the API.""" - url = f"{self.base_url}/{endpoint.strip('/')}/" + if admin: + url = f"{HIDDIFYPANEL_ADMIN_LINK}/{endpoint.strip('/')}/" + else: + url = f"{HIDDIFYPANEL_USER_LINK}/{endpoint.strip('/')}/" try: response = requests.request(method, url, headers={"HIDDIFY-API-KEY": api_key or self.api_key}, verify=False, **kwargs) response.raise_for_status() - return response + return response.json() except requests.RequestException as e: raise HiddifyApiError(f"Error in API request: {e}") from e def get_system_status(self) -> SystemStatus: """Get the system status.""" response = self._make_request("GET", "api/v2/admin/server_status/") - data = response.json() + data = response return SystemStatus(stats=data.get("stats", {}), usage_history=data.get("usage_history", {})) def get_admin_list(self) -> List[Dict[str, Union[str, int]]]: """Get the list of admin users.""" response = self._make_request("GET", "api/v2/admin/admin_user/") - return response.json() + return response def delete_admin_user(self, uuid: str) -> bool: """Delete an admin user.""" @@ -54,21 +66,21 @@ def add_user(self, user: User) -> User: def get_user_info(self, uuid: str = None) -> UserInfo: """Get detailed user information.""" - response = self._make_request("GET", f"api/v2/user/me", api_key=uuid) - return UserInfo(**response.json()) + response = self._make_request("GET", f"api/v2/user/me", api_key=uuid, admin=False) + return UserInfo(**response) def update_my_user(self, data: User): - return self._make_request("PATCH", f"api/v2/user/", json=data) + return self._make_request("PATCH", f"api/v2/user/", json=data, admin=False) def get_admin_info(self, uuid: str = None) -> AdminInfo: """Get detailed user information.""" response = self._make_request("GET", f"api/v2/admin/me", api_key=uuid) - return UserInfo(**response.json()) + return UserInfo(**response) def get_user_list(self) -> List[User]: """Get the list of users.""" response = self._make_request("GET", "api/v2/admin/user/") - return response.json() + return response def get_user_list_by_name(self, query_name: str, offset: int, count: int) -> List[User]: """Get the list of users filtered by name.""" @@ -115,7 +127,7 @@ def delete_user(self, uuid: str) -> bool: def get_user(self, uuid: str) -> User: """Find a user by UUID.""" response = self._make_request("GET", f"api/v2/admin/user/{uuid}/") - return response.json() + return response def backup_file(self) -> bytes: """Backup the file.""" @@ -126,8 +138,8 @@ def get_app_information(self, uuid: str) -> Dict[str, Union[str, List[Dict[str, """Get information about available apps for a given UUID.""" url = f"{self.base_url}/api/v2/user/apps/" params = {"platform": "all"} - response = self._make_request("GET", url, params=params, api_key=uuid) - return response.json() + response = self._make_request("GET", url, params=params, api_key=uuid, admin=False) + return response @staticmethod def generate_qr_code(data: str) -> BytesIO: @@ -135,8 +147,8 @@ def generate_qr_code(data: str) -> BytesIO: qr = qrcode.QRCode(version=1, box_size=10, border=2) qr.add_data(data) qr.make(fit=True) - qr_img = qr.make_image(fill_color="White", back_color="Transparent", image_factory=StyledPilImage, module_drawer=CircleModuleDrawer(), color_mask=RadialGradiantColorMask()) - + # qr_img = qr.make_image(fill_color="White", back_color="Transparent", image_factory=StyledPilImage, module_drawer=CircleModuleDrawer(), color_mask=RadialGradiantColorMask()) + qr_img = qr.make_image(fill_color="black", back_color="white") qr_byte_io = BytesIO() qr_img.save(qr_byte_io, format="PNG") qr_byte_io.seek(0) diff --git a/hiddifypanel_bot/modules/admin/add_user.py b/hiddifypanel_bot/modules/admin/add_user.py index 38d986b..f45debf 100644 --- a/hiddifypanel_bot/modules/admin/add_user.py +++ b/hiddifypanel_bot/modules/admin/add_user.py @@ -2,70 +2,72 @@ from hiddifypanel_bot.utils import tghelper import telebot -from telebot.types import ReplyParameters, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup +from telebot.types import ReplyParameters, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,WebAppInfo from . import constants as C import i18n @bot.callback_query_handler(call_action=C.ADD_USER, role=Role.AGENT) -def add_user_handler(call: HCallbackQuery): - add_user_name(call.message) - bot.answer_callback_query(call.id) +async def add_user_handler(call: HCallbackQuery): + await add_user_name(call.message) + await bot.answer_callback_query(call.id) -def add_user_name(msg: HMessage): +async def add_user_name(msg: HMessage): resp = i18n.t("addname", msg.lang) - bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) - bot.register_next_step_handler(msg, add_user_package_days) + await bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) + await bot.register_next_step_handler(msg.user_id,msg.chat_id, add_user_package_days) - -def add_user_package_days(msg: HMessage): +@bot.step_handler() +async def add_user_package_days(msg: HMessage): name = msg.text resp = i18n.t("adddays", msg.lang) - bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) - bot.register_next_step_handler(msg, validate_package_days, name) - + await bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) + await bot.register_next_step_handler(msg.user_id,msg.chat_id, validate_package_days, name) -def validate_package_days(msg: HMessage, name: str): +@bot.step_handler() +async def validate_package_days(msg: HMessage, name: str): if msg.text.isnumeric(): package_days = int(msg.text) resp = i18n.t("addgb", msg.lang) - bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) - bot.register_next_step_handler(msg, validate_usage_limit, name, package_days) + await bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) + await bot.register_next_step_handler(msg.user_id,msg.chat_id, validate_usage_limit, name, package_days) else: - bot.reply_to(msg, i18n.t("invaliddays", msg.from_user.language_code)) - bot.register_next_step_handler(msg, validate_package_days, name) + await bot.reply_to(msg, i18n.t("invaliddays", msg.from_user.language_code)) + await bot.register_next_step_handler(msg.user_id,msg.chat_id, validate_package_days, name) - -def validate_usage_limit(msg: HMessage, name: str, package_days: int): +@bot.step_handler() +async def validate_usage_limit(msg: HMessage, name: str, package_days: int): try: usage_limit = float(msg.text) - add_user_complete(msg, name, package_days, usage_limit) + await add_user_complete(msg, name, package_days, usage_limit) except ValueError: resp = i18n.t("invalidgb", msg.lang) - bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) - bot.register_next_step_handler(msg, validate_usage_limit, name, package_days) - - -def add_user_complete(msg: HMessage, name: str, package_days: int, usage_limit: float): - - user_data = msg.hapi.add_service("", name, package_days, usage_limit) + await bot.reply_to(msg, resp, reply_markup=ForceReply(False, resp)) + await bot.register_next_step_handler(msg.user_id,msg.chat_id, validate_usage_limit, name, package_days) + +@bot.step_handler() +async def add_user_complete(msg: HMessage, name: str, package_days: int, usage_limit: float): + + user_data = msg.hapi.add_user(User( + comment="", + name= name, + package_days= package_days, + usage_limit_GB= usage_limit + )) if "msg" not in user_data: + user_info=msg.hapi.get_user_info(user_data['uuid']) + resp=tghelper.format_user_message(msg.lang,user_info) uuid = user_data.get("uuid", "N/A") - sublink_data = f"test/{uuid}" + sublink_data = user_info['profile_url'] qr_code = msg.hapi.generate_qr_code(sublink_data) - user_info = ( - f"User UUID: {uuid}\n" - f"Name: {user_data.get('name', 'N/A')}\n" - f"Usage Limit: {user_data.get('usage_limit_GB', 'N/A')} GB\n" - f"Package Days: {user_data.get('package_days', 'N/A')} Days" - ) inline_keyboard = InlineKeyboardMarkup() - inline_keyboard.add(InlineKeyboardButton(text="Open Sublink", web_app=sublink_data)) - - bot.send_photo(msg.chat.id, qr_code, caption=user_info, reply_markup=inline_keyboard) + # inline_keyboard.add(InlineKeyboardButton(text="Open Sublink", web_app=WebAppInfo(sublink_data))) + inline_keyboard.add(InlineKeyboardButton(text="Open Sublink", url=sublink_data)) + + await bot.send_photo(msg.chat.id, qr_code, caption=resp, reply_markup=inline_keyboard) # else: # bot.reply_to(message, "Failed to retrieve user data.") else: - bot.reply_to(msg, "Failed to add user.") + await bot.reply_to(msg, "Failed to add user.") diff --git a/hiddifypanel_bot/modules/admin/search_user.py b/hiddifypanel_bot/modules/admin/search_user.py index c60b009..76615f4 100644 --- a/hiddifypanel_bot/modules/admin/search_user.py +++ b/hiddifypanel_bot/modules/admin/search_user.py @@ -8,18 +8,18 @@ @bot.inline_handler(lambda query: query.query.startswith("search"), role=Role.AGENT) -def handle_inline_query(query: HInlineQuery): +async def handle_inline_query(query: HInlineQuery): search_query = query.query.lstrip("search").strip() results = inline_query(query, search_query) button = types.InlineQueryResultsButton(_("admin.search_in_web"), types.WebAppInfo(query.db["admin_link"] + "/admin/user/?search=" + search_query)) if results: next_offset = int(query.offset or "0") + 50 if len(results) >= 50 else None - bot.answer_inline_query(query.id, results, is_personal=True, next_offset=next_offset, button=button) + await bot.answer_inline_query(query.id, results, is_personal=True, next_offset=next_offset, button=button) else: - bot.answer_inline_query(query.id, results, is_personal=True, button=button) + await bot.answer_inline_query(query.id, results, is_personal=True, button=button) -def inline_query(query: HInlineQuery, name: str): +async def inline_query(query: HInlineQuery, name: str): results = [] offset = int(query.offset or "0") user_list = query.hapi.get_user_list_by_name(name, offset, 50) diff --git a/hiddifypanel_bot/modules/admin/welcome.py b/hiddifypanel_bot/modules/admin/welcome.py index e5afff6..09403dd 100644 --- a/hiddifypanel_bot/modules/admin/welcome.py +++ b/hiddifypanel_bot/modules/admin/welcome.py @@ -7,16 +7,16 @@ @bot.message_handler(commands=["start"], role=Role.AGENT) -def send_welcome(msg: HMessage): - return main_menu(msg) +async def send_welcome(msg: HMessage): + return await main_menu(msg) @bot.message_handler(func=lambda msg: msg.text in [_("backmainmenu", msg.lang), _("mainmenu", msg.lang)], role=Role.AGENT) -def main_menu(msg: HMessage): +async def main_menu(msg: HMessage): text = _("admin.start", msg.lang) keyboards = types.InlineKeyboardMarkup() - keyboards.add(types.InlineKeyboardButton(text=_("admin.open_admin"), web_app=types.WebAppInfo(msg.db["admin_link"]))) + keyboards.add(types.InlineKeyboardButton(text=_("admin.open_admin"), web_app=types.WebAppInfo(await msg.db["admin_link"]))) keyboards.add( types.InlineKeyboardButton(text=_("admin.search_user"), switch_inline_query_current_chat="search "), types.InlineKeyboardButton(text=_("admin.add_user"), callback_data=C.ADD_USER), @@ -25,7 +25,7 @@ def main_menu(msg: HMessage): types.InlineKeyboardButton(text=_("admin.server_info"), callback_data=C.SERVER_INFO), types.InlineKeyboardButton(text=_("admin.admin_users"), callback_data=C.ADMIN_USER) ) - bot.reply_to(msg, text, reply_markup=keyboards) + await bot.reply_to(msg, text, reply_markup=keyboards) # await bot.delete_my_commands(scope=None, language_code=None) diff --git a/hiddifypanel_bot/modules/guest/invalid.py b/hiddifypanel_bot/modules/guest/invalid.py index 8b86989..6b94656 100644 --- a/hiddifypanel_bot/modules/guest/invalid.py +++ b/hiddifypanel_bot/modules/guest/invalid.py @@ -5,15 +5,17 @@ from i18n import t as _ @bot.message_handler(func=lambda message: not message.via_bot) -def default_response(msg:HMessage): - bot.reply_to(msg, _("invalid_request")) +async def default_response(msg:HMessage): + await bot.reply_to(msg, _("invalid_request")) @bot.callback_query_handler(func=lambda message: True) -def default_response(callback:HCallbackQuery): +async def default_response(callback:HCallbackQuery): if callback.message: - bot.answer_callback_query(callback.message.chat_id,callback.id,_("invalid_request")) + await bot.answer_callback_query(callback.message.chat_id,callback.id,_("invalid_request")) @bot.inline_handler(lambda query: True) -def default_response(query:HInlineQuery): +async def default_response(query:HInlineQuery): # bot.answer_inline_query(query.id,results=[types.InlineQueryResultArticle("inline.invalid"+query.query,title=_('inline.invalid_query'),input_message_content=types.InputTextMessageContent(_('inline.invalid_query')))],cache_time=0) - bot.answer_inline_query(query.id,results=[],cache_time=0) \ No newline at end of file + await bot.answer_inline_query(query.id,results=[],cache_time=0) + + \ No newline at end of file diff --git a/hiddifypanel_bot/modules/guest/welcome.py b/hiddifypanel_bot/modules/guest/welcome.py index 7ae6f92..e021a24 100644 --- a/hiddifypanel_bot/modules/guest/welcome.py +++ b/hiddifypanel_bot/modules/guest/welcome.py @@ -4,11 +4,11 @@ from i18n import t as _ @bot.message_handler(role=Role.UNKNOWN) -def send_welcome(msg:HMessage): +async def send_welcome(msg:HMessage): text = _('start',msg.lang) - bot.reply_to(msg, text) + await bot.reply_to(msg, text) # await bot.delete_my_commands(scope=None, language_code=None) diff --git a/hiddifypanel_bot/modules/user/welcome.py b/hiddifypanel_bot/modules/user/welcome.py index f12db46..acb4b24 100644 --- a/hiddifypanel_bot/modules/user/welcome.py +++ b/hiddifypanel_bot/modules/user/welcome.py @@ -7,16 +7,16 @@ @bot.message_handler(role=Role.USER) -def send_welcome(msg: HMessage): +async def send_welcome(msg: HMessage): text = _("start", msg.lang) info = msg.hapi.get_user_info() # bot.reply_to(msg, text+str(info)) - show_user(msg) + await show_user(msg) -def show_user(msg: HMessage, edit=False): +async def show_user(msg: HMessage, edit=False): user_data = msg.hapi.get_user_info() if user_data["lang"] == msg.lang: @@ -26,27 +26,29 @@ def show_user(msg: HMessage, edit=False): restext = tghelper.format_user_message(msg, user_data) sublink = user_data["profile_url"] - qr_code = msg.hapi.generate_qr_code(sublink) + unauthorized_keyboard = types.InlineKeyboardMarkup() unauthorized_keyboard.add(types.InlineKeyboardButton(text=_("user.open_sublink"), web_app=types.WebAppInfo(sublink))) unauthorized_keyboard.add(types.InlineKeyboardButton(text=_("user.update"), callback_data="user.update")) + # unauthorized_keyboard.add(types.InlineKeyboardButton(text="Open Sublink", url=sublink)) if edit: try: # it raise error if no change happens - bot.edit_message_caption(restext, msg.chat_id, message_id=msg.message_id, reply_markup=unauthorized_keyboard) + await bot.edit_message_caption(restext, msg.chat_id, message_id=msg.message_id, reply_markup=unauthorized_keyboard) except: pass else: - bot.send_photo(msg.chat_id, qr_code, caption=restext, reply_markup=unauthorized_keyboard, protect_content=True) + qr_code = msg.hapi.generate_qr_code(sublink) + await bot.send_photo(msg.chat_id, qr_code, caption=restext, reply_markup=unauthorized_keyboard, protect_content=True) if user_data["telegram_id"] == msg.user_id: msg.hapi.update_my_user({"telegram_id", msg.user_id}) else: - bot.reply_to(msg, _("user.not_found")) + await bot.reply_to(msg, _("user.not_found")) @bot.callback_query_handler(call_action=C.USER_UPDATE) -def update_user(call: HCallbackQuery): - show_user(call.message, edit=True) - bot.answer_callback_query(call.id) +async def update_user(call: HCallbackQuery): + await show_user(call.message, edit=True) + await bot.answer_callback_query(call.id) diff --git a/hiddifypanel_bot/utils/tghelper.py b/hiddifypanel_bot/utils/tghelper.py index f281351..8cf1c19 100644 --- a/hiddifypanel_bot/utils/tghelper.py +++ b/hiddifypanel_bot/utils/tghelper.py @@ -7,16 +7,16 @@ from .timedelta_format import format_timedelta -def set_reaction(message: Message, emoji: str = "👍"): - return bot.set_message_reaction(message.chat.id, message.id, [ReactionTypeEmoji(emoji)], is_big=False) +async def set_reaction(message: Message, emoji: str = "👍"): + return await bot.set_message_reaction(message.chat.id, message.id, [ReactionTypeEmoji(emoji)], is_big=False) -def format_user_message(msg, user_data): +def format_user_message(lang, user_data): user_data["profile_usage_current_GB"] = "{:.3f}".format(user_data.get("profile_usage_current", 0)) user_data["profile_usage_total_GB"] = "{:.3f}".format(user_data.get("profile_usage_total", 0)) - return _("user.info", msg.lang, **user_data) + return _("user.info", lang, **user_data) def format_user_message_from_admin(lang, user_data): diff --git a/requirements.txt b/requirements.txt index 8f75a92..e44c03a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,8 @@ # You can also run `make switch-to-poetry` to use the poetry package manager. i18nice==0.15.5 -pyTelegramBotAPI==4.21.0 +#pyTelegramBotAPI==4.21.0 +git+https://github.com/hiddify-com/pyTelegramBotAPI.git@async_register_next_step_handler#egg=pyTelegramBotAPI aiohttp asyncio watchdog