From 748e231fa47c0e78c38da980df76730f3ea3bd60 Mon Sep 17 00:00:00 2001 From: Shayan Heidari <119259115+shayanheidari01@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:35:26 +0330 Subject: [PATCH] Add files via upload --- rubpy/__init__.py | 13 +- rubpy/client.py | 1214 +++---------------------------------------- rubpy/exceptions.py | 120 +++++ rubpy/filters.py | 182 +++++++ rubpy/handlers.py | 96 ++++ rubpy/network.py | 325 ++++++++++++ rubpy/utils.py | 47 ++ 7 files changed, 855 insertions(+), 1142 deletions(-) create mode 100644 rubpy/exceptions.py create mode 100644 rubpy/filters.py create mode 100644 rubpy/handlers.py create mode 100644 rubpy/network.py create mode 100644 rubpy/utils.py diff --git a/rubpy/__init__.py b/rubpy/__init__.py index a3ff162..6a14489 100644 --- a/rubpy/__init__.py +++ b/rubpy/__init__.py @@ -1,9 +1,8 @@ +from .sessions import SQLiteSession, StringSession from .client import Client -from .network import Proxies -from .structs import handlers, models -from .structs.struct import Struct as Message -from .gadgets import exceptions, methods -from . import emoji +from .rubino import Rubino +from . import types, utils, filters, exceptions -__version__ = '6.4.8a2' -__author__ = 'Shayan Heidari' \ No newline at end of file + +__author__ = 'Shayan Heidari' +__version__ = '6.6.4' \ No newline at end of file diff --git a/rubpy/client.py b/rubpy/client.py index 8f9e1f6..4e31480 100644 --- a/rubpy/client.py +++ b/rubpy/client.py @@ -1,1149 +1,93 @@ -from .gadgets.models import users, chats, extras, groups, messages, stickers, contacts -from .gadgets import exceptions, methods, thumbnail -from .sessions import StringSession, SQLiteSession -from .network import Connection, Proxies -from . import __name__ as logger_name -from .structs import Struct -from .crypto import Crypto +from .sessions import SQLiteSession, StringSession +from .parser import Markdown +from .methods import Methods +from typing import Optional, Union -import logging -import asyncio -import aiofiles -import os -import re - - -from typing import Union, Optional -from pathlib import Path -from io import BytesIO - - -from mutagen.ogg import OggFileType -from mutagen.flac import FLAC -from mutagen.mp3 import MP3 -from mutagen.mp4 import MP4 -from mutagen.aac import AAC - - -class Client: - configuire = { - 'package': 'web.rubika.ir', - 'platform': 'Web', +class Client(Methods): + DEFAULT_PLATFORM = { 'app_name': 'Main', - 'user_agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' - 'AppleWebKit/537.36 (KHTML, like Gecko)' - 'Chrome/102.0.0.0 Safari/537.36'), - 'api_version': '6', - 'app_version': '4.4.5' - } + 'app_version': '4.4.6', + 'platform': 'Web', + 'package': 'web.rubika.ir', + } + USER_AGENT = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/102.0.0.0 Safari/537.36') + API_VERSION = '6' def __init__(self, - session: str, - auth: str = None, - private_key: str = None, - proxy=None, - logger=None, - timeout: int = 20, - lang_code: str = 'fa', - user_agent: Optional[str] = None, - request_retries: int = 5, *args, **kwargs): - - """Client - Args: - session_name (`str` | `rubpy.sessions.StringSession`): - The file name of the session file that is used - if there is a string Given (may be a complete path) - or it could be a string session - [rubpy.sessions.StringSession] - - proxy (` rubpy.network.Proxies `, optional): To set up a proxy - - user_agent (`str`, optional): - Client uses the web version, You can set the usr-user_agent - - timeout (`int` | `float`, optional): - To set the timeout `` default( `20 seconds` )`` - - logger (`logging.Logger`, optional): - Logger base for use. - - lang_code(`str`, optional): - To set the lang_code `` default( `fa` ) `` - """ - - if isinstance(session, str): - session = SQLiteSession(session) - - elif not isinstance(session, StringSession): + name: str, + auth: Optional[str] = None, + private_key: Optional[Union[str, bytes]] = None, + bot_token: Optional[str] = None, + phone_number: Optional[str] = None, + user_agent: Optional[str] = None or USER_AGENT, + timeout: Optional[Union[str, int]] = 20, + lang_code: Optional[str] = 'fa', + parse_mode: Optional[str] = 'All', + ) -> None: + super().__init__() + if auth and not isinstance(auth, str): + raise ValueError('`auth` is `string` arg.') + + if private_key: + if not type(private_key) in (str, bytes): + raise ValueError('`private_key` is `string` or `bytes` arg.') + + if bot_token and not isinstance(bot_token, str): + raise ValueError('`bot_token` is `string` arg.') + + if phone_number and not isinstance(phone_number, str): + raise ValueError('`phone_number` is `string` arg.') + + if user_agent and not isinstance(user_agent, str): + raise ValueError('`user_agent` is `string` arg.') + + if not isinstance(timeout, int): + timeout = int(timeout) + + if isinstance(name, str): + session = SQLiteSession(name) + + elif not isinstance(name, StringSession): raise TypeError('The given session must be a ' 'str or [rubpy.sessions.StringSession]') - if not isinstance(logger, logging.Logger): - logger = logging.getLogger(logger_name) - - if proxy and not isinstance(proxy, Proxies): - raise TypeError( - 'The given proxy must be a [rubpy.network.Proxies]') - - self._dcs = None - self._key = None - self._auth = auth - self._private_key = private_key - self._guid = None - self._proxy = proxy - self._logger = logger - self._timeout = timeout - self._session = session - self._handlers = {} - self._request_retries = request_retries - self._user_agent = user_agent or self.configuire['user_agent'] - self._platform = { - 'package': kwargs.get('package', self.configuire['package']), - 'platform': kwargs.get('platform', self.configuire['platform']), - 'app_name': kwargs.get('app_name', self.configuire['app_name']), - 'app_version': kwargs.get('app_version', - self.configuire['app_version']), - 'lang_code': lang_code} - - async def __call__(self, request: object): - try: - result = await self._connection.execute(request) - - # update session - if result.__name__ == 'signIn' and result.status == 'OK': - result.auth = Crypto.decrypt_RSA_OAEP(self._private_key, result.auth) - self._key = Crypto.passphrase(result.auth) - self._auth = result.auth - self._session.insert( - auth=self._auth, - guid=result.user.user_guid, - user_agent=self._user_agent, - phone_number=result.user.phone, - private_key=self._private_key) - - await self( - methods.authorisations.RegisterDevice( - self._user_agent, - lang_code=self._platform['lang_code'], - app_version=self._platform['app_version']) - ) - - return result - - except AttributeError: - raise exceptions.NoConnection( - 'You must first connect the Client' - ' with the *.connect() method') - - async def __aenter__(self) -> "Client": - return await self.start(phone_number=None) - - async def __aexit__(self, *args, **kwargs): - return await self.disconnect() - - async def start(self, phone_number: str = None, *args, **kwargs): - if not hasattr(self, '_connection'): - await self.connect() - - try: - self._logger.info('user info', extra={'data': await self.get_me()}) - - except (exceptions.NotRegistrred, exceptions.InvalidInput): - self._logger.debug('user not registered!') - if phone_number is None: - phone_number = input('Phone Number: ') - is_phone_number_true = True - while is_phone_number_true: - if input(f'Is the {phone_number} correct[y or n] > ').lower() == 'y': - is_phone_number_true = False - else: - phone_number = input('Phone Number: ') - - if phone_number.startswith('0'): - phone_number = '98{}'.format(phone_number[1:]) - elif phone_number.startswith('+98'): - phone_number = phone_number[1:] - elif phone_number.startswith('0098'): - phone_number = phone_number[2:] - - result = await self( - methods.authorisations.SendCode( - phone_number=phone_number, *args, **kwargs)) - - if result.status == 'SendPassKey': - while True: - pass_key = input(f'Password [{result.hint_pass_key}] > ') - result = await self( - methods.authorisations.SendCode( - phone_number=phone_number, - pass_key=pass_key, *args, **kwargs)) - - if result.status == 'OK': - break - - public_key, self._private_key = Crypto.create_keys() - while True: - phone_code = input('Code: ') - result = await self( - methods.authorisations.SignIn( - phone_code=phone_code, - phone_number=phone_number, - phone_code_hash=result.phone_code_hash, - public_key=public_key, - *args, **kwargs)) - - if result.status == 'OK': - break - + if parse_mode not in ('All', 'html', 'markdown', 'mk'): + raise ValueError('The `parse_mode` argument can only be in `("All", "html", "markdown", "mk")`.') + + self.DEFAULT_PLATFORM['lang_code'] = lang_code + self.name = name + self.auth = auth + self.private_key = private_key + self.bot_token = bot_token + self.phone_number = phone_number + self.user_agent = user_agent + self.lang_code = lang_code + self.timeout = timeout + self.session = session + self.parse_mode = parse_mode + self.markdown = Markdown() + self.database = None + self.guid = None + self.key = None + self.handlers = {} + + def __enter__(self): return self - def run(self, phone_number: str = None): - async def main_runner(): - await self.start(phone_number=phone_number) - await self._connection.receive_updates() - - asyncio.run(main_runner()) - - async def connect(self): - self._connection = Connection(client=self) - - if self._auth and self._private_key is not None: - get_me = await self.get_me() - self._guid = get_me.user.user_guid - - information = self._session.information() - self._logger.info(f'the session information was read {information}') - if information: - self._auth = information[1] - self._guid = information[2] - self._private_key = information[4] - if isinstance(information[3], str): - self._user_agent = information[3] or self._user_agent - - return self - - async def disconnect(self): + def __exit__(self, *args, **kwargs): try: - await self._connection.close() - self._logger.info(f'the client was disconnected') - - except AttributeError: - raise exceptions.NoConnection( - 'You must first connect the Client' - ' with the *.connect() method') + return self.disconnect() + except Exception as exc: + print(exc.__name__, exc) - async def run_until_disconnected(self): - return await self._connection.receive_updates() + async def __aenter__(self): + return await self.start() - # handler methods - - def on(self, handler): - def MetaHandler(func): - self.add_handler(func, handler) - return func - return MetaHandler - - def add_handler(self, func, handler): - self._handlers[func] = handler - - def remove_handler(self, func): + async def __aexit__(self, *args, **kwargs): try: - self._handlers.pop(func) - except KeyError: - pass - - # async methods - - async def get_me(self, *args, **kwargs) -> users.GetUserInfo: - result = await self(methods.users.GetUserInfo(*args, **kwargs)) - return users.GetUserInfo(**result.to_dict()) - - async def upload(self, file: bytes, *args, **kwargs): - return await self._connection.upload_file(file=file, *args, **kwargs) - - async def download_file_inline(self, file_inline: Struct, save_as: str = None, chunk_size: int = 131072, callback=None, *args, **kwargs): - result = await self._connection.download( - file_inline.dc_id, - file_inline.file_id, - file_inline.access_hash_rec, - file_inline.size, - chunk=chunk_size, - callback=callback) - - if isinstance(save_as, str): - async with aiofiles.open(save_as, 'wb+') as _file: - await _file.write(result) - return save_as - - return result - -# ---------------- Users Methods ---------------- - - async def get_user_info(self, user_guid: str) -> users.GetUserInfo: - result = await self(methods.users.GetUserInfo(user_guid)) - return users.GetUserInfo(**result.to_dict()) - - async def block_user(self, user_guid: str) -> users.BlockUser: - result = await self(methods.users.SetBlockUser(user_guid)) - return users.BlockUser(**result.to_dict()) - - async def unblock_user(self, user_guid: str) -> users.BlockUser: - result = await self(methods.users.SetBlockUser(user_guid, 'Unblock')) - return users.BlockUser(**result.to_dict()) - - async def delete_user_chat(self, user_guid: str, last_deleted_message_id: str) -> users.DeleteUserChat: - result = await self(methods.users.DeleteUserChat(user_guid, last_deleted_message_id)) - return users.DeleteUserChat(**result.to_dict()) - - async def check_user_username(self, username: str) -> users.CheckUserName: - result = await self(methods.users.CheckUserUsername(username.replace('@', ''))) - return users.CheckUserName(**result.to_dict()) - -# ---------------- Chats Methods ---------------- - - async def upload_avatar(self, object_guid: str, main_file_id: str, thumbnail_file_id: str): - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - return await self(methods.chats.UploadAvatar(object_guid, main_file_id, thumbnail_file_id)) - - async def delete_avatar(self, object_guid: str, avatar_id: str) -> chats.DeleteAvatar: - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - result = await self(methods.chats.DeleteAvatar(object_guid, avatar_id)) - return chats.DeleteAvatar(**result.to_dict()) - - async def get_avatars(self, object_guid: str) -> chats.GetAvatars: - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - result = await self(methods.chats.GetAvatars(object_guid)) - return chats.GetAvatars(**result.to_dict()) - - async def get_chats(self, start_id: Optional[int] = None) -> chats.GetChats: - result = await self(methods.chats.GetChats(start_id)) - return chats.GetChats(**result.to_dict()) - - async def seen_chats(self, seen_list: dict) -> chats.SeenChats: - result = await self(methods.chats.SeenChats(seen_list)) - return chats.SeenChats(**result.to_dict()) - - async def get_chat_ads(self, state: Optional[int] = None): - return await self(methods.chats.GetChatAds(state)) - - async def set_action_chat(self, object_guid: str, action: str) -> chats.SetActionChat: - ''' - alloweds: ["Mute", "Unmute"] - result = await client.set_action_chat('object_guid', 'Mute') - print(result) - ''' - result = await self(methods.chats.SetActionChat(object_guid, action)) - return chats.SetActionChat(**result.to_dict()) - - async def get_chats_updates(self, state: Optional[int] = None) -> chats.GetChatsUpdates: - result = await self(methods.chats.GetChatsUpdates(state)) - return chats.GetChatsUpdates(**result.to_dict()) - - async def send_chat_activity(self, object_guid: str, activity: Optional[str] = None) -> chats.SendChatActivity: - result = await self(methods.chats.SendChatActivity(object_guid, activity)) - return chats.SendChatActivity(**result.to_dict()) - - async def delete_chat_history(self, object_guid: str, last_message_id: str) -> chats.DeleteChatHistory: - result = await self(methods.chats.DeleteChatHistory(object_guid, last_message_id)) - return chats.DeleteChatHistory(**result.to_dict()) - - async def search_chat_messages(self, object_guid: str, search_text: str, type: str = 'Hashtag') -> chats.SearchChatMessages: - result = await self(methods.chats.SearchChatMessages(object_guid, search_text, type)) - return chats.SearchChatMessages(**result.to_dict()) - -# ---------------- Extras Methods ---------------- - - async def search_global_objects(self, search_text: str) -> extras.SearchGlobalObjects: - result = await self(methods.extras.SearchGlobalObjects(search_text)) - return extras.SearchGlobalObjects(**result.to_dict()) - - async def get_abs_objects(self, object_guids: list): - return await self(methods.extras.GetAbsObjects(object_guids)) - - async def get_object_by_username(self, username: str) -> extras.GetObjectByUsername: - result = await self(methods.extras.GetObjectByUsername(username.replace('@', ''))) - return extras.GetObjectByUsername(**result.to_dict()) - - async def get_link_from_app_url(self, app_url: str) -> extras.GetLinkFromAppUrl: - result = await self(methods.extras.GetLinkFromAppUrl(app_url)) - return extras.GetLinkFromAppUrl(**result.to_dict()) - - async def create_voice_call(self, object_guid: str) -> extras.CreateVoiceCall: - if object_guid.startswith('c'): - return await self(methods.channels.CreateChannelVoiceChat(object_guid)) - elif object_guid.startswith('g'): - result = await self(methods.groups.CreateGroupVoiceChat(object_guid)) - return extras.CreateVoiceCall(**result.to_dict()) - else: - print('Invalid Object Guid') - return False - - async def set_voice_chat_setting(self, object_guid: str, voice_chat_id: str, title: Optional[str] = None): - if object_guid.startswith('c'): - return await self(methods.channels.SetChannelVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) - elif object_guid.startswith('g'): - return await self(methods.groups.SetGroupVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) - else: - print('Invalid Object Guid') - return False - - async def discard_voice_chat(self, object_guid: str, voice_chat_id: str): - if object_guid.startswith('c'): - return await self(methods.channels.DiscardChannelVoiceChat(object_guid, voice_chat_id)) - elif object_guid.startswith('g'): - return await self(methods.channels.DiscardGroupVoiceChat(object_guid, voice_chat_id)) - else: - print('Invalid Object Guid') - return None - -# ---------------- Groups Methods ---------------- - - async def add_group(self, title: str, member_guids: list) -> groups.AddGroup: - result = await self(methods.groups.AddGroup(title, member_guids)) - return groups.AddGroup(**result.to_dict()) - - async def join_group(self, link: str) -> groups.JoinGroup: - result = await self(methods.groups.JoinGroup(link)) - return groups.JoinGroup(**result.to_dict()) - - async def leave_group(self, group_guid: str) -> groups.LeaveGroup: - result = await self(methods.groups.LeaveGroup(group_guid)) - return groups.LeaveGroup(**result.to_dict()) - - async def remove_group(self, group_guid: str) -> groups.RemoveGroup: - result = await self(methods.groups.RemoveGroup(group_guid)) - return groups.RemoveGroup(**result.to_dict()) - - async def get_group_info(self, group_guid: str) -> groups.GetGroupInfo: - result = await self(methods.groups.GetGroupInfo(group_guid)) - return groups.GetGroupInfo(**result.to_dict()) - - async def get_group_link(self, group_guid: str) -> groups.GroupLink: - result = await self(methods.groups.GetGroupLink(group_guid)) - return groups.GroupLink(**result.to_dict()) - - async def set_group_link(self, group_guid: str) -> groups.GroupLink: - result = await self(methods.groups.SetGroupLink(group_guid)) - return groups.GroupLink(**result.to_dict()) - - async def edit_group_info(self, - group_guid: str, - title: Optional[str] = None, - description: Optional[str] = None, - reaction_type = None, - selected_reactions: Optional[list] = None, - event_messages: Optional[bool] = None, - chat_history_for_new_members: str = 'Visible' - ) -> groups.EditGroupInfo: - updated_parameters = [] - updated_parameters.append('chat_history_for_new_members') - - if title: - updated_parameters.append('title') - if description: - updated_parameters.append('description') - if event_messages is not None: - updated_parameters.append('event_messages') - - chat_reaction_setting = None - - if reaction_type is not None: - chat_reaction_setting = {'reaction_type': reaction_type} - - if selected_reactions: - chat_reaction_setting['selected_reactions'] = selected_reactions - - result = await self(methods.groups.EditGroupInfo( - group_guid, updated_parameters, title, description, event_messages=event_messages, - chat_history_for_new_members=chat_history_for_new_members, - chat_reaction_setting=chat_reaction_setting)) - - return groups.EditGroupInfo(**result.to_dict()) - - async def set_group_admin(self, - group_guid: str, - member_guid: str, - access_list: list, - action: str = 'SetAdmin', - ) -> groups.InChatMember: - result = await self(methods.groups.SetGroupAdmin(group_guid, member_guid, access_list, action)) - return groups.InChatMember(**result.to_dict()) - - async def ban_group_member(self, group_guid: str, member_guid: str) -> groups.BanGroupMember: - result = await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Set')) - return groups.BanGroupMember(**result.to_dict()) - - async def unban_group_member(self, group_guid: str, member_guid: str) -> groups.BanGroupMember: - result = await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Unset')) - return groups.BanGroupMember(**result.to_dict()) - - async def add_group_members(self, group_guid: str, member_guids: list) -> groups.AddGroupMembers: - result = await self(methods.groups.AddGroupMembers(group_guid, member_guids)) - return groups.AddGroupMembers(**result.to_dict()) - - async def get_group_all_members(self, group_guid: str, search_text: str = None, start_id: int = None) -> groups.GetAllGroupMembers: - result = await self(methods.groups.GetGroupAllMembers(group_guid, search_text, start_id)) - return groups.GetAllGroupMembers(**result.to_dict()) - - async def get_group_admin_members(self, group_guid: str, start_id: int = None) -> groups.GetGroupAdminMembers: - result = await self(methods.groups.GetGroupAdminMembers(group_guid, start_id)) - return groups.GetGroupAdminMembers(**result.to_dict()) - - async def get_group_mention_list(self, group_guid: str, search_mention: str = None) -> groups.GetGroupMentionList: - result = await self(methods.groups.GetGroupMentionList(group_guid, search_mention)) - return groups.GetGroupMentionList(**result.to_dict()) - - async def get_group_default_access(self, group_guid: str) -> groups.GetGroupDefaultAccess: - result = await self(methods.groups.GetGroupDefaultAccess(group_guid)) - return groups.GetGroupDefaultAccess(**result.to_dict()) - - async def set_group_default_access(self, group_guid: str, access_list: list): - return await self(methods.groups.SetGroupDefaultAccess(group_guid, access_list)) - - async def group_preview_by_join_link(self, group_link: str) -> groups.GroupPreviewByJoinLink: - result = await self(methods.groups.GroupPreviewByJoinLink(group_link)) - return groups.GroupPreviewByJoinLink(**result.to_dict()) - - async def delete_no_access_group_chat(self, group_guid: str) -> groups.DeleteNoAccessGroupChat: - result = await self(methods.groups.DeleteNoAccessGroupChat(group_guid)) - return groups.DeleteNoAccessGroupChat(**result.to_dict()) - - async def get_group_admin_access_list(self, group_guid: str, member_guid: str) -> groups.GetGroupAdminAccessList: - result = await self(methods.groups.GetGroupAdminAccessList(group_guid, member_guid)) - return groups.GetGroupAdminAccessList(**result.to_dict()) - - async def get_banned_group_members(self, group_guid: str, start_id: int = None) -> groups.GetBannedGroupMembers: - result = await self(methods.groups.GetBannedGroupMembers(group_guid, start_id)) - return groups.GetBannedGroupMembers(**result.to_dict()) - - async def set_group_timer(self, group_guid: str, time: int) -> groups.EditGroupInfo: - result = await self(methods.groups.EditGroupInfo(group_guid, slow_mode=time, updated_parameters=['slow_mode'])) - return groups.EditGroupInfo(**result.to_dict()) - -# ---------------- Messages Methods ---------------- - - async def custom_send_message(self, - object_guid: str, - message=None, - reply_to_message_id: str = None, - file: bytes = None, - file_inline: dict = None, - *args, **kwargs - ) -> messages.SendMessage: - if compile(r'(?i)^(me|self|cloud)$').match(object_guid): - object_guid = self._guid - - if file: - file = await self.upload(file, *args, **kwargs) - for key, value in file_inline.items(): - file[key] = value - - result = await self( - methods.messages.SendMessage( - object_guid, - message=message, - file_inline=file, - reply_to_message_id=reply_to_message_id)) - - return messages.SendMessage(**result.to_dict()) - - async def auto_delete_message(self, object_guid: str, message_id: str, after_time: int): - await asyncio.sleep(after_time) - return await self.delete_messages(object_guid, message_id) - - async def send_message(self, - object_guid: str, - message: Optional[str] = None, - reply_to_message_id: Optional[str] = None, - file_inline: Optional[Union[Path, bytes]] = None, - type: str = methods.messages.File, - thumb: bool = True, - auto_delete: Optional[int] = None, - *args, **kwargs) -> messages.SendMessage: - """_send message_ - - Args: - object_guid (str): - _object guid_ - - message (Any, optional): - _message or cation or sticker_ . Defaults to None. - - reply_to_message_id (str, optional): - _reply to message id_. Defaults to None. - - file_inline (typing.Union[pathlib.Path, bytes], optional): - _file_. Defaults to None. - - type (str, optional): - _file type_. Defaults to methods.messages.File.( - methods.messages.Gif, - methods.messages.Image, - methods.messages.Voice, - methods.messages.Music, - methods.messages.Video - ) - - thumb (bool, optional): - if value is "True", - the lib will try to build the thumb ( require cv2 ) - if value is thumbnail.Thumbnail, to set custom - Defaults to True. - """ - - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - if file_inline: - if not isinstance(file_inline, Struct): - if isinstance(file_inline, str): - async with aiofiles.open(file_inline, 'rb') as file: - kwargs['file_name'] = kwargs.get( - 'file_name', os.path.basename(file_inline)) - file_inline = await file.read() - - if type == methods.messages.Music: - thumb = None - kwargs['time'] = kwargs.get('time', self.get_audio_duration(file_inline, kwargs.get('file_name'))) - - elif type == methods.messages.Voice: - thumb = None - kwargs['time'] = kwargs.get('time', self.get_audio_duration(file_inline, kwargs.get('file_name'))) - - if thumb: - if type in (methods.messages.Video, methods.messages.Gif): - thumb = thumbnail.MakeThumbnail.from_video(file_inline) - elif type == methods.messages.Image: - thumb = thumbnail.MakeThumbnail(file_inline) - - if not hasattr(thumb, 'image'): - type = methods.messages.File - thumb = None - - file_inline = await self.upload(file_inline, *args, **kwargs) - file_inline['type'] = type - file_inline['time'] = kwargs.get('time', 1) - file_inline['width'] = kwargs.get('width', 200) - file_inline['height'] = kwargs.get('height', 200) - file_inline['music_performer'] = kwargs.get('performer', '') - - if isinstance(thumb, thumbnail.Thumbnail): - file_inline['time'] = thumb.seconds - file_inline['width'] = thumb.width - file_inline['height'] = thumb.height - file_inline['thumb_inline'] = thumb.to_base64() - - result = await self( - methods.messages.SendMessage( - object_guid, - message=message, - file_inline=file_inline, - reply_to_message_id=reply_to_message_id)) - - if auto_delete is not None: - asyncio.create_task(self.auto_delete_message(result.object_guid, - result.message_id, - auto_delete)) - - message = messages.SendMessage(**result.to_dict()) - await message.set_shared_data(self, message) - return message - - async def send_photo(self, object_guid: str, photo: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send a photo. - - Args: - object_guid (str): - The GUID of the recipient. - - photo (bytes): - The photo data. - - caption (str, optional): - The caption for the photo. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=photo, type=methods.messages.Image, auto_delete=auto_delete, *args, **kwargs) - - async def send_document(self, object_guid: str, document: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send a document. - - Args: - object_guid (str): - The GUID of the recipient. - - document (bytes): - The document data. - - caption (str, optional): - The caption for the document. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=document, thumb=False, auto_delete=auto_delete, *args, **kwargs) - - async def send_gif(self, object_guid: str, gif: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send a GIF. - - Args: - object_guid (str): - The GUID of the recipient. - - gif (bytes): - The GIF data. - - caption (str, optional): - The caption for the GIF. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=gif, type=methods.messages.Gif, auto_delete=auto_delete, *args, **kwargs) - - async def send_video(self, object_guid: str, video: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send a video. - - Args: - object_guid (str): - The GUID of the recipient. - - video (bytes): - The video data. - - caption (str, optional): - The caption for the video. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=video, type=methods.messages.Video, auto_delete=auto_delete, *args, **kwargs) - - async def send_music(self, object_guid: str, music: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send music. - - Args: - object_guid (str): - The GUID of the recipient. - - music (bytes): - The music data. - - caption (str, optional): - The caption for the music. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=music, type=methods.messages.Music, thumb=False, auto_delete=auto_delete, *args, **kwargs) - - async def send_voice(self, object_guid: str, voice: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: - """ - Send a voice message. - - Args: - object_guid (str): - The GUID of the recipient. - - voice (bytes): - The voice message data. - - caption (str, optional): - The caption for the voice message. Defaults to None. - - reply_to_message_id (str, optional): - The ID of the message to which this is a reply. Defaults to None. - """ - return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=voice, type=methods.messages.Voice, auto_delete=auto_delete, *args, **kwargs) - - async def edit_message(self, object_guid: str, message_id: str, text: str) -> messages.EditMessage: - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - result = await self(methods.messages.EditMessage(object_guid, message_id, text)) - return messages.EditMessage(**result.to_dict()) - - async def delete_messages(self, object_guid: str, message_ids: list, type: str = 'Global') -> messages.DeleteMessages: - if object_guid.lower() in ('me', 'cloud', 'self'): - object_guid = self._guid - - result = await self(methods.messages.DeleteMessages(object_guid, message_ids, type)) - return messages.DeleteMessages(**result.to_dict()) - - async def request_send_file(self, file_name: str, size: int, mime: str) -> messages.RequestSendFile: - result = await self(methods.messages.RequestSendFile(file_name, size, mime)) - return messages.RequestSendFile(**result.to_dict()) - - async def forward_messages(self, from_object_guid: str, to_object_guid: str, message_ids: list) -> messages.ForwardMessages: - if from_object_guid.lower() in ('me', 'cloud', 'self'): - from_object_guid = self._guid - - result = await self(methods.messages.ForwardMessages(from_object_guid, to_object_guid, message_ids)) - return messages.ForwardMessages(**result.to_dict()) - - async def create_poll(self, - object_guid: str, - question: str, - options: list, - type: str = 'Regular', - is_anonymous: bool = True, - allows_multiple_answers: bool = False, - correct_option_index: int = 0, - explanation: str = None, - reply_to_message_id: int = 0, - ) -> messages.SendMessage: - if type == 'Regular': - result = await self(methods.messages.CreatePoll( - object_guid=object_guid, - question=question, - options=options, - allows_multiple_answers=allows_multiple_answers, - is_anonymous=is_anonymous, - reply_to_message_id=reply_to_message_id, - type=type, - )) - - else: - result = await self(methods.messages.CreatePoll( - object_guid=object_guid, - question=question, - options=options, - allows_multiple_answers=allows_multiple_answers, - is_anonymous=is_anonymous, - reply_to_message_id=reply_to_message_id, - correct_option_index=correct_option_index, - explanation=explanation, - type=type, - )) - - return messages.SendMessage(**result.to_dict()) - - async def vote_poll(self, poll_id: str, selection_index: int) -> messages.VotePoll: - result = await self(methods.messages.VotePoll(poll_id, selection_index)) - return messages.VotePoll(**result.to_dict()) - - async def get_poll_status(self, poll_id: str) -> messages.VotePoll: - result = await self(methods.messages.GetPollStatus(poll_id)) - return messages.VotePoll(**result.to_dict()) - - async def get_poll_option_voters(self, poll_id: str, selection_index: int, start_id: int = None): - return await self(methods.messages.GetPollOptionVoters(poll_id, selection_index, start_id)) - - async def set_pin_message(self, object_guid: str, message_id: str, action: str = 'Pin') -> messages.SetPinMessage: - result = await self(methods.messages.SetPinMessage(object_guid, message_id, action)) - return messages.SetPinMessage(**result.to_dict()) - - async def unset_pin_message(self, object_guid: str, message_id: str, action: str = 'Unpin') -> messages.SetPinMessage: - result = await self(methods.messages.SetPinMessage(object_guid, message_id, action)) - return messages.SetPinMessage(**result.to_dict()) - - async def get_messages_updates(self, object_guid: str, state: int = None) -> messages.GetMessagesUpdates: - result = await self(methods.messages.GetMessagesUpdates(object_guid, state)) - return messages.GetMessagesUpdates(**result.to_dict()) - - async def search_global_messages(self, search_text: str, type: str = 'Text') -> messages.SearchGlobalMessages: - result = await self(methods.messages.SearchGlobalMessages(search_text, type)) - return messages.SearchGlobalMessages(**result.to_dict()) - - async def click_message_url(self, object_guid: str, message_id: str, link_url: str): - return await self(methods.messages.ClickMessageUrl(object_guid, message_id, link_url)) - - async def get_messages_by_ID(self, object_guid: str, message_ids: list) -> messages.GetMessagesByID: - result = await self(methods.messages.GetMessagesByID(object_guid, message_ids)) - return messages.GetMessagesByID(**result.to_dict()) - - async def get_messages(self, object_guid: str, min_id: int, max_id: int, sort: str = 'FromMin', limit: int = 10): - return await self(methods.messages.GetMessages(object_guid, min_id, max_id, sort, limit)) - - async def get_messages_interval(self, object_guid: str, middle_message_id: str) -> messages.GetMessagesInterval: - result = await self(methods.messages.GetMessagesInterval(object_guid, middle_message_id)) - return messages.GetMessagesInterval(**result.to_dict()) - - async def get_message_url(self, object_guid: str, message_id: int): - if type(message_id) == str: - message_id = int(message_id) - - return await self(methods.messages.GetMessageShareUrl(object_guid, message_id)) - - async def reaction(self, object_guid: str, message_id: str, reaction_id: str) -> messages.Reacion: - result = await self(methods.messages.ActionOnMessageReaction(object_guid, message_id, 'Add', reaction_id)) - return messages.Reacion(**result.to_dict()) - - async def delete_reaction(self, object_guid: str, message_id: str) -> messages.Reacion: - result = await self(methods.messages.ActionOnMessageReaction(object_guid, message_id, 'Remove')) - return messages.Reacion(**result.to_dict()) - -# ---------------- Channels Methods ---------------- - - async def add_channel(self, title: str, description: str = None): - return await self(methods.channels.AddChannel(title, description)) - - async def remove_channel(self, channel_guid: str): - return await self(methods.channels.RemoveChannel(channel_guid)) - - async def get_channel_info(self, channel_guid: str): - return await self(methods.channels.GetChannelInfo(channel_guid)) - - async def edit_channel_info(self, - channel_guid: str, - title: str = None, - description: str = None, - channel_type: str = None, - sign_messages: str = None, - ): - updated_parameters = [] - - if title: - updated_parameters.append('title') - if description: - updated_parameters.append('description') - if channel_type: - updated_parameters.append('channel_type') - if sign_messages: - updated_parameters.append('sign_messages') - - return await self(methods.channels.EditChannelInfo( - channel_guid, updated_parameters, title, description, channel_type, sign_messages)) - - async def join_channel(self, channel_guid: str): - return await self(methods.channels.JoinChannelAction(channel_guid, 'Join')) - - async def leave_channel(self, channel_guid: str): - return await self(methods.channels.JoinChannelAction(channel_guid, 'Remove')) - - async def archive_channel(self, channel_guid: str): - return await self(methods.channels.JoinChannelAction(channel_guid, 'Archive')) - - async def join_channel_by_link(self, link: str): - return await self(methods.channels.JoinChannelByLink(link)) - - async def add_channel_members(self, channel_guid: str, member_guids: list): - return await self(methods.channels.AddChannelMembers(channel_guid, member_guids)) - - async def ban_channel_member(self, channel_guid: str, member_guid: str): - return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Set')) - - async def unban_channel_member(self, channel_guid: str, member_guid: str): - return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Unset')) - - async def check_channel_username(self, username: str): - return await self(methods.channels.CheckChannelUsername(username)) - - async def channel_preview_by_join_link(self, link: str): - return await self(methods.channels.ChannelPreviewByJoinLink(link)) - - async def get_channel_all_members(self, channel_guid: str, search_text: str = None, start_id: int = None): - return await self(methods.channels.GetChannelAllMembers(channel_guid, search_text, start_id)) - - async def check_join(self, channel_guid: str, username: str) -> bool: - result = await self.get_channel_all_members(channel_guid, username.replace('@', '')) - in_chat_members: dict = result['in_chat_members'] - for member in in_chat_members: - if username in member.values(): - return True - else: - continue - else: - return False - - async def get_channel_admin_members(self, channel_guid: str, start_id: int = None): - return await self(methods.channels.GetChannelAdminMembers(channel_guid, start_id)) - - async def update_channel_username(self, channel_guid: str, username: str): - return await self(methods.channels.UpdateChannelUsername(channel_guid, username)) - - async def get_channel_link(self, channel_guid: str): - return await self(methods.channels.GetChannelLink(channel_guid)) - - async def set_channel_link(self, channel_guid: str): - return await self(methods.channels.SetChannelLink(channel_guid)) - - async def get_channel_admin_access_list(self, channel_guid: str, member_guid: str): - return await self(methods.channels.GetChannelAdminAccessList(channel_guid, member_guid)) - -# ---------------- Contacts Methods ---------------- - - async def delete_contact(self, user_guid: str) -> contacts.DeleteContact: - result = await self(methods.contacts.DeleteContact(user_guid)) - return contacts.DeleteContact(**result.to_dict()) - - async def add_address_book(self, phone: str, first_name: str, last_name: str = '') -> contacts.AddAddressBook: - result = await self(methods.contacts.AddAddressBook(phone, first_name, last_name)) - return contacts.AddAddressBook(**result.to_dict()) - - async def get_contacts_updates(self, state: int = None): - return await self(methods.contacts.GetContactsUpdates(state)) - - async def get_contacts(self, start_id: str = None) -> contacts.GetContacts: - result = await self(methods.contacts.GetContacts(start_id)) - return contacts.GetContacts(**result.to_dict()) - -# ---------------- Settings Methods ---------------- - - async def set_setting(self, - show_my_last_online: str = None, - show_my_phone_number: str = None, - show_my_profile_photo: str = None, - link_forward_message: str = None, - can_join_chat_by: str = None - ): - updated_parameters = [] - - if show_my_last_online: - updated_parameters.append('show_my_last_online') - if show_my_phone_number: - updated_parameters.append('show_my_phone_number') - if show_my_profile_photo: - updated_parameters.append('show_my_profile_photo') - if link_forward_message: - updated_parameters.append('link_forward_message') - if can_join_chat_by: - updated_parameters.append('can_join_chat_by') - - return await self(methods.settings.SetSetting( - updated_parameters=updated_parameters, - show_my_last_online=show_my_last_online, - show_my_phone_number=show_my_phone_number, - show_my_profile_photo=show_my_profile_photo, - link_forward_message=link_forward_message, - can_join_chat_by=can_join_chat_by)) - - async def add_folder(self, - include_chat_types: list = None, - exclude_chat_types: list = None, - include_object_guids: list = None, - exclude_object_guids: list = None - ): - return await self(methods.settings.AddFolder( - include_chat_types, - exclude_chat_types, - include_object_guids, - exclude_object_guids)) - - async def get_folders(self, last_state: int): - return await self(methods.settings.GetFolders(last_state)) - - async def edit_folder(self, - include_chat_types: list = None, - exclude_chat_types: list = None, - include_object_guids: list = None, - exclude_object_guids: list = None - ): - updated_parameters = [] - - if include_chat_types: - updated_parameters.append('include_chat_types') - if exclude_chat_types: - updated_parameters.append('exclude_chat_types') - if include_object_guids: - updated_parameters.append('include_object_guids') - if exclude_object_guids: - updated_parameters.append('exclude_object_guids') - - return await self(methods.settings.EditFolder( - updated_parameters, - include_chat_types, - exclude_chat_types, - include_object_guids, - exclude_object_guids)) - - async def delete_folder(self, folder_id: str): - return await self(methods.settings.DeleteFolder(folder_id)) - - async def update_profile(self, first_name: str = None, last_name: str = None, bio: str = None): - updated_parameters = [] - - if first_name: - updated_parameters.append('first_name') - if last_name: - updated_parameters.append('last_name') - if bio: - updated_parameters.append('bio') - - return await self(methods.settings.UpdateProfile(updated_parameters, first_name, last_name, bio)) - - async def update_username(self, username: str): - return await self(methods.settings.UpdateUsername(username)) - - async def get_two_passcode_status(self): - return await self(methods.settings.GetTwoPasscodeStatus()) - - async def get_suggested_folders(self): - return await self(methods.settings.GetSuggestedFolders()) - - async def get_privacy_setting(self): - return await self(methods.settings.GetPrivacySetting()) - - async def get_blocked_users(self): - return await self(methods.settings.GetBlockedUsers()) - - async def get_my_sessions(self): - return await self(methods.settings.GetMySessions()) - - async def terminate_session(self, session_key: str): - return await self(methods.settings.TerminateSession(session_key)) - - async def setup_two_step_verification(self, password: str, hint: str, recovery_email: str): - return await self(methods.settings.SetupTwoStepVerification(password, hint, recovery_email)) - -# ---------------- Stickers Methods ---------------- - - async def get_my_sticker_sets(self) -> stickers.GetMyStickerSets: - result = await self(methods.stickers.GetMyStickerSets()) - return stickers.GetMyStickerSets(**result.to_dict()) - - async def search_stickers(self, search_text: str = '', start_id: int = None): - return await self(methods.stickers.SearchStickers(search_text, start_id)) - - async def get_sticker_set_by_ID(self, sticker_set_id: str): - return await self(methods.stickers.GetStickerSetByID(sticker_set_id)) - - async def action_on_sticker_set(self, sticker_set_id: str, action: str = 'Add'): - return await self(methods.stickers.ActionOnStickerSet(sticker_set_id, action)) - - async def get_stickers_by_emoji(self, emoji: str, suggest_by: str = 'Add'): - return await self(methods.stickers.GetStickersByEmoji(emoji, suggest_by)) - - async def get_stickers_by_set_IDs(self, sticker_set_ids: list) -> stickers.GetStickersBySetIDs: - result = await self(methods.stickers.GetStickersBySetIDs(sticker_set_ids)) - return stickers.GetStickersBySetIDs(**result.to_dict()) - - async def get_trend_sticker_sets(self, start_id: int = None) -> stickers.GetTrendStickerSets: - result = await self(methods.stickers.GetTrendStickerSets(start_id)) - return stickers.GetTrendStickerSets(**result.to_dict()) - - def get_audio_duration(self, file: bytes, file_name: str) -> float: - file_format = re.search(r'\.([a-zA-Z0-9]+)$', file_name) - if not file_format: - raise ValueError('The file format is not specified.') - - file_format = file_format.group(1).lower() - - if file_format == 'mp3': - audio = MP3(BytesIO(file)) - elif file_format == 'm4a': - audio = MP4(BytesIO(file)) - elif file_format in ['flac', 'fla']: - audio = FLAC(BytesIO(file)) - elif file_format == 'aac': - audio = AAC(BytesIO(file)) - elif file_format == 'ogg': - audio = OggFileType(BytesIO(file)) - else: - raise ValueError(f'Format {file_format} is not supported.') - - return float(audio.info.length) \ No newline at end of file + return await self.disconnect() + except Exception as exc: + print(exc.__name__, exc) \ No newline at end of file diff --git a/rubpy/exceptions.py b/rubpy/exceptions.py new file mode 100644 index 0000000..7bd8a9b --- /dev/null +++ b/rubpy/exceptions.py @@ -0,0 +1,120 @@ +import sys + + +class ClientError(Exception): + pass + +class StopHandler(ClientError): + pass + +class APIException(Exception): + pass + +class CancelledError(ClientError): + pass + + +class RequestError(ClientError): + def __init__(self, message, request=None): + self.message = str(message) + self.request = request + + +class UploadError(Exception): + def __init__(self, status, status_det, dev_message: str=None): + self.status = status + self.status_det = status_det + self.dev_message = dev_message + +class CodeIsUsed(RequestError): + pass + + +class TooRequests(RequestError): + pass + + +class InvalidAuth(RequestError): + pass + + +class ServerError(RequestError): + pass + + +class UrlNotFound(RequestError): + pass + + +class ErrorAction(RequestError): + pass + + +class ErrorIgnore(RequestError): + pass + + +class ErrorGeneric(RequestError): + pass + + +class NoConnection(RequestError): + pass + + +class InvalidInput(RequestError): + pass + + +class Undeliverable(RequestError): + pass + + +class NotRegistered(RequestError): + pass + + +class CodeIsExpired(RequestError): + pass + + +class InvalidMethod(RequestError): + pass + + +class UsernameExist(RequestError): + pass + + +class ErrorTryAgain(RequestError): + pass + + +class ErrorMessageTry(RequestError): + pass + + +class InternalProblem(RequestError): + pass + + +class ErrorMessageIgn(RequestError): + pass + + +class NotSupportedApiVersion(RequestError): + pass + + +class ExcetionsHandler: + def __init__(self, name) -> None: + self.name = name + + def __getattr__(self, name): + name = ''.join([chunk.title() for chunk in name.split('_')]) + return globals().get(name, ClientError) + + def __call__(self, name, *args, **kwargs): + return getattr(self, name) + +sys.modules[__name__] = ExcetionsHandler(__name__) \ No newline at end of file diff --git a/rubpy/filters.py b/rubpy/filters.py new file mode 100644 index 0000000..266319c --- /dev/null +++ b/rubpy/filters.py @@ -0,0 +1,182 @@ +import difflib +import inspect +import warnings +import sys +import re + +__all__ = ['Operator', 'BaseModel', 'RegexModel'] +__models__ = [ + 'is_pinned', 'is_mute', 'count_unseen', 'message_id', + 'is_group', 'is_private', 'is_channel', 'is_in_contact', + 'raw_text', 'original_update', 'object_guid', 'author_guid', 'time', 'reply_message_id'] + +def create(name, __base, authorise: list = [], exception: bool = True, *args, **kwargs): + result = None + if name in authorise: + result = name + + else: + proposal = difflib.get_close_matches(name, authorise, n=1) + if proposal: + result = proposal[0] + caller = inspect.getframeinfo(inspect.stack()[2][0]) + warnings.warn( + f'{caller.filename}:{caller.lineno}: do you mean' + f' "{name}", "{result}"? correct it') + + if result is not None or not exception: + if result is None: + result = name + return type(result, __base, {'__name__': result, **kwargs}) + + raise AttributeError(f'module has no attribute ({name})') + + +class Operator: + Or = 'OR' + And = 'AND' + Less = 'Less' + Lesse = 'Lesse' + Equal = 'Equal' + Greater = 'Greater' + Greatere = 'Greatere' + Inequality = 'Inequality' + + def __init__(self, value, operator, *args, **kwargs): + self.value = value + self.operator = operator + + def __eq__(self, value) -> bool: + return self.operator == value + + +class BaseModel: + def __init__(self, func=None, filters=[], *args, **kwargs) -> None: + self.func = func + if not isinstance(filters, list): + filters = [filters] + self.filters = filters + + def insert(self, filter): + self.filters.append(filter) + return self + + def __or__(self, value): + return self.insert(Operator(value, Operator.Or)) + + def __and__(self, value): + return self.insert(Operator(value, Operator.And)) + + def __eq__(self, value): + return self.insert(Operator(value, Operator.Equal)) + + def __ne__(self, value): + return self.insert(Operator(value, Operator.Inequality)) + + def __lt__(self, value): + return self.insert(Operator(value, Operator.Less)) + + def __le__(self, value): + return self.insert(Operator(value, Operator.Lesse)) + + def __gt__(self, value): + return self.insert(Operator(value, Operator.Greater)) + + def __ge__(self, value): + return self.insert(Operator(value, Operator.Greatere)) + + async def build(self, update): + # get key + result = getattr(update, self.__class__.__name__, None) + if callable(self.func): + if update.is_async(self.func): + result = await self.func(result) + else: + result = self.func(result) + + for filter in self.filters: + value = filter.value + + # if the comparison was with a function + if callable(value): + if update.is_async(value): + value = await value(update, result) + else: + value = value(update, result) + + if self.func: + if update.is_async(self.func): + value = await self.func(value) + else: + value = self.func(value) + + if filter == Operator.Or: + result = result or value + + elif filter == Operator.And: + result = result and value + + elif filter == Operator.Less: + result = result < value + + elif filter == Operator.Lesse: + result = result <= value + + elif filter == Operator.Equal: + result = result == value + + elif filter == Operator.Greater: + result = result > value + + elif filter == Operator.Greatere: + result = result >= value + + elif filter == Operator.Inequality: + result = result != value + + return bool(result) + + async def __call__(self, update, *args, **kwargs): + return await self.build(update) + + +class RegexModel(BaseModel): + def __init__(self, pattern, *args, **kwargs) -> None: + self.pattern = re.compile(pattern) + super().__init__(*args, **kwargs) + + async def __call__(self, update, *args, **kwargs) -> bool: + if update.raw_text is None: + return False + + update.pattern_match = self.pattern.match(update.raw_text) + return bool(update.pattern_match) + + +class Models: + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseModel in value.__bases__ + + def __dir__(self): + return sorted(__models__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name) + + def __getattr__(self, name): + if name in __all__: + return globals()[name] + return create(name, (BaseModel,), authorize=__models__, exception=False) + + # async def text(self): + # print('call text') + # return create('text', (BaseModel,), exception=False) + + # async def text(self, *args, **kwargs): + # print('called.') + # return Operator('text', Operator.Equal) + +sys.modules[__name__] = Models(__name__) \ No newline at end of file diff --git a/rubpy/handlers.py b/rubpy/handlers.py new file mode 100644 index 0000000..458ffb4 --- /dev/null +++ b/rubpy/handlers.py @@ -0,0 +1,96 @@ +import sys +import difflib +import inspect +import warnings +import asyncio +from .types import SocketResults + + +__handlers__ = [ + 'ChatUpdates', + 'MessageUpdates', + 'ShowActivities', + 'ShowNotifications', + 'RemoveNotifications' +] + +def create(name, __base, authorise: list = [], exception: bool = True, *args, **kwargs): + result = None + if name in authorise: + result = name + + else: + proposal = difflib.get_close_matches(name, authorise, n=1) + if proposal: + result = proposal[0] + caller = inspect.getframeinfo(inspect.stack()[2][0]) + warnings.warn( + f'{caller.filename}:{caller.lineno}: do you mean' + f' "{name}", "{result}"? correct it') + + if result is not None or not exception: + if result is None: + result = name + return type(result, __base, {'__name__': result, **kwargs}) + + raise AttributeError(f'module has no attribute ({name})') + + +class BaseHandlers(SocketResults): + __name__ = 'CustomHandlers' + + def __init__(self, *models, __any: bool = False, **kwargs) -> None: + self.__models = models + self.__any = __any + + def is_async(self, value, *args, **kwargs): + result = False + if asyncio.iscoroutinefunction(value): + result = True + + elif asyncio.iscoroutinefunction(value.__call__): + result = True + + return result + + async def __call__(self, update: dict, *args, **kwargs) -> bool: + self.original_update = update + if self.__models: + for filter in self.__models: + if callable(filter): + # if BaseModels is not called + if isinstance(filter, type): + filter = filter(func=None) + + if self.is_async(filter): + status = await filter(self, result=None) + + else: + status = filter(self, result=None) + + if status and self.__any: + return True + + elif not status: + return False + + return True + + +class Handlers: + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseHandlers in value.__bases__ + + def __dir__(self): + return sorted(__handlers__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name)(*args, **kwargs) + + def __getattr__(self, name): + return create(name, (BaseHandlers,), __handlers__) + +sys.modules[__name__] = Handlers(__name__) diff --git a/rubpy/network.py b/rubpy/network.py new file mode 100644 index 0000000..ae63980 --- /dev/null +++ b/rubpy/network.py @@ -0,0 +1,325 @@ +import asyncio +import aiohttp +import rubpy +import aiofiles +import os +from .crypto import Crypto +from . import exceptions +from .types import Results + +def capitalize(text: str): + return ''.join([c.title() for c in text.split('_')]) + +class Network: + HEADERS = {'user-agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/102.0.0.0 Safari/537.36'), + 'origin': 'https://web.rubika.ir', + 'referer': 'https://web.rubika.ir/'} + + def __init__(self, client: "rubpy.Client") -> None: + self.client = client + self.session = aiohttp.ClientSession( + headers=self.HEADERS, + timeout=aiohttp.ClientTimeout(client.timeout), + ) + + if client.bot_token is not None: + self.bot_api_url = f'https://messengerg2b1.iranlms.ir/v3/{client.bot_token}/' + + self.api_url = None + self.wss_url = None + + async def close(self): + await self.session.close() + + async def get_dcs(self): + try_count = 0 + + while True: + try: + async with self.session.get('https://getdcmess.iranlms.ir/', verify_ssl=False) as response: + if not response.ok: + continue + + response = (await response.json()).get('data') + + self.api_url = response.get('API').get(response.get('default_api')) + self.wss_url = response.get('socket').get(response.get('default_socket')) + return True + + except aiohttp.ServerTimeoutError: + try_count += 1 + print(f'Server timeout error ({try_count})') + await asyncio.sleep(try_count) + continue + + except aiohttp.ClientConnectionError: + try_count += 1 + print(f'Client connection error ({try_count})') + await asyncio.sleep(try_count) + continue + + async def request(self, url: str, data: dict): + for _ in range(3): + try: + async with self.session.request(method='POST', url=url, json=data, verify_ssl=False) as response: + if response.ok: + return await response.json() + + except aiohttp.ServerTimeoutError: + print('Rubika server timeout error, try again ({})'.format(_)) + + async def send(self, **kwargs): + api_version: str = str(kwargs.get('api_version', self.client.API_VERSION)) + auth: str = kwargs.get('auth', self.client.auth) + client: dict = kwargs.get('client', self.client.DEFAULT_PLATFORM) + input: dict = kwargs.get('input', {}) + method: str = kwargs.get('method', 'getUserInfo') + encrypt: bool = kwargs.get('encrypt', True) + tmp_session: bool = kwargs.get('tmp_session', False) + url: str = kwargs.get('url', self.api_url) + + data = dict( + api_version=api_version, + ) + + data['tmp_session' if tmp_session is True else 'auth'] = auth if tmp_session is True else Crypto.decode_auth(auth) + + if api_version == '6': + data_enc = dict( + client=client, + method=method, + input=input, + ) + + if encrypt is True: + data['data_enc'] = Crypto.encrypt(data_enc, key=self.client.key) + + if tmp_session is False: + data['sign'] = Crypto.sign(self.client.private_key, data['data_enc']) + + elif api_version == '0': + data['auth'] = auth + data['client'] = client + data['data'] = input + data['method'] = method + + elif api_version == '4': + data['client'] = client + data['method'] = method + + elif api_version == 'bot': + return await self.request( + url=self.bot_api_url + method, + data=input, + ) + + return await self.request(url, data=data) + + async def update_handler(self, update: dict): + data_enc: str = update.get('data_enc') + + if data_enc: + result = Crypto.decrypt(data_enc, key=self.client.key) + user_guid = result.pop('user_guid') + + for name, package in result.items(): + if not isinstance(package, list): + continue + + for update in package: + update['client'] = self.client + update['user_guid'] = user_guid + + for func, handler in self.client.handlers.items(): + try: + # if handler is empty filters + if isinstance(handler, type): + handler = handler() + + if handler.__name__ != capitalize(name): + continue + + # analyze handlers + if not await handler(update=update): + continue + + asyncio.create_task(func(handler)) + + except exceptions.StopHandler: + #print(error.message) + break + + except Exception: + pass + # self._client._logger.error( + # 'handler raised an exception', extra={'data': update}, exc_info=True) + + async def get_updates(self): + try_count = 0 + + while True: + try: + async with self.session.ws_connect(self.wss_url, verify_ssl=False) as wss: + self.ws_connect = wss + await self.send_json_to_ws() + asyncio.create_task(self.send_json_to_ws(data=True)) + + if try_count != 0: + print('The connection was re-established.') + try_count = 0 + + async for message in wss: + if message.type in (aiohttp.WSMsgType.CLOSED, + aiohttp.WSMsgType.ERROR): + await self.send_json_to_ws() + + message = message.json() + await self.update_handler(message) + + except aiohttp.ClientConnectionError: + try_count += 1 + print(f'Client connection error ({try_count})') + await asyncio.sleep(try_count) + + except ConnectionResetError: + try_count += 1 + print(f'Connection reset error ({try_count})') + await asyncio.sleep(try_count) + + async def send_json_to_ws(self, data=False): + if data: + while True: + #try: + await asyncio.sleep(10) + await self.ws_connect.send_json('{}') + + return await self.ws_connect.send_json( + { + 'method': 'handShake', + 'auth': self.client.auth, + 'api_version': '5', + 'data': '', + }) + + async def upload_file(self, file, mime: str = None, file_name: str = None, chunk: int = 1048576 * 2, + callback=None, *args, **kwargs): + if isinstance(file, str): + if not os.path.exists(file): + raise ValueError('file not found in the given path') + + if file_name is None: + file_name = os.path.basename(file) + + async with aiofiles.open(file, 'rb') as file: + file = await file.read() + + elif not isinstance(file, bytes): + raise TypeError('file arg value must be file path or bytes') + + if file_name is None: + raise ValueError('the file_name is not set') + + if mime is None: + mime = file_name.split('.')[-1] + + result = await self.client.request_send_file(file_name, len(file), mime) + + id = result.id + index = 0 + dc_id = result.dc_id + total = int(len(file) / chunk + 1) + upload_url = result.upload_url + access_hash_send = result.access_hash_send + + while index < total: + data = file[index * chunk: index * chunk + chunk] + try: + result = await self.session.post( + upload_url, + headers={ + 'auth': self.client.auth, + 'file-id': id, + 'total-part': str(total), + 'part-number': str(index + 1), + 'chunk-size': str(len(data)), + 'access-hash-send': access_hash_send + }, + data=data + ) + result = await result.json() + + if result.get('status') != 'OK': + raise exceptions.UploadError(result.get('status'), + result.get('status_det'), + dev_message=result.get('dev_message')) + + if callable(callback): + try: + await callback(len(file), index * chunk) + + except exceptions.CancelledError: + return None + + except Exception: + pass + + index += 1 + + except Exception: + pass + + status = result['status'] + status_det = result['status_det'] + + if status == 'OK' and status_det == 'OK': + result = { + 'mime': mime, + 'size': len(file), + 'dc_id': dc_id, + 'file_id': id, + 'file_name': file_name, + 'access_hash_rec': result['data']['access_hash_rec'] + } + + return Results(result) + + #self._client._logger.debug('upload failed', extra={'data': result}) + raise exceptions(status_det)(result, request=result) + + async def download(self, dc_id: int, file_id: int, access_hash: str, size: int, chunk=131072, callback=None): + url = f'https://messenger{dc_id}.iranlms.ir/GetFile.ashx' + start_index = 0 + result = b'' + + headers = { + 'auth': self.client.auth, + 'access-hash-rec': access_hash, + 'file-id': str(file_id), + 'user-agent': self.client.user_agent + } + + async with aiohttp.ClientSession() as session: + while True: + last_index = start_index + chunk - 1 if start_index + chunk < size else size - 1 + + headers['start-index'] = str(start_index) + headers['last-index'] = str(last_index) + + response = await session.post(url, headers=headers) + if response.ok: + data = await response.read() + if data: + result += data + if callback: + await callback(size, len(result)) + + # Check for the end of the file + if len(result) >= size: + break + + # Update the start_index value to fetch the next part of the file + start_index = last_index + 1 + + return result diff --git a/rubpy/utils.py b/rubpy/utils.py new file mode 100644 index 0000000..f066d87 --- /dev/null +++ b/rubpy/utils.py @@ -0,0 +1,47 @@ +import re + +RUBIKA_LINK_PATTERN = re.compile(r'\brubika\.ir\b') +GROUP_LINK_PATTERN = re.compile(r'https://rubika\.ir/joing/[A-Z0-9]+') +USERNAME_PATTERN = re.compile(r'@([a-zA-Z0-9_]{3,20})') + +def is_rubika_link(string: str) -> bool: + return bool(RUBIKA_LINK_PATTERN.search(string)) + +def is_group_link(string: str) -> bool: + return bool(GROUP_LINK_PATTERN.search(string)) + +def is_username(string: str) -> bool: + return bool(USERNAME_PATTERN.search(string)) + +def get_rubika_links(string: str) -> list: + return RUBIKA_LINK_PATTERN.findall(string) + +def get_group_links(string: str) -> list: + return GROUP_LINK_PATTERN.findall(string) + +def get_usernames(string: str) -> list: + return USERNAME_PATTERN.findall(string) + +def Bold(text: str) -> str: + return f'**{text.strip()}**' + +def Italic(text: str) -> str: + return f'__{text.strip()}__' + +def Underline(text: str) -> str: + return f'--{text.strip()}--' + +def Strike(text: str) -> str: + return f'~~{text.strip()}~~' + +def Spoiler(text: str) -> str: + return f'||{text.strip()}||' + +def Code(text: str): + return f'`{text.strip()}`' + +def Mention(text: str, object_guid: str) -> str: + return f'[{text.strip()}]({object_guid.strip()})' + +def HyperLink(text: str, link: str) -> str: + return f'[{text.strip()}]({link.strip()})' \ No newline at end of file