├── rubpy ├── crypto │ ├── __init__.py │ ├── __pycache__ │ │ ├── crypto.cpython-311.pyc │ │ └── __init__.cpython-311.pyc │ └── crypto.py ├── network │ ├── __init__.py │ ├── __pycache__ │ │ ├── proxies.cpython-311.pyc │ │ ├── __init__.cpython-311.pyc │ │ └── connection.cpython-311.pyc │ ├── connection.py │ └── proxies.py ├── sessions │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── sqliteSession.cpython-311.pyc │ │ └── stringSession.cpython-311.pyc │ ├── stringSession.py │ └── sqliteSession.py ├── __pycache__ │ ├── __init__.cpython-311.pyc │ └── client.cpython-311.pyc ├── gadgets │ ├── __init__.py │ ├── __pycache__ │ │ ├── methods.cpython-311.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── classino.cpython-311.pyc │ │ ├── exceptions.cpython-311.pyc │ │ ├── grouping.cpython-311.pyc │ │ └── thumbnail.cpython-311.pyc │ ├── classino.py │ ├── thumbnail.py │ ├── exceptions.py │ ├── methods.py │ └── grouping.py ├── structs │ ├── __init__.py │ ├── __pycache__ │ │ ├── models.cpython-311.pyc │ │ ├── results.cpython-311.pyc │ │ ├── struct.cpython-311.pyc │ │ ├── __init__.cpython-311.pyc │ │ └── handlers.cpython-311.pyc │ ├── results.py │ ├── handlers.py │ ├── models.py │ └── struct.py ├── __init__.py └── client.py ├── README.md ├── Fosh.txt └── main.py /rubpy/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | from .crypto import Crypto -------------------------------------------------------------------------------- /rubpy/network/__init__.py: -------------------------------------------------------------------------------- 1 | from .proxies import Proxies 2 | from .connection import Connection -------------------------------------------------------------------------------- /rubpy/sessions/__init__.py: -------------------------------------------------------------------------------- 1 | from .sqliteSession import SQLiteSession 2 | from .stringSession import StringSession -------------------------------------------------------------------------------- /rubpy/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/__pycache__/client.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/__pycache__/client.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__init__.py: -------------------------------------------------------------------------------- 1 | from . import methods 2 | from . import thumbnail 3 | from . import exceptions 4 | from .classino import Classino 5 | -------------------------------------------------------------------------------- /rubpy/structs/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import results 3 | from . import handlers 4 | from .struct import Struct 5 | 6 | -------------------------------------------------------------------------------- /rubpy/crypto/__pycache__/crypto.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/crypto/__pycache__/crypto.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/crypto/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/crypto/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/methods.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/methods.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/network/__pycache__/proxies.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/network/__pycache__/proxies.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/structs/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/structs/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/structs/__pycache__/results.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/structs/__pycache__/results.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/structs/__pycache__/struct.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/structs/__pycache__/struct.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/classino.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/classino.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/exceptions.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/exceptions.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/grouping.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/grouping.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/gadgets/__pycache__/thumbnail.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/gadgets/__pycache__/thumbnail.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/network/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/network/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/network/__pycache__/connection.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/network/__pycache__/connection.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/sessions/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/sessions/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/structs/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/structs/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/structs/__pycache__/handlers.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/structs/__pycache__/handlers.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/sessions/__pycache__/sqliteSession.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/sessions/__pycache__/sqliteSession.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/sessions/__pycache__/stringSession.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scorpian-my/Logo-AI-Bot/HEAD/rubpy/sessions/__pycache__/stringSession.cpython-311.pyc -------------------------------------------------------------------------------- /rubpy/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .network import Proxies 3 | from .structs import handlers, models 4 | from .structs.struct import Struct as Message 5 | from .gadgets import exceptions, methods 6 | 7 | __version__ = '6.2.4' 8 | __author__ = 'Shayan Heidari' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logo-AI-Bot 2 | [Support](https:/rubika.ir/Pyrogram) 3 |

4 | 5 | It is a robot for the messenger Rubika 6 | **Please note that this robot is not complete and needs to be developed, and this is the purpose of writing it, I hope it will be used to gain useful knowledge and information.** 7 |
8 | 9 | ``` 10 | pip uninstall rubpy -y 11 | pip install persiantools 12 | pip install requests 13 | ``` 14 |

Logo Creator And AI Text To Image

15 | 16 |

Thanks to the Haji API team for completing the project

17 | 18 | 19 | -------------------------------------------------------------------------------- /rubpy/structs/results.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .struct import Struct 3 | from ..gadgets import Classino 4 | 5 | 6 | class BaseResults(Struct): 7 | __type__ = 'CustomResult' 8 | 9 | def __init__(self, update, *args, **kwargs) -> None: 10 | self.original_update = update 11 | 12 | 13 | class Results(Classino): 14 | def __init__(self, name, *args, **kwargs) -> None: 15 | self.__name__ = name 16 | 17 | def __eq__(self, value: object) -> bool: 18 | return BaseResults in value.__bases__ 19 | 20 | def __call__(self, name, *args, **kwargs): 21 | return self.__getattr__(name)(*args, **kwargs) 22 | 23 | def __getattr__(self, name): 24 | return self.create(name, (BaseResults, ), exception=False) 25 | 26 | 27 | sys.modules[__name__] = Results(__name__) 28 | -------------------------------------------------------------------------------- /rubpy/gadgets/classino.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | import inspect 3 | import warnings 4 | 5 | class Classino: 6 | 7 | @classmethod 8 | def create(cls, name, __base, authorise: list = [], exception: bool = True, *args, **kwargs): 9 | result = None 10 | if name in authorise: 11 | result = name 12 | 13 | else: 14 | proposal = difflib.get_close_matches(name, authorise, n=1) 15 | if proposal: 16 | result = proposal[0] 17 | caller = inspect.getframeinfo(inspect.stack()[2][0]) 18 | warnings.warn( 19 | f'{caller.filename}:{caller.lineno}: do you mean' 20 | f' "{name}", "{result}"? correct it') 21 | 22 | if result is not None or not exception: 23 | if result is None: 24 | result = name 25 | return type(result, __base, {'__name__': result, **kwargs}) 26 | 27 | print(f'module has no attribute ({name})') -------------------------------------------------------------------------------- /rubpy/sessions/stringSession.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | 4 | 5 | class StringSession(object): 6 | def __init__(self, session: str = None) -> None: 7 | self.session = self.load(session) 8 | 9 | @classmethod 10 | def load(cls, session): 11 | if isinstance(session, str): 12 | return json.loads(base64.b64decode(session)) 13 | 14 | @classmethod 15 | def dump(cls, session): 16 | if isinstance(session, list): 17 | session = json.dumps(session).encode('utf-8') 18 | return base64.b64encode(session).decode('utf-8') 19 | 20 | @classmethod 21 | def from_sqlite(cls, session): 22 | session = cls.dump(session.information()) 23 | return StringSession(session) 24 | 25 | def insert(self, phone_number, auth, guid, user_agent, *args, **kwargs): 26 | self.session = [phone_number, auth, guid, user_agent] 27 | 28 | def information(self): 29 | return self.session 30 | 31 | def save(self, file_name=None): 32 | result = self.dump(self.session) 33 | if result is None: 34 | if isinstance(file_name, str): 35 | if not file_name.endswith('.txt'): 36 | file_name += '.txt' 37 | with open(file_name, 'w+') as file: 38 | file.write(result) 39 | return result 40 | -------------------------------------------------------------------------------- /Fosh.txt: -------------------------------------------------------------------------------- 1 | Nude 2 | nude 3 | node 4 | noode 5 | nuude 6 | naked 7 | vagina 8 | penis 9 | bitch 10 | breast 11 | horny 12 | hot 13 | women 14 | xnxx 15 | sex 16 | porn 17 | leasbian 18 | gay 19 | alexis 20 | pussy 21 | dick 22 | deck 23 | boob 24 | ass 25 | xxx 26 | pornhub 27 | lgbt 28 | bdsm 29 | bitch 30 | fuck 31 | step 32 | mother 33 | jews 34 | milf 35 | women 36 | pornhub.com 37 | anal 38 | xnxx 39 | xvideo 40 | کیر 41 | کص 42 | کون 43 | کس ننت 44 | کوس 45 | کوص 46 | ممه 47 | ننت 48 | بی ناموس 49 | بیناموس 50 | بیناموص 51 | بی ناموص 52 | گایید 53 | جنده 54 | جندع 55 | جیندا 56 | پستون 57 | کسکش 58 | ننه کس 59 | اوبی 60 | هرزه 61 | قحبه 62 | عنتر 63 | فاک 64 | کسعمت 65 | کصخل 66 | کسخل 67 | تخمی 68 | سکس 69 | صکص 70 | کسخول 71 | کسشر 72 | کسشعر 73 | کیر 74 | ساک 75 | لواط 76 | روبیکا 77 | لز 78 | کسلیس 79 | لیس 80 | کیرم 81 | گوه 82 | کُس 83 | نوب 84 | خارکسه 85 | کاندوم 86 | سکسی 87 | خودکشی 88 | جنازه 89 | هک 90 | دزد 91 | بمبر 92 | لیسر 93 | فیشینگ 94 | باسن 95 | جسد 96 | خارکسده 97 | لاشی 98 | لاشین 99 | مستهجن 100 | مستحجن 101 | مخ 102 | خایه 103 | خامنه 104 | خمینی 105 | کسشعر 106 | کصشعر 107 | واژن 108 | ننه 109 | کسمادرت 110 | کصمادرت 111 | حشری 112 | اوث 113 | کشتم 114 | اوت 115 | جانی 116 | مادرت 117 | خواهرت 118 | ننت 119 | دول 120 | حرومزاده 121 | کس 122 | لخت شو 123 | فیلتر 124 | کصشر 125 | kos 126 | کسشر 127 | فیلترینگ 128 | جق 129 | خودارضایی 130 | بیناموس 131 | فاحشه 132 | تخمم 133 | الکسیس 134 | تجاوز 135 | لوات 136 | کسکش 137 | dick 138 | هرزه 139 | purn 140 | آلت 141 | Porn 142 | دخول 143 | الت 144 | ممه 145 | ارضا 146 | ارضایی 147 | پدرت 148 | مادر 149 | کون 150 | +18 151 | +۱۸ 152 | قاتل 153 | کونی 154 | کوس 155 | کوص 156 | کصکش 157 | جنده 158 | پورن 159 | قتل 160 | سکس 161 | کصپدر 162 | دولی 163 | گی 164 | کصخل 165 | جنسی 166 | porn 167 | fuck 168 | sex 169 | کسخل 170 | mother 171 | کسننت 172 | کصننت 173 | نود بده 174 | نود بفرست 175 | نود 176 | گِی 177 | حامله 178 | کص 179 | سوپر 180 | باردار 181 | اسپرم 182 | عمت 183 | پستان 184 | پستون 185 | خرابکار 186 | -------------------------------------------------------------------------------- /rubpy/structs/handlers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from .struct import Struct 4 | from ..gadgets import Classino 5 | 6 | __handlers__ = [ 7 | 'ChatUpdates', 8 | 'MessageUpdates', 9 | 'ShowActivities', 10 | 'ShowNotifications', 11 | 'RemoveNotifications' 12 | ] 13 | 14 | 15 | class BaseHandlers(Struct): 16 | __name__ = 'CustomHandlers' 17 | 18 | def __init__(self, *models, __any: bool = False, **kwargs) -> None: 19 | self.__models = models 20 | self.__any = __any 21 | 22 | def is_async(self, value, *args, **kwargs): 23 | result = False 24 | if asyncio.iscoroutinefunction(value): 25 | result = True 26 | 27 | elif asyncio.iscoroutinefunction(value.__call__): 28 | result = True 29 | 30 | return result 31 | 32 | async def __call__(self, update: dict, *args, **kwargs) -> bool: 33 | self.original_update = update 34 | if self.__models: 35 | for filter in self.__models: 36 | if callable(filter): 37 | # if BaseModels is not called 38 | if isinstance(filter, type): 39 | filter = filter(func=None) 40 | 41 | if self.is_async(filter): 42 | status = await filter(self, result=None) 43 | 44 | else: 45 | status = filter(self, result=None) 46 | 47 | if status and self.__any: 48 | return True 49 | 50 | elif not status: 51 | return False 52 | 53 | return True 54 | 55 | 56 | class Handlers(Classino): 57 | def __init__(self, name, *args, **kwargs) -> None: 58 | self.__name__ = name 59 | 60 | def __eq__(self, value: object) -> bool: 61 | return BaseHandlers in value.__bases__ 62 | 63 | def __dir__(self): 64 | return sorted(__handlers__) 65 | 66 | def __call__(self, name, *args, **kwargs): 67 | return self.__getattr__(name)(*args, **kwargs) 68 | 69 | def __getattr__(self, name): 70 | return self.create(name, (BaseHandlers,), __handlers__) 71 | 72 | sys.modules[__name__] = Handlers(__name__) 73 | -------------------------------------------------------------------------------- /rubpy/sessions/sqliteSession.py: -------------------------------------------------------------------------------- 1 | # import os 2 | import sqlite3 3 | 4 | suffix = '.rbs' 5 | rbs_version = 1 6 | 7 | 8 | class SQLiteSession(object): 9 | 10 | def __init__(self, session: str) -> None: 11 | self.filename = session 12 | if not session.endswith(suffix): 13 | self.filename += suffix 14 | 15 | self._connection = sqlite3.connect(self.filename, 16 | check_same_thread=False) 17 | cursor = self._connection.cursor() 18 | cursor.execute('select name from sqlite_master ' 19 | 'where type=? and name=?', ('table', 'version')) 20 | if cursor.fetchone(): 21 | cursor.execute('select version from version') 22 | version = cursor.fetchone()[0] 23 | if rbs_version != version: 24 | self.upgrade_database(version) 25 | 26 | else: 27 | cursor.execute( 28 | 'create table version (version integer primary key)') 29 | cursor.execute('insert into version values (?)', (rbs_version,)) 30 | cursor.execute('create table session (phone text primary key' 31 | ', auth text, guid text, agent text, private_key text)') 32 | self._connection.commit() 33 | cursor.close() 34 | 35 | def upgrade_database(self, version): 36 | pass 37 | 38 | def information(self): 39 | cursor = self._connection.cursor() 40 | cursor.execute('select * from session') 41 | result = cursor.fetchone() 42 | cursor.close() 43 | return result 44 | 45 | def insert(self, phone_number, auth, guid, user_agent, private_key, *args, **kwargs): 46 | cursor = self._connection.cursor() 47 | cursor.execute( 48 | 'insert or replace into session (phone, auth, guid, agent, private_key)' 49 | ' values (?, ?, ?, ?, ?)', 50 | (phone_number, auth, guid, user_agent, private_key) 51 | ) 52 | self._connection.commit() 53 | cursor.close() 54 | 55 | @classmethod 56 | def from_string(cls, session, file_name=None): 57 | info = session.information() 58 | if file_name is None: 59 | if info is None: 60 | raise ValueError('file_name arg is not set') 61 | file_name = info[0] 62 | 63 | session = SQLiteSession(file_name) 64 | if info is not None: 65 | session.insert(*info) 66 | 67 | return session 68 | -------------------------------------------------------------------------------- /rubpy/gadgets/thumbnail.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import base64 4 | import tempfile 5 | 6 | 7 | try: 8 | import cv2 9 | import numpy 10 | 11 | except ImportError: 12 | cv2 = None 13 | numpy = None 14 | 15 | 16 | class Thumbnail: 17 | def __init__(self, 18 | image: bytes, 19 | width: int = 200, 20 | height: int = 200, 21 | seconds: int = 1, *args, **kwargs) -> None: 22 | 23 | self.image = image 24 | self.width = width 25 | self.height = height 26 | self.seconds = seconds 27 | 28 | if isinstance(self.image, str): 29 | with open(image, 'rb') as file: 30 | self.image = file.read() 31 | 32 | def to_base64(self, *args, **kwargs) -> str: 33 | if self.image is not None: 34 | return base64.b64encode(self.image).decode('utf-8') 35 | 36 | 37 | class MakeThumbnail(Thumbnail): 38 | def __init__(self, 39 | image, 40 | width: int = 200, 41 | height: int = 200, 42 | seconds: int = 1, *args, **kwargs) -> None: 43 | self.image = None 44 | self.width = width 45 | self.height = height 46 | self.seconds = seconds 47 | if hasattr(cv2, 'imdecode'): 48 | if not isinstance(image, numpy.ndarray): 49 | image = numpy.frombuffer(image, dtype=numpy.uint8) 50 | image = cv2.imdecode(image, flags=1) 51 | 52 | self.image = self.ndarray_to_bytes(image) 53 | 54 | def ndarray_to_bytes(self, image, *args, **kwargs) -> str: 55 | if hasattr(cv2, 'resize'): 56 | self.width = image.shape[1] 57 | self.height = image.shape[0] 58 | image = cv2.resize(image, 59 | (round(self.width / 10), round(self.height / 10)), 60 | interpolation=cv2.INTER_CUBIC) 61 | 62 | status, buffer = cv2.imencode('.png', image) 63 | if status is True: 64 | return io.BytesIO(buffer).read() 65 | 66 | @classmethod 67 | def from_video(cls, video: bytes, *args, **kwargs): 68 | if hasattr(cv2, 'VideoCapture'): 69 | with tempfile.TemporaryFile(mode='wb+') as file: 70 | file.write(video) 71 | capture = cv2.VideoCapture(file.name) 72 | status, image = capture.read() 73 | if status is True: 74 | fps = capture.get(cv2.CAP_PROP_FPS) 75 | frames = capture.get(cv2.CAP_PROP_FRAME_COUNT) 76 | return MakeThumbnail( 77 | image=image, 78 | seconds=int(frames / fps), *args, **kwargs) 79 | -------------------------------------------------------------------------------- /rubpy/gadgets/exceptions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class ClientError(Exception): 4 | pass 5 | 6 | class SocksError(ClientError): 7 | pass 8 | 9 | class StopHandler(ClientError): 10 | pass 11 | 12 | class CancelledError(ClientError): 13 | pass 14 | 15 | 16 | class UnknownAuthMethod(SocksError): 17 | pass 18 | 19 | 20 | class InvalidServerReply(SocksError): 21 | pass 22 | 23 | 24 | class SocksConnectionError(SocksError): 25 | pass 26 | 27 | 28 | class InvalidServerVersion(SocksError): 29 | pass 30 | 31 | 32 | class NoAcceptableAuthMethods(SocksError): 33 | pass 34 | 35 | 36 | class LoginAuthenticationFailed(SocksError): 37 | pass 38 | 39 | 40 | class RequestError(ClientError): 41 | def __init__(self, message, request=None): 42 | self.message = str(message) 43 | self.request = request 44 | 45 | 46 | class CodeIsUsed(RequestError): 47 | pass 48 | 49 | 50 | class TooRequests(RequestError): 51 | pass 52 | 53 | 54 | class InvalidAuth(RequestError): 55 | pass 56 | 57 | 58 | class ServerError(RequestError): 59 | pass 60 | 61 | 62 | class UrlNotFound(RequestError): 63 | pass 64 | 65 | 66 | class ErrorAction(RequestError): 67 | pass 68 | 69 | 70 | class ErrorIgnore(RequestError): 71 | pass 72 | 73 | 74 | class ErrorGeneric(RequestError): 75 | pass 76 | 77 | 78 | class NoConnection(RequestError): 79 | pass 80 | 81 | 82 | class InvalidInput(RequestError): 83 | pass 84 | 85 | 86 | class Undeliverable(RequestError): 87 | pass 88 | 89 | 90 | class NotRegistered(RequestError): 91 | pass 92 | 93 | 94 | class CodeIsExpired(RequestError): 95 | pass 96 | 97 | 98 | class InvalidMethod(RequestError): 99 | pass 100 | 101 | 102 | class UsernameExist(RequestError): 103 | pass 104 | 105 | 106 | class NotRegistrred(RequestError): 107 | pass 108 | 109 | 110 | class ErrorTryAgain(RequestError): 111 | pass 112 | 113 | 114 | class ErrorMessageTry(RequestError): 115 | pass 116 | 117 | 118 | class InternalProblem(RequestError): 119 | pass 120 | 121 | 122 | class ErrorMessageIgn(RequestError): 123 | pass 124 | 125 | 126 | class NotSupportedApiVersion(RequestError): 127 | pass 128 | 129 | 130 | class ExcetionsHandler: 131 | def __init__(self, name) -> None: 132 | self.name = name 133 | 134 | def __getattr__(self, name): 135 | name = ''.join([chunk.title() for chunk in name.split('_')]) 136 | return globals().get(name, ClientError) 137 | 138 | def __call__(self, name, *args, **kwargs): 139 | return getattr(self, name) 140 | 141 | sys.modules[__name__] = ExcetionsHandler(__name__) -------------------------------------------------------------------------------- /rubpy/crypto/crypto.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import base64 4 | import string 5 | import secrets 6 | from json import JSONDecoder 7 | from Crypto.Cipher import AES 8 | from Crypto.Hash import SHA256 9 | from Crypto.Signature import pkcs1_15 10 | from Crypto.PublicKey import RSA 11 | from Crypto.Cipher import PKCS1_OAEP 12 | from string import ascii_lowercase, ascii_uppercase 13 | 14 | class Crypto(object): 15 | AES_IV = b'\x00' * 16 16 | 17 | def decode_auth(auth: str) -> str: 18 | result, digits = '', '0123456789' 19 | for char in auth: 20 | if char in ascii_lowercase: 21 | result += chr(((32 - (ord(char) - 97)) % 26) + 97) 22 | elif char in ascii_uppercase: 23 | result += chr(((29- (ord(char) - 65)) % 26) + 65) 24 | elif char in digits: 25 | result += chr(((13 - (ord(char)- 48)) % 10) + 48) 26 | else: 27 | result += char 28 | return result 29 | 30 | @classmethod 31 | def passphrase(cls, auth): 32 | if len(auth) != 32: 33 | raise ValueError('auth length should be 32 digits') 34 | 35 | result = '' 36 | chunks = re.findall(r'\S{8}', auth) 37 | for character in (chunks[2] + chunks[0] + chunks[3] + chunks[1]): 38 | result += chr(((ord(character) - 97 + 9) % 26) + 97) 39 | return result 40 | 41 | @classmethod 42 | def secret(cls, length): 43 | return ''.join(secrets.choice(string.ascii_lowercase) 44 | for _ in range(length)) 45 | 46 | @classmethod 47 | def decrypt(cls, data, key): 48 | decoder = JSONDecoder() 49 | cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) 50 | result, _ = decoder.raw_decode(cipher.decrypt( 51 | base64.b64decode(data)).decode('utf-8')) 52 | return result 53 | 54 | @classmethod 55 | def encrypt(cls, data, key): 56 | if not isinstance(data, str): 57 | data = json.dumps(data, default=lambda x: str(x)) 58 | cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) 59 | length = 16 - (len(data) % 16) 60 | data += chr(length) * length 61 | return ( 62 | base64.b64encode(cipher.encrypt(data.encode('utf-8'))) 63 | .decode('utf-8') 64 | ) 65 | 66 | def sign(private_key: str, data: str) -> str: 67 | key = RSA.import_key(private_key.encode('utf-8')) 68 | signature = pkcs1_15.new(key).sign( 69 | SHA256.new(data.encode('utf-8'))) 70 | return base64.b64encode(signature).decode('utf-8') 71 | 72 | def create_keys() -> tuple: 73 | keys = RSA.generate(1024) 74 | public_key = Crypto.decode_auth(base64.b64encode(keys.publickey().export_key()).decode('utf-8')) 75 | private_key = keys.export_key().decode('utf-8') 76 | return public_key, private_key 77 | 78 | def decrypt_RSA_OAEP(private_key: str, data: str): 79 | key = RSA.import_key(private_key.encode('utf-8')) 80 | return PKCS1_OAEP.new(key).decrypt(base64.b64decode(data)).decode('utf-8') -------------------------------------------------------------------------------- /rubpy/structs/models.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import asyncio 4 | from ..gadgets import Classino 5 | 6 | __all__ = ['Operator', 'BaseModels', 'RegexModel'] 7 | __models__ = [ 8 | 'is_pinned', 'is_mute', 'count_unseen', 'message_id', 9 | 'is_group', 'is_private', 'is_channel', 'is_in_contact', 10 | 'raw_text', 'original_update', 'object_guid', 'author_guid', 'time', 'reply_message_id'] 11 | 12 | class Operator: 13 | Or = 'OR' 14 | And = 'AND' 15 | Less = 'Less' 16 | Lesse = 'Lesse' 17 | Equal = 'Equal' 18 | Greater = 'Greater' 19 | Greatere = 'Greatere' 20 | Inequality = 'Inequality' 21 | 22 | def __init__(self, value, operator, *args, **kwargs): 23 | self.value = value 24 | self.operator = operator 25 | 26 | def __eq__(self, value) -> bool: 27 | return self.operator == value 28 | 29 | 30 | class BaseModels: 31 | __name__ = 'CustomModels' 32 | 33 | def __init__(self, 34 | func=None, filters=[], *args, **kwargs) -> None: 35 | self.func = func 36 | if not isinstance(filters, list): 37 | filters = [filters] 38 | self.filters = filters 39 | 40 | def insert(self, filter): 41 | self.filters.append(filter) 42 | return self 43 | 44 | def __or__(self, value): 45 | return self.insert(Operator(value, Operator.Or)) 46 | 47 | def __and__(self, value): 48 | return self.insert(Operator(value, Operator.And)) 49 | 50 | def __eq__(self, value): 51 | return self.insert(Operator(value, Operator.Equal)) 52 | 53 | def __ne__(self, value): 54 | return self.insert(Operator(value, Operator.Inequality)) 55 | 56 | def __lt__(self, value): 57 | return self.insert(Operator(value, Operator.Less)) 58 | 59 | def __le__(self, value): 60 | return self.insert(Operator(value, Operator.Lesse)) 61 | 62 | def __gt__(self, value): 63 | return self.insert(Operator(value, Operator.Greater)) 64 | 65 | def __ge__(self, value): 66 | return self.insert(Operator(value, Operator.Greatere)) 67 | 68 | async def build(self, update): 69 | # get key 70 | result = getattr(update, self.__name__, None) 71 | if callable(self.func): 72 | if update.is_async(self.func): 73 | result = await self.func(result) 74 | else: 75 | result = self.func(result) 76 | 77 | for filter in self.filters: 78 | value = filter.value 79 | 80 | # if the comparison was with a function 81 | if callable(value): 82 | if update.is_async(value): 83 | value = await value(update, result) 84 | else: 85 | value = value(update, result) 86 | 87 | if self.func: 88 | if update.is_async(self.func): 89 | value = await self.func(value) 90 | else: 91 | value = self.func(value) 92 | 93 | if filter == Operator.Or: 94 | result = result or value 95 | 96 | elif filter == Operator.And: 97 | result = result and value 98 | 99 | elif filter == Operator.Less: 100 | result = result < value 101 | 102 | elif filter == Operator.Lesse: 103 | result = result <= value 104 | 105 | elif filter == Operator.Equal: 106 | result = result == value 107 | 108 | elif filter == Operator.Greater: 109 | result = result > value 110 | 111 | elif filter == Operator.Greatere: 112 | result = result >= value 113 | 114 | elif filter == Operator.Inequality: 115 | result = result != value 116 | 117 | return bool(result) 118 | 119 | async def __call__(self, update, *args, **kwargs): 120 | return await self.build(update) 121 | 122 | 123 | class RegexModel(BaseModels): 124 | def __init__(self, pattern, *args, **kwargs) -> None: 125 | self.pattern = re.compile(pattern) 126 | super().__init__(*args, **kwargs) 127 | 128 | def __call__(self, update, *args, **kwargs) -> bool: 129 | if update.raw_text is None: 130 | return False 131 | 132 | update.pattern_match = self.pattern.match(update.raw_text) 133 | return bool(update.pattern_match) 134 | 135 | 136 | class Models(Classino): 137 | def __init__(self, name, *args, **kwargs) -> None: 138 | self.__name__ = name 139 | 140 | def __eq__(self, value: object) -> bool: 141 | return BaseModels in value.__bases__ 142 | 143 | def __dir__(self): 144 | return sorted(__models__) 145 | 146 | def __call__(self, name, *args, **kwargs): 147 | return self.__getattr__(name) 148 | 149 | def __getattr__(self, name): 150 | if name in __all__: 151 | return globals()[name] 152 | return self.create(name, (BaseModels, ), 153 | authorise=__models__, exception=False) 154 | 155 | 156 | sys.modules[__name__] = Models(__name__) 157 | -------------------------------------------------------------------------------- /rubpy/network/connection.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | import aiofiles 4 | import aiohttp 5 | from random import randint 6 | from time import time 7 | from ..crypto import Crypto 8 | from ..structs import results 9 | from ..gadgets import exceptions, methods 10 | 11 | 12 | def capitalize(text): 13 | return ''.join([ 14 | c.title() for c in text.split('_')]) 15 | 16 | 17 | class Connection: 18 | """Internal class""" 19 | 20 | def __init__(self, client): 21 | self._client = client 22 | self._connection = aiohttp.ClientSession( 23 | connector=self._client._proxy, 24 | headers={'user-agent': self._client._user_agent, 25 | 'origin': 'https://web.rubika.ir', 26 | 'referer': 'https://web.rubika.ir/'}, 27 | timeout=aiohttp.ClientTimeout(total=self._client._timeout)) 28 | 29 | async def _dcs(self): 30 | if not self._client._dcs: 31 | self._client._dcs = await self.execute( 32 | methods.authorisations.GetDCs()) 33 | 34 | return self._client._dcs 35 | 36 | async def close(self): 37 | await self._connection.close() 38 | 39 | async def upload_file(self, file, 40 | mime: str = None, file_name: str = None, 41 | chunk: int = 131072, callback=None, *args, **kwargs): 42 | if isinstance(file, str): 43 | if not os.path.exists(file): 44 | raise ValueError('file not found in the given path') 45 | if file_name is None: 46 | file_name = os.path.basename(file) 47 | 48 | async with aiofiles.open(file, 'rb') as f: 49 | file = await file.read() 50 | await f.close() 51 | 52 | elif not isinstance(file, bytes): 53 | raise TypeError('file arg value must be file path or bytes') 54 | 55 | if file_name is None: 56 | raise ValueError('the file_name is not set') 57 | 58 | if mime is None: 59 | mime = file_name.split('.')[-1] 60 | 61 | result = await self.execute( 62 | methods.messages.RequestSendFile( 63 | mime=mime, size=len(file), file_name=file_name)) 64 | 65 | id = result.id 66 | index = 0 67 | dc_id = result.dc_id 68 | total = int(len(file) / chunk + 1) 69 | upload_url = result.upload_url 70 | access_hash_send = result.access_hash_send 71 | 72 | while index < total: 73 | data = file[index * chunk: index * chunk + chunk] 74 | try: 75 | result = await self._connection.post( 76 | upload_url, 77 | headers={ 78 | 'auth': self._client._auth, 79 | 'file-id': id, 80 | 'total-part': str(total), 81 | 'part-number': str(index + 1), 82 | 'chunk-size': str(len(data)), 83 | 'access-hash-send': access_hash_send 84 | }, 85 | data=data 86 | ) 87 | result = await result.json() 88 | if callable(callback): 89 | try: 90 | await callback(len(file), index * chunk) 91 | 92 | except exceptions.CancelledError: 93 | return None 94 | 95 | except Exception: 96 | pass 97 | 98 | index += 1 99 | except Exception: 100 | pass 101 | 102 | status = result['status'] 103 | status_det = result['status_det'] 104 | if status == 'OK' and status_det == 'OK': 105 | result = { 106 | 'mime': mime, 107 | 'size': len(file), 108 | 'dc_id': dc_id, 109 | 'file_id': id, 110 | 'file_name': file_name, 111 | 'access_hash_rec': result['data']['access_hash_rec'] 112 | } 113 | 114 | return results('UploadFile', result) 115 | 116 | self._client._logger.debug('upload failed', extra={'data': result}) 117 | raise exceptions(status_det)(result, request=result) 118 | 119 | async def execute(self, request: dict): 120 | if not isinstance(request, dict): 121 | request = request() 122 | 123 | self._client._logger.info('execute method', extra={'data': request}) 124 | method_urls = request.pop('urls') 125 | if method_urls is None: 126 | method_urls = (await self._dcs()).default_api_urls 127 | 128 | if not method_urls: 129 | raise exceptions.UrlNotFound( 130 | 'It seems that the client could not' 131 | ' get the list of Rubika api\'s.' 132 | ' Please wait and try again.', 133 | request=request) 134 | 135 | method = request['method'] 136 | tmp_session = request.pop('tmp_session') 137 | if self._client._auth is None: 138 | self._client._auth = Crypto.secret(length=32) 139 | self._client._logger.info( 140 | 'create auth secret', extra={'data': self._client._auth}) 141 | 142 | if self._client._key is None: 143 | self._client._key = Crypto.passphrase(self._client._auth) 144 | self._client._logger.info( 145 | 'create key passphrase', extra={'data': self._client._key}) 146 | 147 | request['client'] = self._client._platform 148 | if request.get('encrypt') is True: 149 | request = {'data_enc': Crypto.encrypt(request, key=self._client._key)} 150 | 151 | request['tmp_session' if tmp_session else 'auth'] = self._client._auth 152 | 153 | if 'api_version' not in request: 154 | request['api_version'] = self._client.configuire['api_version'] 155 | 156 | if request['api_version'] == '6' and tmp_session == False: 157 | request['auth'] = Crypto.decode_auth(request['auth']) 158 | request['sign'] = Crypto.sign(self._client._private_key, request['data_enc']) 159 | 160 | if not method_urls[0].startswith('https://getdcmess'): 161 | method_urls = [method_urls[0]] * 3 162 | 163 | for _ in range(self._client._request_retries): 164 | for url in method_urls: 165 | try: 166 | #async with self._connection.options(url) as result: 167 | # if result.status != 200: 168 | # continue 169 | 170 | async with self._connection.post(url, json=request) as result: 171 | if result.status != 200: 172 | continue 173 | 174 | result = await result.json() 175 | if result.get('data_enc'): 176 | result = Crypto.decrypt(result['data_enc'], 177 | key=self._client._key) 178 | status = result['status'] 179 | status_det = result['status_det'] 180 | if status == 'OK' and status_det == 'OK': 181 | result['data']['_client'] = self._client 182 | return results(method, update=result['data']) 183 | 184 | self._client._logger.warning( 185 | 'request status ' 186 | + capitalize(status_det), extra={'data': request}) 187 | 188 | raise exceptions(status_det)(result, request=request) 189 | 190 | except aiohttp.ServerTimeoutError: 191 | pass 192 | 193 | raise exceptions.InternalProblem( 194 | 'rubika server has an internal problem', request=request) 195 | 196 | async def download(self, dc_id, file_id, 197 | access_hash, chunk=131072, callback=None): 198 | url = (await self._dcs()).storages[str(dc_id)] 199 | headers = { 200 | 'file-id': str(file_id), 201 | 'auth': self._client._auth, 202 | 'access-hash-rec': access_hash} 203 | async with self._connection.post(url, headers=headers) as result: 204 | if result.status != 200: 205 | raise exceptions.InternalProblem( 206 | 'the server is not responding') 207 | 208 | data = b'' 209 | total = int(result.headers.get('total_length', None)) 210 | 211 | async for chunk in result.content.iter_chunked(chunk): 212 | data += chunk 213 | if callable(callback): 214 | try: 215 | await callback(total, len(data)) 216 | 217 | except exceptions.CancelledError: 218 | self._client._logger.info( 219 | 'download media cancelled (%s) (%s)', headers, url) 220 | return None 221 | 222 | except Exception: 223 | pass 224 | 225 | return data 226 | 227 | async def handel_update(self, name, update): 228 | handlers = self._client._handlers.copy() 229 | for func, handler in handlers.items(): 230 | try: 231 | # if handler is empty filters 232 | if isinstance(handler, type): 233 | handler = handler() 234 | 235 | if handler.__name__ != capitalize(name): 236 | continue 237 | 238 | # analyze handlers 239 | if not await handler(update=update): 240 | continue 241 | 242 | await func(handler) 243 | 244 | except exceptions.StopHandler: 245 | break 246 | 247 | except Exception: 248 | self._client._logger.error( 249 | 'handler raised an exception', 250 | extra={'data': update}, exc_info=True) 251 | 252 | async def receive_updates(self): 253 | default_sockets = (await self._dcs()).default_sockets 254 | asyncio.create_task(self.to_keep_alive()) 255 | for url in default_sockets: 256 | async with self._connection.ws_connect(url) as wss: 257 | await self.send_data_to_ws(wss) 258 | asyncio.create_task(self.keep_socket(wss)) 259 | self._client._logger.info( 260 | 'start receiving updates', extra={'data': url}) 261 | async for message in wss: 262 | if message.type in (aiohttp.WSMsgType.CLOSED, 263 | aiohttp.WSMsgType.ERROR): 264 | await wss.send_json() 265 | try: 266 | result = message.json() 267 | if not result.get('data_enc'): 268 | self._client._logger.debug( 269 | 'the data_enc key was not found', 270 | extra={'data': result}) 271 | continue 272 | 273 | result = Crypto.decrypt(result['data_enc'], 274 | key=self._client._key) 275 | user_guid = result.pop('user_guid') 276 | for name, package in result.items(): 277 | if not isinstance(package, list): 278 | continue 279 | 280 | for update in package: 281 | update['_client'] = self._client 282 | update['user_guid'] = user_guid 283 | asyncio.create_task( 284 | self.handel_update(name, update)) 285 | 286 | except Exception: 287 | self._client._logger.error( 288 | 'websocket raised an exception', 289 | extra={'data': url}, exc_info=True) 290 | 291 | async def send_data_to_ws(self, wss, data='handSnake'): 292 | if data == 'handSnake': 293 | await wss.send_json({ 294 | 'method': 'handShake', 295 | 'data': '', 296 | 'auth': self._client._auth, 297 | 'api_version': '5',#self._client.configuire['api_version'] 298 | }) 299 | elif data == 'keep': 300 | await wss.send_json({}) 301 | 302 | async def to_keep_alive(self): 303 | while True: 304 | await asyncio.sleep(5) 305 | try: 306 | await self._client(methods.chats.GetChatsUpdates(state=round(time()) - 200)) 307 | except: continue 308 | 309 | async def keep_socket(self, wss): 310 | while True: 311 | await asyncio.sleep(5) 312 | try: 313 | await self.send_data_to_ws(wss, data='keep') 314 | except: continue -------------------------------------------------------------------------------- /rubpy/gadgets/methods.py: -------------------------------------------------------------------------------- 1 | from .classino import Classino 2 | from .grouping import grouping 3 | import re 4 | import sys 5 | import time 6 | import random 7 | import warnings 8 | 9 | class Functions: 10 | system_versions = { 11 | 'Windows NT 10.0': 'Windows 10', 12 | 'Windows NT 6.2': 'Windows 8', 13 | 'Windows NT 6.1': 'Windows 7', 14 | 'Windows NT 6.0': 'Windows Vista', 15 | 'Windows NT 5.1': 'windows XP', 16 | 'Windows NT 5.0': 'Windows 2000', 17 | 'Mac': 'Mac/iOS', 18 | 'X11': 'UNIX', 19 | 'Linux': 'Linux' 20 | } 21 | 22 | @classmethod 23 | def get_phone(cls, value, *args, **kwargs): 24 | phone_number = ''.join(re.findall(r'\d+', value)) 25 | if not phone_number.startswith('98'): 26 | phone_number = '98' + phone_number 27 | return phone_number 28 | 29 | @classmethod 30 | def get_browser(cls, user_agent, lang_code, app_version, *args, **kwargs): 31 | device_model = re.search(r'(opera|chrome|safari|firefox|msie' 32 | r'|trident)\/(\d+)', user_agent.lower()) 33 | if not device_model: 34 | device_model = 'Unknown' 35 | warnings.warn(f'can not parse user-agent ({user_agent})') 36 | 37 | else: 38 | device_model = device_model.group(1) + ' ' + device_model.group(2) 39 | 40 | system_version = 'Unknown' 41 | for key, value in cls.system_versions.items(): 42 | if key in user_agent: 43 | system_version = value 44 | break 45 | 46 | # window.navigator.mimeTypes.length (outdated . Defaults to '2') 47 | device_hash = '2' 48 | return { 49 | 'token': '', 50 | 'lang_code': lang_code, 51 | 'token_type': 'Web', 52 | 'app_version': f'WB_{app_version}', 53 | 'system_version': system_version, 54 | 'device_model': device_model.title(), 55 | 'device_hash': device_hash + ''.join(re.findall(r'\d+', user_agent))} 56 | 57 | @classmethod 58 | def random_number(cls, *args, **kwargs): 59 | return int(random.random() * 1e6 + 1) 60 | 61 | @classmethod 62 | def timestamp(cls, *args, **kwargs): 63 | return int(time.time()) 64 | 65 | @classmethod 66 | def get_format(cls, value, *args, **kwargs): 67 | return value.split('.')[-1] 68 | 69 | @classmethod 70 | def get_hash_link(cls, value, *args, **kwargs): 71 | return value.split('/')[-1] 72 | 73 | @classmethod 74 | def to_float(cls, value, *args, **kwargs): 75 | return float(value) 76 | 77 | @classmethod 78 | def to_number(cls, value, *args, **kwargs): 79 | return int(value) 80 | 81 | @classmethod 82 | def to_string(cls, value, *args, **kwargs): 83 | return str(value) 84 | 85 | @classmethod 86 | def to_array(cls, value, *args, **kwargs): 87 | if isinstance(value, list): 88 | return value 89 | 90 | elif isinstance(value, str): 91 | return [value] 92 | 93 | try: 94 | return value.to_dict() 95 | 96 | except AttributeError: 97 | try: 98 | return dict(value) 99 | 100 | except Exception: 101 | return value 102 | 103 | @classmethod 104 | def to_metadata(cls, value, *args, **kwargs): 105 | pattern = r'`(.*)`|\*\*(.*)\*\*|__(.*)__|~~(.*)~~|--(.*)--|\[(.*)\]\((\S+)\)' 106 | conflict = 0 107 | meta_data_parts = [] 108 | for markdown in re.finditer(pattern, value): 109 | span = markdown.span() 110 | if markdown.group(0).startswith('`'): 111 | value = re.sub(pattern, r'\1', value, count=1) 112 | meta_data_parts.append( 113 | { 114 | 'type': 'Mono', 115 | 'from_index': span[0] - conflict, 116 | 'length': span[1] - span[0] - 2 117 | } 118 | ) 119 | conflict += 2 120 | 121 | elif markdown.group(0).startswith('**'): 122 | value = re.sub(pattern, r'\2', value, count=1) 123 | meta_data_parts.append( 124 | { 125 | 'type': 'Bold', 126 | 'from_index': span[0] - conflict, 127 | 'length': span[1] - span[0] - 4 128 | } 129 | ) 130 | conflict += 4 131 | 132 | elif markdown.group(0).startswith('__'): 133 | value = re.sub(pattern, r'\3', value, count=1) 134 | meta_data_parts.append( 135 | { 136 | 'type': 'Italic', 137 | 'from_index': span[0] - conflict, 138 | 'length': span[1] - span[0] - 4 139 | } 140 | ) 141 | conflict += 4 142 | 143 | elif markdown.group(0).startswith('~~'): 144 | value = re.sub(pattern, r'\4', value, count=1) 145 | meta_data_parts.append( 146 | { 147 | 'type': 'Strike', 148 | 'from_index': span[0] - conflict, 149 | 'length': span[1] - span[0] - 4 150 | } 151 | ) 152 | conflict += 4 153 | 154 | elif markdown.group(0).startswith('--'): 155 | value = re.sub(pattern, r'\5', value, count=1) 156 | meta_data_parts.append( 157 | { 158 | 'type': 'Underline', 159 | 'from_index': span[0] - conflict, 160 | 'length': span[1] - span[0] - 4 161 | } 162 | ) 163 | conflict += 4 164 | 165 | else: 166 | value = re.sub(pattern, r'\6', value, count=1) 167 | mention_text_object_guid = markdown.group(7) 168 | mention_type = 'MentionText' 169 | 170 | if mention_text_object_guid.startswith('g'): 171 | mention_text_object_type = 'Group' 172 | 173 | elif mention_text_object_guid.startswith('c'): 174 | mention_text_object_type = 'Channel' 175 | 176 | elif mention_text_object_guid.startswith('u'): 177 | mention_text_object_type = 'User' 178 | 179 | else: 180 | mention_text_object_type = 'hyperlink' 181 | mention_type = 'Link' 182 | 183 | if mention_type == 'MentionText': 184 | meta_data_parts.append({ 185 | 'type': 'MentionText', 186 | 'from_index': span[0] - conflict, 187 | 'length': len(markdown.group(6)), 188 | 'mention_text_object_guid': mention_text_object_guid, 189 | 'mention_text_object_type': mention_text_object_type 190 | }) 191 | conflict += 4 + len(mention_text_object_guid) 192 | 193 | else: 194 | meta_data_parts.append({ 195 | "from_index": span[0] - conflict, 196 | "length": len(markdown.group(6)), 197 | "link": { 198 | "hyperlink_data": { 199 | "url": mention_text_object_guid 200 | }, 201 | "type": mention_text_object_type, 202 | }, 203 | "type": mention_type, 204 | }) 205 | conflict += 4 + len(mention_text_object_guid) 206 | 207 | result = {'text': value} 208 | if meta_data_parts: 209 | result['metadata'] = { 210 | 'meta_data_parts': meta_data_parts 211 | } 212 | 213 | return result 214 | 215 | 216 | class BaseMethod: 217 | __name__ = 'CustomMethod' 218 | 219 | def __init__(self, method: dict, *args, **kwargs): 220 | self.method = method 221 | 222 | def __str__(self): 223 | result = f'{self.method_name}(*, *args, **kwargs)' 224 | 225 | if self.method_param: 226 | result += '\nArgs:\n' 227 | for name, param in self.method_param.items(): 228 | types = param.get('types') 229 | default = param.get('default') 230 | alloweds = param.get('alloweds') 231 | heirship = param.get('heirship') 232 | 233 | if not isinstance(types, list): 234 | types = [types] 235 | 236 | types = ', '.join(types) 237 | result += f'\t{name} ({types})\n' 238 | if alloweds is not None: 239 | result += '\t\tthe allowed values are: ' 240 | result += str([alloweds]) 241 | 242 | if default is not None: 243 | result += f'\n\t\tthe default value is {default}' 244 | 245 | if heirship is not None: 246 | result += ('\n\t\tif it is not set, it takes the' 247 | f' value from the ({[alloweds]}) argument\'s') 248 | result += '\n' 249 | return result 250 | 251 | @property 252 | def method_name(self): 253 | return self.__name__[0].lower() + self.__name__[1:] 254 | 255 | @property 256 | def method_param(self): 257 | if self.method: 258 | if isinstance(self.method['params'], dict): 259 | return self.method['params'] 260 | 261 | def build(self, argument, param, *args, **kwargs): 262 | ifs = param.get('ifs') 263 | func = param.get('func') 264 | types = param.get('types') 265 | alloweds = param.get('alloweds') 266 | 267 | # set defualt value 268 | try: 269 | value = self.request[argument] 270 | 271 | except KeyError: 272 | value = param['default'] 273 | if isinstance(value, dict): 274 | default_func = value.get('func') 275 | if isinstance(default_func, str): 276 | value = getattr(Functions, default_func)( 277 | **value, **self.request) 278 | 279 | # get value heirship 280 | for heirship in param.get('heirship', []): 281 | try: 282 | value = self.request[heirship] 283 | except KeyError: 284 | pass 285 | 286 | # clall func method 287 | if isinstance(func, str) and value is not None: 288 | value = getattr(Functions, func)(value, **self.request) 289 | argument = param.get('cname', argument) 290 | 291 | # check value types 292 | if types and not type(value).__name__ in types: 293 | if value is not None and 'optional' in types: 294 | raise TypeError( 295 | f'The given {argument} must be' 296 | f' {types} not {type(value).__name__}') 297 | 298 | if alloweds is not None: 299 | if isinstance(value, list): 300 | for _value in value: 301 | if _value not in alloweds: 302 | raise ValueError( 303 | f'the {argument}({_value}) value is' 304 | f' not in the allowed list {alloweds}') 305 | 306 | elif value not in alloweds: 307 | raise ValueError( 308 | f'the {argument}({value}) value is' 309 | f' not in the allowed list {alloweds}') 310 | 311 | # get ifs 312 | if isinstance(ifs, dict): 313 | 314 | # move to the last key 315 | if 'otherwise' in ifs: 316 | ifs['otherwise'] = ifs.pop('otherwise') 317 | 318 | for operator, work in ifs.items(): 319 | if type(value).__name__ == operator or operator == 'otherwise': 320 | func = work.get('func') 321 | param = work 322 | if isinstance(func, str): 323 | value = getattr(Functions, func)(value, **self.request) 324 | break 325 | 326 | # to avoid adding an extra value if there is "cname" 327 | if argument in self.request: 328 | self.request.pop(argument) 329 | 330 | if value is not None: 331 | if param.get('unpack'): 332 | self.request.update(value) 333 | 334 | else: 335 | self.request[param.get('cname', argument)] = value 336 | 337 | def __call__(self, *args, **kwargs): 338 | if self.method_param: 339 | self.request = {} 340 | params = list(self.method['params'].keys()) 341 | for index, value in enumerate(args): 342 | try: 343 | self.request[params[index]] = value 344 | 345 | except IndexError: 346 | pass 347 | 348 | for argument, value in kwargs.items(): 349 | if self.method['params'].get(argument): 350 | self.request[argument] = value 351 | 352 | for argument, param in self.method['params'].items(): 353 | try: 354 | self.build(argument, param) 355 | except KeyError: 356 | if 'optional' not in param['types']: 357 | raise TypeError( 358 | f'{self.__name__}() ' 359 | f'required argument ({argument})') 360 | 361 | if self.method.get('urls') is not None: 362 | self.request['method'] = self.method_name 363 | 364 | else: 365 | self.request = { 366 | 'method': self.method_name, 'input': self.request} 367 | 368 | self.request['urls'] = self.method.get('urls') 369 | self.request['encrypt'] = self.method.get('encrypt', True) 370 | self.request['tmp_session'] = bool(self.method.get('tmp_session')) 371 | 372 | #print(self.request) 373 | return self.request 374 | else: 375 | return { 376 | 'urls': None, 377 | 'input': {}, 378 | 'method': self.method_name, 379 | 'encrypt': True, 380 | 'tmp_session': False} 381 | 382 | 383 | class BaseGrouping(Classino): 384 | def __init__(self, methods: dict, *args, **kwargs): 385 | self.methods = methods 386 | 387 | def __dir__(self): 388 | methods = list(self.methods.keys()) 389 | methods.remove('Values') 390 | return self.methods['Values'] + methods 391 | 392 | def __getattr__(self, name) -> BaseMethod: 393 | if name in self.methods['Values']: 394 | return name 395 | 396 | method = self.create(name, (BaseMethod,), dir(self)) 397 | return method(self.methods[method.__name__]) 398 | 399 | 400 | class Methods(Classino): 401 | def __init__(self, name, *args, **kwargs): 402 | self.__name__ = name 403 | 404 | def __dir__(self): 405 | return grouping.keys() 406 | 407 | def __getattr__(self, name) -> BaseGrouping: 408 | group = self.create(name, (BaseGrouping,), dir(self)) 409 | return group(methods=grouping[group.__name__]) 410 | 411 | sys.modules[__name__] = Methods(__name__) -------------------------------------------------------------------------------- /rubpy/network/proxies.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | import asyncio 4 | from ..gadgets import exceptions 5 | from aiohttp import TCPConnector 6 | from urllib.parse import urlparse 7 | from aiohttp.abc import AbstractResolver 8 | 9 | 10 | class BaseSocketWrapper(object): 11 | def __init__(self, loop, host, port, family=socket.AF_INET): 12 | self._loop = loop 13 | self._socket = None 14 | self._family = family 15 | self._dest_host = None 16 | self._dest_port = None 17 | self._socks_host = host 18 | self._socks_port = port 19 | 20 | async def _send(self, request): 21 | data = bytearray() 22 | for item in request: 23 | if isinstance(item, int): 24 | data.append(item) 25 | elif isinstance(item, (bytearray, bytes)): 26 | data += item 27 | else: 28 | raise ValueError('Unsupported item') 29 | await self._loop.sock_sendall(self._socket, data) 30 | 31 | async def _receive(self, n): 32 | data = b'' 33 | while len(data) < n: 34 | packet = await self._loop.sock_recv(self._socket, n - len(data)) 35 | if not packet: 36 | raise exceptions.InvalidServerReply('Not all data available') 37 | data += packet 38 | return bytearray(data) 39 | 40 | async def _resolve_addr(self, host, port): 41 | addresses = await self._loop.getaddrinfo(host=host, port=port, 42 | family=socket.AF_UNSPEC, 43 | type=socket.SOCK_STREAM, 44 | proto=socket.IPPROTO_TCP, 45 | flags=socket.AI_ADDRCONFIG) 46 | if not addresses: 47 | raise OSError(f'Can`t resolve address {host}:{host}') 48 | return addresses[0][0], addresses[0][4][0] 49 | 50 | async def negotiate(self): 51 | raise NotImplementedError 52 | 53 | async def connect(self, address): 54 | self._dest_host = address[0] 55 | self._dest_port = address[1] 56 | self._socket = socket.socket(family=self._family, 57 | type=socket.SOCK_STREAM) 58 | self._socket.setblocking(False) 59 | 60 | try: 61 | await self._loop.sock_connect(sock=self._socket, 62 | address=(self._socks_host, 63 | self._socks_port)) 64 | except OSError as x: 65 | self.close() 66 | raise exceptions.SocksConnectionError( 67 | x.errno, 68 | 'Can not connect to proxy' 69 | f'{self._socks_host}:{self._socks_port} [{x.strerror}]' 70 | ) from x 71 | except asyncio.CancelledError: 72 | self.close() 73 | 74 | try: 75 | await self.negotiate() 76 | except exceptions.SocksError: 77 | self.close() 78 | 79 | except asyncio.CancelledError: 80 | if isinstance(self._loop, asyncio.ProactorEventLoop): 81 | self.close() 82 | 83 | def close(self): 84 | self._socket.close() 85 | 86 | async def sendall(self, data): 87 | await self._loop.sock_sendall(self._socket, data) 88 | 89 | async def recv(self, nbytes): 90 | return await self._loop.sock_recv(self._socket, nbytes) 91 | 92 | @property 93 | def socket(self): 94 | return self._socket 95 | 96 | 97 | class Socks4SocketWrapper(BaseSocketWrapper): 98 | def __init__(self, loop, host, port, user_id=None, resolver=False): 99 | BaseSocketWrapper.__init__(self, loop=loop, host=host, 100 | port=port, family=socket.AF_INET) 101 | self._user_id = user_id 102 | self._resolver = resolver 103 | 104 | async def _socks_connect(self): 105 | host = self._dest_host 106 | include_hostname = False 107 | try: 108 | host_bytes = socket.inet_aton(self._dest_host) 109 | except socket.error: 110 | if self._resolver: 111 | host_bytes = bytes([0x00, 0x00, 0x00, 0x01]) 112 | include_hostname = True 113 | else: 114 | _, host = await self._resolve_addr(self._dest_host, 115 | self._dest_port) 116 | host_bytes = socket.inet_aton(self._dest_host) 117 | 118 | request = [0x04, 0x01, struct.pack('>H', self._dest_port), host_bytes] 119 | 120 | if self._user_id: 121 | request.append(self._user_id.encode()) 122 | 123 | request.append(0x00) 124 | 125 | if include_hostname: 126 | request += [host.encode('idna'), 0x00] 127 | 128 | await self._send(request) 129 | 130 | respond = await self._receive(8) 131 | 132 | if respond[0] != 0x00: 133 | raise exceptions.InvalidServerReply( 134 | 'SOCKS4 proxy server sent invalid data') 135 | 136 | if respond[1] == 0x5B: 137 | raise exceptions.SocksError('Request rejected or failed') 138 | 139 | elif respond[1] == 0x5C: 140 | raise exceptions.SocksError('Request rejected because SOCKS server') 141 | 142 | elif respond[1] == 0x5D: 143 | raise exceptions.SocksError( 144 | 'Request rejected because the client program') 145 | 146 | elif respond[1] != 0x5A: 147 | raise exceptions.SocksError('Unknown error') 148 | 149 | return ((host, self._dest_port), 150 | socket.inet_ntoa(respond[4:]), 151 | struct.unpack('>H', respond[2:4])[0]) 152 | 153 | async def negotiate(self): 154 | await self._socks_connect() 155 | 156 | 157 | class Socks5SocketWrapper(BaseSocketWrapper): 158 | def __init__(self, loop, host, port, username=None, 159 | password=None, resolver=True, family=socket.AF_INET): 160 | BaseSocketWrapper.__init__(self, loop=loop, host=host, 161 | port=port, family=family) 162 | self._resolver = resolver 163 | self._username = username 164 | self._password = password 165 | 166 | async def _socks_auth(self): 167 | auth_methods = [0x00] 168 | if self._username and self._password: 169 | auth_methods = [0x02, 0x00] 170 | 171 | await self._send([0x05, len(auth_methods)] + auth_methods) 172 | respond = await self._receive(2) 173 | if respond[0] != 0x05: 174 | raise exceptions.InvalidServerVersion( 175 | f'Unexpected SOCKS version number: {respond[0]}') 176 | 177 | if respond[1] == 0xFF: 178 | raise exceptions.NoAcceptableAuthMethods( 179 | 'No acceptable authentication methods were offered') 180 | 181 | if respond[1] not in auth_methods: 182 | raise exceptions.UnknownAuthMethod( 183 | f'Unexpected SOCKS authentication method: {respond[1]}') 184 | 185 | if respond[1] == 0x02: 186 | await self._send([0x01, 187 | chr(len(self._username)).encode(), 188 | self._username.encode(), 189 | chr(len(self._password)).encode(), 190 | self._password.encode()]) 191 | 192 | respond = await self._receive(2) 193 | if respond[0] != 0x01: 194 | raise exceptions.InvalidServerReply( 195 | 'Invalid authentication response') 196 | 197 | if respond[1] != 0x00: 198 | raise exceptions.LoginAuthenticationFailed( 199 | 'Username and password authentication failure') 200 | 201 | async def _socks_connect(self): 202 | req_addr, resolved_addr = await self._build_dest_address() 203 | await self._send([0x05, 0x01, 0x00] + req_addr) 204 | respond = await self._receive(3) 205 | if respond[0] != 0x05: 206 | raise exceptions.InvalidServerVersion( 207 | f'Unexpected SOCKS version number: {respond[0]}') 208 | 209 | if respond[1] == 0x01: 210 | raise exceptions.SocksError('General SOCKS server failure') 211 | 212 | elif respond[1] == 0x02: 213 | raise exceptions.SocksError('Connection not allowed by ruleset') 214 | 215 | elif respond[1] == 0x03: 216 | raise exceptions.SocksError('Network unreachable') 217 | 218 | elif respond[1] == 0x04: 219 | raise exceptions.SocksError('Host unreachable') 220 | 221 | elif respond[1] == 0x05: 222 | raise exceptions.SocksError('Connection refused') 223 | 224 | elif respond[1] == 0x06: 225 | raise exceptions.SocksError('TTL expired') 226 | 227 | elif respond[1] == 0x07: 228 | raise exceptions.SocksError('Command not supported, or protocol error') 229 | 230 | elif respond[1] == 0x08: 231 | raise exceptions.SocksError('Address type not supported') 232 | 233 | elif respond[1] != 0x00: 234 | raise exceptions.SocksError('Unknown error') 235 | 236 | if respond[2] != 0x00: 237 | raise exceptions.InvalidServerReply('The reserved byte must be 0x00') 238 | 239 | return resolved_addr, await self._read_binded_address() 240 | 241 | async def _build_dest_address(self): 242 | port_bytes = struct.pack('>H', self._dest_port) 243 | for family in (socket.AF_INET, socket.AF_INET6): 244 | try: 245 | host_bytes = socket.inet_pton(family, self._dest_host) 246 | return ( 247 | [0x01 if family == socket.AF_INET else 0x04, 248 | host_bytes, port_bytes], 249 | (self._dest_host, self._dest_port) 250 | ) 251 | 252 | except socket.error: 253 | pass 254 | 255 | if self._resolver: 256 | host_bytes = self._dest_host.encode('idna') 257 | return [0x03, chr(len(host_bytes)).encode(), 258 | host_bytes, port_bytes], (self._dest_host, self._dest_port) 259 | 260 | family, _ = await self._resolve_addr(host=self._dest_host, 261 | port=self._dest_port) 262 | return ( 263 | [0x01 if family == socket.AF_INET else 0x04, 264 | host_bytes, port_bytes], 265 | (socket.inet_ntop(family, host_bytes), self._dest_port) 266 | ) 267 | 268 | async def _read_binded_address(self): 269 | atype = (await self._receive(1))[0] 270 | if atype == 0x01: 271 | addr = await self._receive(4) 272 | addr = socket.inet_ntoa(addr) 273 | elif atype == 0x03: 274 | length = await self._receive(1) 275 | addr = await self._receive(ord(length)) 276 | elif atype == 0x04: 277 | addr = await self._receive(16) 278 | addr = socket.inet_ntop(socket.AF_INET6, addr) 279 | else: 280 | raise exceptions.InvalidServerReply( 281 | 'SOCKS5 proxy server sent invalid data') 282 | port = await self._receive(2) 283 | return addr, struct.unpack('>H', port)[0] 284 | 285 | async def negotiate(self): 286 | await self._socks_auth() 287 | await self._socks_connect() 288 | 289 | 290 | class Resolver(AbstractResolver): 291 | async def resolve(self, host, port=0, family=socket.AF_INET): 292 | return [{ 293 | 'proto': 0, 'flags': 0, 294 | 'host': host, 'port': port, 295 | 'family': family, 'hostname': host}] 296 | 297 | async def close(self): 298 | pass 299 | 300 | 301 | class Proxies(TCPConnector): 302 | 303 | def __init__(self, 304 | host: str, 305 | port: int, 306 | type: str = 'http', 307 | resolver: bool = False, 308 | username: str = None, 309 | password: str = None, 310 | family=socket.AF_INET, *args, **kwargs): 311 | 312 | if isinstance(type, str): 313 | if type.lower() not in ['http', 'https', 'socks5', 'socks4']: 314 | raise ValueError( 315 | 'proxy type must be' 316 | '(`socks5` | `socks4` | `http` | `https` )') 317 | if resolver: 318 | kwargs['resolver'] = Resolver() 319 | 320 | TCPConnector.__init__(self, **kwargs) 321 | self._resolver = resolver 322 | self._proxy_host = host 323 | self._proxy_port = port 324 | self._proxy_type = type 325 | self._proxy_family = family 326 | self._proxy_username = username 327 | self._proxy_password = password 328 | 329 | @property 330 | def proxy_url(self): 331 | pattern = '{scheme}://{host}:{port}' 332 | if self._proxy_username: 333 | pattern = '{scheme}://{username}:{password}@{host}:{port}' 334 | return urlparse(pattern.format(scheme=self._proxy_type, 335 | username=self._proxy_username, 336 | password=self._proxy_password, 337 | host=self._proxy_host, 338 | port=self._proxy_port)) 339 | 340 | @classmethod 341 | def from_url(cls, url, **kwargs): 342 | """_from_url_ 343 | 344 | Args: 345 | url (str): proxy url 346 | http exmaple: http://login:password@127.0.0.1:1080 347 | https exmaple: https://login:password@127.0.0.1:1080 348 | socks4 exmaple: socks4://username:password@127.0.0.1:1080 349 | socks5 exmaple: socks5://username:password@127.0.0.1:1080 350 | """ 351 | parse = urlparse(url) 352 | return cls( 353 | type=parse.scheme, port=int(parse.port), host=parse.hostname, 354 | username=parse.username, password=parse.password, **kwargs) 355 | 356 | async def connect(self, req, traces, timeout): 357 | if self.proxy_url.scheme in ['http', 'https']: 358 | req.update_proxy(self.proxy_url._replace(scheme='https').geturl(), 359 | None, req.proxy_headers) 360 | return await TCPConnector.connect( 361 | self, req=req, traces=traces, timeout=timeout) 362 | 363 | async def _wrap_create_connection(self, protocol_factory, host=None, 364 | port=None, *args, **kwargs): 365 | if self.proxy_url.scheme not in ['http', 'https']: 366 | if self.proxy_url.scheme == 'socks5': 367 | sock = Socks5SocketWrapper(resolver=self._resolver, 368 | loop=self._loop, 369 | host=self._proxy_host, 370 | port=self._proxy_port, 371 | family=self._proxy_family, 372 | username=self._proxy_username, 373 | password=self._proxy_password) 374 | else: 375 | sock = Socks4SocketWrapper(resolver=self._resolver, 376 | loop=self._loop, 377 | host=self._proxy_host, 378 | port=self._proxy_port, 379 | user_id=self._proxy_username) 380 | await sock.connect((host, port)) 381 | return await TCPConnector._wrap_create_connection( 382 | self, protocol_factory, None, None, 383 | *args, sock=sock.socket, **kwargs) 384 | else: 385 | return await TCPConnector._wrap_create_connection( 386 | self, protocol_factory, host, port, 387 | *args, **kwargs) 388 | -------------------------------------------------------------------------------- /rubpy/structs/struct.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | from ..gadgets import methods, thumbnail 4 | 5 | 6 | class Struct: 7 | def __str__(self) -> str: 8 | return self.jsonify(indent=2) 9 | 10 | def __getattr__(self, name): 11 | return self.find_keys(keys=name) 12 | 13 | def __setitem__(self, key, value): 14 | self.original_update[key] = value 15 | 16 | def __getitem__(self, key): 17 | return self.original_update[key] 18 | 19 | def __lts__(self, update: list, *args, **kwargs): 20 | for index, element in enumerate(update): 21 | if isinstance(element, list): 22 | update[index] = self.__lts__(update=element) 23 | 24 | elif isinstance(element, dict): 25 | update[index] = Struct(update=element) 26 | 27 | else: 28 | update[index] = element 29 | return update 30 | 31 | def __init__(self, update: dict, *args, **kwargs) -> None: 32 | self.original_update = update 33 | 34 | def to_dict(self): 35 | return self.original_update 36 | 37 | def jsonify(self, indent=None, *args, **kwargs) -> str: 38 | result = self.original_update 39 | result['original_update'] = 'dict{...}' 40 | return json.dumps(result, indent=indent, 41 | ensure_ascii=False, 42 | default=lambda value: str(value)) 43 | 44 | def find_keys(self, keys, original_update=None, *args, **kwargs): 45 | 46 | if original_update is None: 47 | original_update = self.original_update 48 | 49 | if not isinstance(keys, list): 50 | keys = [keys] 51 | 52 | if isinstance(original_update, dict): 53 | for key in keys: 54 | try: 55 | update = original_update[key] 56 | if isinstance(update, dict): 57 | update = Struct(update=update) 58 | 59 | elif isinstance(update, list): 60 | update = self.__lts__(update=update) 61 | 62 | return update 63 | 64 | except KeyError: 65 | pass 66 | original_update = original_update.values() 67 | 68 | for value in original_update: 69 | if isinstance(value, (dict, list)): 70 | try: 71 | return self.find_keys(keys=keys, original_update=value) 72 | 73 | except AttributeError: 74 | pass 75 | 76 | raise AttributeError(f'Struct object has no attribute {keys}') 77 | 78 | def guid_type(self, guid: str, *args, **kwargs) -> str: 79 | if isinstance(guid, str): 80 | if guid.startswith('u'): 81 | return 'User' 82 | 83 | elif guid.startswith('g'): 84 | return 'Group' 85 | 86 | elif guid.startswith('c'): 87 | return 'Channel' 88 | 89 | # property functions 90 | 91 | @property 92 | def type(self): 93 | try: 94 | return self.find_keys(keys=['type', 'author_type']) 95 | 96 | except AttributeError: 97 | pass 98 | 99 | @property 100 | def raw_text(self): 101 | try: 102 | return self.find_keys(keys='text') 103 | 104 | except AttributeError: 105 | pass 106 | 107 | @property 108 | def message_id(self): 109 | try: 110 | return self.find_keys(keys=['message_id', 111 | 'pinned_message_id']) 112 | except AttributeError: 113 | pass 114 | 115 | @property 116 | def reply_message_id(self): 117 | try: 118 | return self.find_keys(keys='reply_to_message_id') 119 | 120 | except AttributeError: 121 | pass 122 | 123 | @property 124 | def is_group(self): 125 | return self.type == 'Group' 126 | 127 | @property 128 | def is_channel(self): 129 | return self.type == 'Channel' 130 | 131 | @property 132 | def is_private(self): 133 | return self.type == 'User' 134 | 135 | @property 136 | def object_guid(self): 137 | try: 138 | return self.find_keys(keys=['group_guid', 139 | 'object_guid', 'channel_guid']) 140 | except AttributeError: 141 | pass 142 | 143 | @property 144 | def author_guid(self): 145 | try: 146 | return self.author_object_guid 147 | 148 | except AttributeError: 149 | pass 150 | 151 | # async methods 152 | 153 | async def pin(self, 154 | object_guid: str = None, 155 | message_id: str = None, 156 | action: str = methods.messages.Pin, *args, **kwargs): 157 | """_pin_ 158 | Args: 159 | object_guid (str, optional): 160 | _custom object guid_. Defaults to update.object_guid. 161 | message_id (str, optional): 162 | _custom message id_. Defaults to update.message_id. 163 | action (bool, optional): 164 | _pin or unpin_. Defaults to methods.messages.Pin. ( 165 | methods.messages.Pin, 166 | methods.messages.Unpin 167 | ) 168 | Returns: 169 | BaseResults: result 170 | """ 171 | 172 | if object_guid is None: 173 | object_guid = self.object_guid 174 | 175 | if message_id is None: 176 | message_id = self.message_id 177 | 178 | return await self._client( 179 | methods.messages.SetPinMessage( 180 | object_guid=object_guid, 181 | message_id=message_id, 182 | action=action)) 183 | 184 | async def edit(self, 185 | text: str, 186 | object_guid: str = None, 187 | message_id: str = None, *args, **kwargs): 188 | """_edit_ 189 | Args: 190 | text (str): 191 | _message text_ 192 | object_guid (str, optional): 193 | _custom objec guid_. Defaults to update.object_guid. 194 | message_id (str, optional): 195 | _custom message id_. Defaults to update.message_id. 196 | """ 197 | 198 | if object_guid is None: 199 | object_guid = self.object_guid 200 | 201 | if message_id is None: 202 | message_id = self.message_id 203 | 204 | return await self._client( 205 | methods.messages.EditMessage( 206 | object_guid=object_guid, 207 | message_id=message_id, 208 | text=text)) 209 | 210 | async def copy(self, 211 | to_object_guid: str, 212 | from_object_guid: str = None, message_ids=None, *args, **kwargs): 213 | """_copy_ 214 | Args: 215 | to_object_guid (str): 216 | _to object guid_. 217 | from_object_guid (str, optional): 218 | _from object guid_. Defaults to update.object_guid. 219 | message_ids (typing.Union[str, int, typing.List[str]], optional): 220 | _message ids_. Defaults to update.message_id. 221 | """ 222 | 223 | if from_object_guid is None: 224 | from_object_guid = self.object_guid 225 | 226 | if message_ids is None: 227 | message_ids = self.message_id 228 | 229 | result = await self.get_messages(from_object_guid, message_ids) 230 | messages = [] 231 | if result.messages: 232 | for message in result.messages: 233 | 234 | try: 235 | file_inline = message.file_inline 236 | kwargs.update(file_inline.to_dict()) 237 | 238 | except AttributeError: 239 | file_inline = None 240 | 241 | try: 242 | thumb = thumbnail.Thumbnail( 243 | base64.b64decode(message.thumb_inline), *args, **kwargs) 244 | 245 | except AttributeError: 246 | thumb = kwargs.get('thumb', True) 247 | 248 | try: 249 | message = message.sticker 250 | 251 | except AttributeError: 252 | message = message.raw_text 253 | 254 | if file_inline is not None: 255 | if file_inline.type not in [methods.messages.Gif, 256 | methods.messages.Sticker]: 257 | file_inline = await self.download(file_inline) 258 | messages.append(await self._client.send_message( 259 | thumb=thumb, 260 | message=message, 261 | file_inline=file_inline, 262 | object_guid=to_object_guid, *args, **kwargs)) 263 | continue 264 | 265 | messages.append(await self._client.send_message( 266 | message=message, 267 | object_guid=to_object_guid, 268 | file_inline=file_inline, *args, **kwargs)) 269 | 270 | return Struct({'status': 'OK', 'messages': messages}) 271 | 272 | async def seen(self, seen_list: dict = None, *args, **kwargs): 273 | """_seen_ 274 | Args: 275 | seen_list (dict, optional): 276 | _description_. Defaults t 277 | {update.object_guid: update.message_id} 278 | """ 279 | 280 | if seen_list is None: 281 | seen_list = {self.object_guid: self.message_id} 282 | return await self._client(methods.chats.SeenChats(seen_list=seen_list)) 283 | 284 | async def reply(self, 285 | message=None, 286 | object_guid: str = None, 287 | reply_to_message_id: str = None, 288 | file_inline: str = None, *args, **kwargs): 289 | """_reply_ 290 | Args: 291 | message (Any, optional): 292 | _message or cation or sticker_ . Defaults to None. 293 | object_guid (str): 294 | _object guid_ . Defaults to update.object_guid 295 | reply_to_message_id (str, optional): 296 | _reply to message id_. Defaults to None. 297 | file_inline (typing.Union[pathlib.Path, bytes], optional): 298 | _file_. Defaults to None. 299 | is_gif (bool, optional): 300 | _is it a gif file or not_. Defaults to None. 301 | is_image (bool, optional): 302 | _is it a image file or not_. Defaults to None. 303 | is_voice (bool, optional): 304 | _is it a voice file or not_. Defaults to None. 305 | is_music (bool, optional): 306 | _is it a music file or not_. Defaults to None. 307 | is_video (bool, optional): 308 | _is it a video file or not_. Defaults to None. 309 | thumb (bool, optional): 310 | if value is "True", 311 | the lib will try to build the thumb ( require cv2 ) 312 | if value is thumbnail.Thumbnail, to set custom 313 | Defaults to True. 314 | """ 315 | 316 | if object_guid is None: 317 | object_guid = self.object_guid 318 | 319 | if reply_to_message_id is None: 320 | reply_to_message_id = self.message_id 321 | 322 | return await self._client.send_message( 323 | message=message, 324 | object_guid=object_guid, 325 | file_inline=file_inline, 326 | reply_to_message_id=reply_to_message_id, *args, **kwargs) 327 | 328 | async def forwards(self, 329 | to_object_guid: str, 330 | from_object_guid: str = None, 331 | message_ids=None, *args, **kwargs): 332 | """_forwards_ 333 | Args: 334 | to_object_guid (str): 335 | _to object guid_. 336 | from_object_guid (str, optional): 337 | _from object guid_. Defaults to update.object_guid. 338 | message_ids (typing.Union[str, int, typing.List[str]], optional): 339 | _message ids_. Defaults to update.message_id. 340 | """ 341 | 342 | if from_object_guid is None: 343 | from_object_guid = self.object_guid 344 | 345 | if message_ids is None: 346 | message_ids = self.message_id 347 | 348 | return await self._client( 349 | methods.messages.ForwardMessages( 350 | from_object_guid=from_object_guid, 351 | to_object_guid=to_object_guid, 352 | message_ids=message_ids)) 353 | 354 | async def download(self, file_inline=None, file=None, *args, **kwargs): 355 | return await self._client.download_file_inline( 356 | file_inline or self.file_inline, 357 | file=file, *args, **kwargs) 358 | 359 | async def get_author(self, author_guid: str = None, *args, **kwargs): 360 | """_get user or author information_ 361 | Args: 362 | author_guid (str, optional): 363 | _custom author guid_. Defaults to update.author_guid 364 | """ 365 | 366 | if author_guid is None: 367 | author_guid = self.author_guid 368 | 369 | return await self.get_object(object_guid=author_guid, *args, **kwargs) 370 | 371 | async def get_object(self, object_guid: str = None, *args, **kwargs): 372 | """_get object information_ 373 | Args: 374 | object_guid (str, optional): 375 | _custom object guid_. Defaults to update.object_guid. 376 | """ 377 | 378 | if object_guid is None: 379 | object_guid = self.object_guid 380 | 381 | if self.guid_type(object_guid) == 'User': 382 | return await self._client( 383 | methods.users.GetUserInfo( 384 | user_guid=object_guid)) 385 | 386 | elif self.guid_type(object_guid) == 'Group': 387 | return await self._client( 388 | methods.groups.GetGroupInfo( 389 | object_guid=object_guid)) 390 | 391 | elif self.guid_type(object_guid) == 'Channel': 392 | return await self._client( 393 | methods.channels.GetChannelInfo( 394 | object_guid=object_guid)) 395 | 396 | async def get_messages(self, 397 | object_guid: str = None, 398 | message_ids=None, *args, **kwargs): 399 | """_get messages_ 400 | Args: 401 | object_guid (str, optional): 402 | _custom object guid_. Defaults to update.object_guid. 403 | message_ids (str, int, typing.List[str]], optional): 404 | _message ids_. Defaults to update.message_id. 405 | """ 406 | 407 | if object_guid is None: 408 | object_guid = self.object_guid 409 | 410 | if message_ids is None: 411 | message_ids = self.message_id 412 | 413 | return await self._client( 414 | methods.messages.GetMessagesByID( 415 | object_guid=object_guid, message_ids=message_ids)) 416 | 417 | async def delete_messages(self, 418 | object_guid: str = None, 419 | message_ids=None, *args, **kwargs): 420 | """_delete messages_ 421 | Args: 422 | object_guid (str, optional): 423 | _custom object guid_. Defaults to update.object_guid. 424 | message_ids (str, list, optional): 425 | _custom message ids_. Defaults to update.message_id. 426 | type(str, optional): 427 | the message should be deleted for everyone or not. 428 | Defaults to methods.messages.Global ( 429 | methods.messages.Local, 430 | methods.messages.Global 431 | ) 432 | """ 433 | 434 | if object_guid is None: 435 | object_guid = self.object_guid 436 | 437 | if message_ids is None: 438 | message_ids = self.message_id 439 | 440 | return await self._client( 441 | methods.messages.DeleteMessages( 442 | object_guid=object_guid, 443 | message_ids=message_ids, *args, **kwargs)) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -=-=-=-=-=-=-=-=-=-=-= 2 | #GitHub.com/Scorpian-my 3 | # -=-=-=-=-=-=-=-=-=-=-= 4 | from rubpy import Client, handlers, Message, models 5 | from requests import get, post 6 | from random import randrange 7 | from os import remove 8 | from re import findall 9 | import requests 10 | import asyncio 11 | import random 12 | from persiantools.jdatetime import JalaliDate 13 | balance = {} 14 | warning = {} 15 | generate_ai = {} 16 | voice = {} 17 | # Guid manager of the channel and group 18 | owner = input("Enter Your Guid [Guid Owner]: ") 19 | gap = input("Enter Guid Group: ") # Guid Group 20 | # Channel Guid for mandatory join 21 | Channell = input("Enter Your Channel Guid: ") 22 | print("Run Successfuly") 23 | 24 | 25 | 26 | 27 | def voice_generate(text, file): 28 | voice = get( 29 | f"https://api.irateam.ir/create-voice/?text={text}&Character=DilaraNeural").json() 30 | voice = voice["results"]["url"] 31 | voice = requests.get(voice) 32 | with open(f"{file}", "wb") as f: 33 | f.write(voice.content) 34 | f.close() 35 | 36 | 37 | def Pic_Prof(id): 38 | url = f"https://rubika.ir/{id}" 39 | 40 | response = get(url) 41 | page_content = response.text 42 | pattern = r'= 0 else 0 78 | 79 | 80 | def join_message(): 81 | join_message = """👋 کاربر عزیز 82 | 83 | 🌟 برای استفاده از دستورات ربات، ابتدا به کانال زیر عضو شوید: 84 | 85 | 📢 @FreeTube 86 | 87 | 🔥 پس از عضویت، امکان استفاده از دستورات ربات برای شما فعال می‌شود. 🤖 88 | چنانچه شما از قبل عضو ربات بودید لطفا یک بار لفت بدید از کانال مجدد جوین شوید 89 | با تشکر! 90 | """ 91 | return join_message 92 | 93 | 94 | def gpt(prompt): 95 | requests.session().cookies.clear() 96 | 97 | options_url = "https://api.tapsi.cab/api/v1/chat-gpt/chat/completion" 98 | headers = { 99 | "Access-Control-Request-Method": "POST", 100 | "Access-Control-Request-Headers": "content-type,x-agent", 101 | "Origin": "https://chatgpt.tapsi.cab", 102 | } 103 | response = requests.options(options_url, headers=headers) 104 | rand = random.randrange(11, 99) 105 | rand1 = random.randrange(111, 999) 106 | 107 | ip_address = f"{rand}.{rand1}.{rand}.{rand}" 108 | 109 | ip_parts = ip_address.split(".") 110 | random.shuffle(ip_parts) 111 | new_ip = ".".join(ip_parts) 112 | 113 | webkit_version = f"{random.randint(500, 600)}.{random.randint(0, 99)}" 114 | major_version = random.randint(100, 150) 115 | minor_version = random.randint(0, 9) 116 | build_version = random.randint(0, 9999) 117 | safari_version = f"{random.randint(500, 600)}.{random.randint(0, 99)}" 118 | user_agent = f"Mozilla/5.0 (Linux; Android 10; STK-L21) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{major_version}.0.{minor_version}.{build_version} Mobile Safari/{safari_version}" 119 | 120 | post_url = "https://api.tapsi.cab/api/v1/chat-gpt/chat/completion" 121 | headers = { 122 | "Content-Type": "application/json", 123 | "X-Agent": user_agent, 124 | "X-Forwarded-For": new_ip, 125 | "Origin": "https://chatgpt.tapsi.cab", 126 | } 127 | data = {"messages": [{"role": "user", "content": prompt}]} 128 | return requests.post(post_url, headers=headers, json=data).json()["data"][ 129 | "message" 130 | ]["content"] 131 | 132 | 133 | def download_image(number, logo, save): 134 | log = "https://haji-api.ir/ephoto360/?type=text&id={}&text={}".format( 135 | number, logo) 136 | response = get(log) 137 | with open(f"{save}.png", "wb") as f: 138 | f.write(response.content) 139 | f.close() 140 | 141 | 142 | def is_user_in_channel(guid, Channel1): 143 | channel_members = Channel1["in_chat_members"] 144 | for member in channel_members: 145 | if member["member_guid"] == guid: 146 | return True 147 | return False 148 | 149 | 150 | def AI(text, type): 151 | generate = requests.get( 152 | "https://haji-api.ir/prompts/?text={}".format(text)).json() 153 | photos = generate["result"] 154 | random_index = randrange(0, len(photos) - 1) 155 | random_photo = photos[random_index] 156 | response = requests.get(random_photo) 157 | with open(f"{type}.png", "wb") as ai: 158 | ai.write(response.content) 159 | 160 | 161 | def detect_profanity(text): 162 | with open("Fosh.txt", "r", encoding="utf-8") as file: 163 | lines = file.readlines() 164 | 165 | for line in lines: 166 | words = line.split() 167 | 168 | for word in words: 169 | if word in text: 170 | return True 171 | 172 | return False 173 | 174 | 175 | async def main(): 176 | global Channell 177 | async with Client(session='rubpy') as client: 178 | await client.join_channel("c0BMKOd0fd6d1b62a51de3a9590c2631") 179 | await client.send_message(gap, "ربات هوش مصنوعی و لوگوساز با موفقیت اجرا شد\nبا دستور راهنما از دستورات با خبر باشید") 180 | await client.send_message(owner, "سلام ادمین عزیز \nاین دستورات است برای هدایت صحیح ممبر ها\nبرای شارژ آنها ابتدا کلمه شارژ را بنویسید و بعد آیدی آن و عدد شارژ\nبه عنوان مثال\nشارژ @pyrogram 100\n-=-=-=-=-=-=-=-=-=-=-=\nبرای دریافت موجودی آن ها بنویسید\n/profile @ID") 181 | 182 | @client.on(handlers.MessageUpdates(models.is_group(gap))) 183 | async def updates(message: Message): 184 | text = message.raw_text 185 | messID = message["message_id"] 186 | guid = message["message"]["author_object_guid"] 187 | 188 | Name = await client.get_user_info(guid) 189 | Name = Name["user"]["first_name"] 190 | Channel1 = await client.get_channel_all_members(Channell) 191 | Channel1 = Channel1.to_dict() 192 | if text == "/start" or text == "ربات" or text == "سلام": 193 | await message.reply(f""" 194 | ‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌ 195 | 👋 سلام کاربر ،{Name} 196 | به ربات هوش مصنوعی خوش آمدید. 🤖 197 | 198 | برای نمایش دستورات، کلمه 'راهنما' را ارسال کنید. 📚 199 | 200 | با احترام، تیم ربات نویسی: 201 | 202 | 🌟 @Legacy_Source""") 203 | elif text == "راهنما": 204 | try: 205 | await client.send_message(guid, """ 206 | سلام چطوری..! 🌟 207 | 208 | اومدم خوش آمد گویی کنم وارد ربات شدی 🐳 209 | 210 | امیدوارم از ربات لذت ببری و کنارمون بمونی 😊✨ 211 | 212 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 213 | 214 | خلللللاصه بگذریم سر تو بدرد نمیارم و دستور های ربات رو بهت میگم 😄 215 | 216 | 217 | میدونستی قابلیت حرف زدن هم داره؟ 218 | 219 | بهش بگی بگو سلام زندگی بهت ویس میده :) 220 | به عنوان مثال: 221 | بگو سلامتی نادرشاه افشار بزرگ 222 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 223 | واقعاً هیجان زده‌ایم که بهتون اعلام کنیم که میتونین با استفاده از دستور زیر پیام ناشناس بفرستین: 224 | 225 | خب مرحله اول اگر میخوای لوگو بسازی با اسم قشنگت باید بری داخل این کاناله عددی که میخوای ربات با اسمت بسازه رو برداری 226 | 227 | 🌐 @LogoExpress 228 | 229 | مثلا من میخوام با مدل لوگوی شماره 50 واسم لوگو بسازه 230 | 231 | باید بنویسم 232 | 233 | /logo 50 Mahyar 234 | 235 | اینجوری میتونم لوگو امو بسازم 😊✨ 236 | 237 | 238 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 239 | واقعاً هیجان زده‌ایم که بهتون اعلام کنیم که میتونین با استفاده از دستور زیر پیام ناشناس بفرستین: 240 | 241 | /sms @USERNAME سلام 242 | 243 | با استفاده از این دستور، میتونین به کسی که توسط نام کاربری مشخص میشه، یک پیام ناشناس با محتوای "سلام" ارسال کنین. 244 | 245 | پیام ناشناس بفرستید و لحظات شیرینی رو براش رقم بزنید! 💌💫 246 | -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 247 | تازه میتونی با کمک هوش مصنوعی عکس دلخواه بسازی! 😃✨ 248 | 249 | برای مثال، اگر میخوای عکسی از خونه ای در جنگل بسازی باید به انگلیسی بنویسی: 250 | 251 | /generate A house in the forest 252 | 253 | 254 | 255 | با استفاده از این دستور میتونم بهت عکسی مرتبط با خونه ای در جنگل بدم. امیدوارم ازش لذت ببری! 🏡🌳📷 256 | 257 | 258 | ما خوشحالیم که همیشه در خدمتتون هستیم و میتونیم بهتون کمک کنیم. اگر سوال یا درخواست دیگه ای دارید، حتما بپرسید! 😊🌟 259 | 👉 @The_Pynux 260 | """, 261 | ) 262 | await message.reply("دستورات ربات به پیوی شما ارسال شد") 263 | except Exception as e: 264 | print(e) 265 | 266 | elif text.startswith("/generate "): 267 | Gen = text.replace("/generate ", "").replace('"', "") 268 | Gen = Gen.lower() 269 | if guid == owner: 270 | try: 271 | AI(Gen, "owner") 272 | await client.send_photo(gap, "owner.jpg", "تقدیم با عشق", "owner.jpg", "360", "360", reply_to_message_id=messID) 273 | remove("owner.png") 274 | except Exception as e: 275 | print(e) 276 | else: 277 | if detect_profanity(Gen): 278 | if guid not in warning: 279 | warning[guid] = 0 280 | if warning[guid] < 2: 281 | warning[guid] += 1 282 | await message.reply( 283 | f"شما به دلیل رعایت نکردن قوانین ربات، از کلمات ممنوعه استفاده کردید، اخطار گرفتید \n تعداد اخطار های شما: {warning[guid]}" 284 | ) 285 | else: 286 | await message.reply( 287 | f"شما به دلیل رعایت نکردن قوانین ربات، از کلمات ممنوعه استفاده کردید، اخطار گرفتید \n تعداد اخطار های شما: 3" 288 | ) 289 | await message.reply( 290 | "شما به حداکثر اخطار رسیدید، تا دیدار بعد خدانگهدار" 291 | ) 292 | await client.ban_group_member(gap, guid) 293 | else: 294 | if is_user_in_channel(guid, Channel1): 295 | try: 296 | if guid not in generate_ai: 297 | generate_ai[guid] = 15 298 | if generate_ai[guid] > 1: 299 | generate_ai[guid] -= 1 300 | AI(Gen, "user") 301 | await client.send_photo( 302 | gap, 303 | "user.png", 304 | f"درخواست شما با موفقیت تکمیل شد.🎉\n\n- Daily credit : {generate_ai[guid]}/15🎉\n∞ Scorpian AI Bot ∞\n# @Legacy_Source", "user.png", "360", "360", reply_to_message_id=messID 305 | ) 306 | else: 307 | await message.reply( 308 | "موجودی شما تمام شده است برای شارژ و یا تمدید با این آیدی در ارتباط باشید :\n@Pyrogram" 309 | ) 310 | except Exception as e: 311 | print(e) 312 | else: 313 | await message.reply(join_message()) 314 | if text.startswith("/logo "): 315 | text = text.replace("/logo ", "") 316 | text_parts = text.split() 317 | 318 | if len(text_parts) >= 2: 319 | number_text = text_parts[0] 320 | if number_text.isdigit(): 321 | number = int(number_text) 322 | if 1 <= number <= 136: 323 | if guid == owner: 324 | try: 325 | download_image( 326 | text_parts[0], text_parts[1], "owner") 327 | await client.send_photo(gap, "owner.png", "تقدیم با عشق", "owner.png", "320", "202", reply_to_message_id=messID) 328 | 329 | remove("owner.png") 330 | return ... 331 | except Exception as e: 332 | print(e) 333 | return ... 334 | else: 335 | try: 336 | if is_user_in_channel(guid, Channel1): 337 | if guid not in balance: 338 | balance[guid] = 15 339 | if balance[guid] > 1: 340 | balance[guid] -= 1 341 | download_image( 342 | text_parts[0], text_parts[1], "user") 343 | await client.send_photo(gap, "user.png", f"درخواست شما با موفقیت تکمیل شد.\n\n- Daily credit : {balance[guid]}/15\n∞ Scorpian AI Bot ∞\n# @LogoExpress", "user.png", "360", "360", "Music.jpg", messID) 344 | remove("user.png") 345 | else: 346 | await message.reply( 347 | "موجودی شما تمام شده است برای شارژ و یا تمدید با این آیدی در ارتباط باشید :\n@Pyrogram" 348 | ) 349 | else: 350 | await message.reply(join_message()) 351 | except Exception as e: 352 | print(e) 353 | else: 354 | await message.reply( 355 | "کاربر عزیز، عدد وارد شده باید بین 1 تا 136 باشد" 356 | ) 357 | return ... 358 | else: 359 | await message.reply( 360 | "کاربر عزیز، لطفاً یک عدد معتبر را وارد کنید" 361 | ) 362 | return ... 363 | else: 364 | await message.reply( 365 | "نوع درخواست شما اشتباه است\n برای درخواست عکس باید عدد مورد نظر خود را ازینجا بردارید\n@LogoExpress\nمثالی از نمونه درخواست:\n/logo 120 Mahyar" 366 | ) 367 | return ... 368 | elif text.startswith("!"): 369 | text.replace("!", "") 370 | try: 371 | if len(text) > 4999: 372 | await message.reply( 373 | "تعداد متن های جواب بیشتر از حد است و نمیتوان فرستاد" 374 | ) 375 | else: 376 | await message.reply(f"پاسخ شما:\n{gpt(text)}") 377 | except Exception as e: 378 | print(e) 379 | 380 | elif text.startswith("شارژ @"): 381 | if guid == owner: 382 | text = text.replace("شارژ @", "") 383 | text = text.split() 384 | username = text[0] 385 | amount = int(text[1]) 386 | try: 387 | user = await client.get_object_by_username(username) 388 | if user: 389 | guis = user["user"]["user_guid"] 390 | up_balance(guis, amount) 391 | await message.reply( 392 | f"مقدار شارژ کاربر @{username} به میزان {amount} افزایش یافت." 393 | ) 394 | else: 395 | await message.reply(f"کاربر {username} یافت نشد.") 396 | except Exception as e: 397 | print(e) 398 | else: 399 | ... 400 | elif text.startswith("بگو "): 401 | text = text.replace("بگو ", "") 402 | if guid == owner: 403 | try: 404 | voice_generate(text, "owner.mp3") 405 | await client.send_voice(gap, "owner.mp3", "**بفرمایید قربان**", "owner.mp3", time="12345678", reply_to_message_id=messID) 406 | remove("owner.mp3") 407 | return ... 408 | except Exception as e: 409 | print(e) 410 | 411 | else: 412 | if is_user_in_channel(guid, Channel1): 413 | try: 414 | if len(text) > 700: 415 | await message.reply( 416 | "تعداد متن های جواب بیشتر از حد است و نمیتوان فرستاد" 417 | ) 418 | else: 419 | if guid not in voice: 420 | voice[guid] = 15 421 | if voice[guid] > 1: 422 | voice[guid] -= 1 423 | voice_generate(text, "user.mp3") 424 | await client.send_voice(gap, "user.mp3", f"**ویس شما آماده شد**\nاعتبار فعلی شما: {voice[guid]}/15", "user.mp3", time="12345678", reply_to_message_id=messID) 425 | 426 | remove("user.mp3") 427 | else: 428 | await message.reply( 429 | "موجودی شما تمام شده است برای شارژ و یا تمدید با این آیدی در ارتباط باشید :\n@Pyrogram" 430 | ) 431 | except Exception as e: 432 | print(e) 433 | 434 | else: 435 | await message.reply(join_message()) 436 | try: 437 | remove("voice.mp3") 438 | except Exception as e: 439 | print(e) 440 | elif text.startswith('/sms'): 441 | try: 442 | info = await client.get_object_by_username(text.split()[1][1:]) 443 | info = info["user"]["user_guid"] 444 | await client.send_message(info, '📨 شما یک پیام ناشناس دارید:‌\n\n'+" ".join(text.split()[2:])) 445 | await message.reply(f'پیام شما با موفقیت ارسال شد...') 446 | except Exception as e: 447 | await message.reply("احتمالا آیدی وجود ندارد") 448 | elif text.startswith("/profile @"): 449 | text = text.replace("/profile @", "") 450 | text = text.lower() 451 | profile = await client.get_object_by_username(text) 452 | stamp = profile["user"]["last_online"] 453 | name = profile["user"]["first_name"] 454 | last = profile["user"]["last_name"] 455 | user = profile["user"]["username"] 456 | bio = profile["user"]["bio"] 457 | time = JalaliDate.fromtimestamp(int(stamp)) 458 | Pic_Prof(text) 459 | if text == "pyrogram": 460 | await client.send_photo(gap, "Profile.png", f""" 461 | 🌟 اطلاعات کاربر مورد نظر: 462 | –––––––––––––––––––––––– 463 | 👤 نام کاربر: {name}{last} 464 | -=-=-=-=-=-=-=-=-=-=-= 465 | 💰 اعتبار: VIP 466 | -=-=-=-=-=-=-=-=-=-=-= 467 | 🆔 آیدی: @{user} 468 | -=-=-=-=-=-=-=-=-=-=-= 469 | 💬 بیوگرافی: {bio} 470 | -=-=-=-=-=-=-=-=-=-=-= 471 | ⏱ آخرین بازدید: 472 | {time} 473 | -=-=-=-=-=-=-=-=-=-=-= 474 | 🔐 اشتراک کاربر: VIP 475 | –––––––––––––––––––––––– 476 | #Information_Bot""", "Profile.png", "320", "202", reply_to_message_id=messID) 477 | else: 478 | try: 479 | await client.send_photo(gap, "Profile.png", f""" 480 | 🌟 اطلاعات کاربر مورد نظر: 481 | –––––––––––––––––––––––– 482 | 👤 نام کاربر: {name}{last} 483 | -=-=-=-=-=-=-=-=-=-=-= 484 | 💰 اعتبار: VIP 485 | -=-=-=-=-=-=-=-=-=-=-= 486 | 🆔 آیدی: @{user} 487 | -=-=-=-=-=-=-=-=-=-=-= 488 | 💬 بیوگرافی: {bio} 489 | -=-=-=-=-=-=-=-=-=-=-= 490 | ⏱ آخرین بازدید: 491 | {time} 492 | -=-=-=-=-=-=-=-=-=-=-= 493 | 🔐 اشتراک کاربر: معمولی 494 | –––––––––––––––––––––––– 495 | #Information_Bot""", "Profile.png", "320", "202", reply_to_message_id=messID) 496 | except Exception as e: 497 | print(e) 498 | await client.run_until_disconnected() 499 | asyncio.run(main()) 500 | -------------------------------------------------------------------------------- /rubpy/gadgets/grouping.py: -------------------------------------------------------------------------------- 1 | grouping = { 2 | "users": { 3 | "Values": ["Block", "Unblock"], 4 | "GetUserInfo": { 5 | "params": { 6 | "user_guid": {"types": "str"} 7 | } 8 | }, 9 | "SetBlockUser": { 10 | "params": { 11 | "user_guid": {"types": "str"}, 12 | "action": {"types": ["str", "optional"], "alloweds": ["Block", "Unblock"], "default": "Block"} 13 | } 14 | }, 15 | "DeleteUserChat": { 16 | "params": { 17 | "user_guid": {"types": "str"}, 18 | "last_deleted_message_id": {"types": ["str", "int"], "func": "to_string"} 19 | } 20 | }, 21 | "CheckUserUsername": { 22 | "params": { 23 | "username": {"types": "str"} 24 | } 25 | } 26 | }, 27 | "chats": { 28 | "Values": ["Mute", "Unmute", "Typing", "Uploading", "Recording", "Text", "Hashtag"], 29 | "UploadAvatar": { 30 | "params": { 31 | "object_guid": {"types": "str"}, 32 | "main_file_id": {"types": "str"}, 33 | "thumbnail_file_id": {"types": "str"} 34 | } 35 | }, 36 | "DeleteAvatar": { 37 | "params": { 38 | "object_guid": {"types": "str"}, 39 | "avatar_id": {"types": "str"} 40 | } 41 | }, 42 | "GetAvatars": { 43 | "params": { 44 | "object_guid": {"types": "str"} 45 | } 46 | }, 47 | "GetChats": { 48 | "params": { 49 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 50 | } 51 | }, 52 | "SeenChats": { 53 | "params": { 54 | "seen_list": {"types": "dict", "func": "to_array"} 55 | } 56 | }, 57 | "GetChatAds": { 58 | "params": { 59 | "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} 60 | } 61 | }, 62 | "SetActionChat": { 63 | "params": { 64 | "object_guid": {"types": "str"}, 65 | "action": {"types": ["str", "optional"], "alloweds": ["Mute", "Unmute"], "default": "Mute"} 66 | } 67 | }, 68 | "GetChatsUpdates": { 69 | "params": { 70 | "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} 71 | } 72 | }, 73 | "SendChatActivity": { 74 | "params": { 75 | "object_guid": {"types": "str"}, 76 | "activity": {"types": ["str", "optional"], "alloweds": ["Typing", "Uploading", "Recording"], "default": "Typing"} 77 | } 78 | }, 79 | "DeleteChatHistory": { 80 | "params": { 81 | "object_guid": {"types": "str"} 82 | } 83 | }, 84 | "SearchChatMessages": { 85 | "params": { 86 | "object_guid": {"types": "str"}, 87 | "search_text": {"types": "str"}, 88 | "type": {"types": ["str", "optional"], "alloweds": ["Text", "Hashtag"], "default": "Hashtag"} 89 | } 90 | } 91 | 92 | }, 93 | "extras": { 94 | "Values": [], 95 | "SearchGlobalObjects": { 96 | "params": { 97 | "search_text": {"types": "str"} 98 | } 99 | }, 100 | "GetAbsObjects": { 101 | "params": { 102 | "objects_guids": {"types": ["str", "list"], "func": "to_array"} 103 | } 104 | }, 105 | "GetObjectByUsername": { 106 | "params": { 107 | "username": {"types": "str"} 108 | } 109 | }, 110 | "GetLinkFromAppUrl": { 111 | "params": { 112 | "app_url": {"types": "str"} 113 | } 114 | } 115 | }, 116 | "groups": { 117 | "Values": ["Set", "Unset", "SetAdmin", "UnsetAdmin", "Hidden", "Visible", "AddMember", "ViewAdmins", "ViewMembers", "SendMessages", "SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], 118 | "AddGroup": { 119 | "params": { 120 | "title": {"types": "str"}, 121 | "member_guids": {"types": ["str", "list"], "func": "to_array"} 122 | } 123 | }, 124 | "JoinGroup": { 125 | "params": { 126 | "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} 127 | } 128 | }, 129 | "LeaveGroup": { 130 | "params": { 131 | "group_guid": {"types": "str"} 132 | } 133 | }, 134 | "RemoveGroup": { 135 | "params": { 136 | "group_guid": {"types": "str"} 137 | } 138 | }, 139 | "GetGroupInfo": { 140 | "params": { 141 | "group_guid": {"types": "str"} 142 | } 143 | }, 144 | "GetGroupLink": { 145 | "params": { 146 | "group_guid": {"types": "str"} 147 | } 148 | }, 149 | "SetGroupLink": { 150 | "params": { 151 | "group_guid": {"types": "str"} 152 | } 153 | }, 154 | "EditGroupInfo": { 155 | "updated_parameters": True, 156 | "params": { 157 | "group_guid": {"types": "str"}, 158 | "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "slow_mode", "chat_history_for_new_members"]}, 159 | "title": {"types": ["str", "optional"]}, 160 | "description": {"types": ["str", "optional"]}, 161 | "slow_mode": {"types": ["int", "str", "optional"]}, 162 | "chat_history_for_new_members": {"types": ["str", "optional"], "alloweds": ["Hidden", "Visible"]}, 163 | } 164 | }, 165 | "SetGroupAdmin": { 166 | "params": { 167 | "group_guid": {"types": "str"}, 168 | "member_guid": {"types": "str"}, 169 | "access_list": {"types": ["str", "list"], "alloweds": ["SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], "func": "to_array"}, 170 | "action": {"types": ["str", "optional"], "alloweds": ["SetAdmin", "UnsetAdmin"], "default": "SetAdmin"} 171 | } 172 | }, 173 | "BanGroupMember": { 174 | "params": { 175 | "group_guid": {"types": "str"}, 176 | "member_guid": {"types": "str"}, 177 | "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} 178 | } 179 | }, 180 | "AddGroupMembers": { 181 | "params": { 182 | "group_guid": {"types": "str"}, 183 | "member_guids": {"types": ["str", "list"], "func": "to_array"} 184 | } 185 | }, 186 | "GetGroupAllMembers": { 187 | "params": { 188 | "group_guid": {"types": "str"}, 189 | "search_text": {"types": ["str", "optional"], "default": ""}, 190 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 191 | } 192 | }, 193 | "GetGroupAdminMembers": { 194 | "params": { 195 | "group_guid": {"types": "str"}, 196 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 197 | } 198 | }, 199 | "GetGroupMentionList": { 200 | "params": { 201 | "group_guid": {"types": "str"}, 202 | "search_mention": {"types": ["str", "optional"]} 203 | } 204 | }, 205 | "GetGroupDefaultAccess": { 206 | "params": { 207 | "group_guid": {"types": "str"} 208 | 209 | } 210 | }, 211 | "SetGroupDefaultAccess": { 212 | "params": { 213 | "group_guid": {"types": "str"}, 214 | "access_list": {"types": ["str", "list"], "alloweds": ["AddMember", "ViewAdmins", "ViewMembers", "SendMessages"]} 215 | } 216 | }, 217 | "GroupPreviewByJoinLink": { 218 | "params": { 219 | "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} 220 | } 221 | }, 222 | "DeleteNoAccessGroupChat": { 223 | "params": { 224 | "group_guid": {"types": "str"} 225 | } 226 | }, 227 | "GetGroupAdminAccessList": { 228 | "params": { 229 | "group_guid": {"types": "str"}, 230 | "member_guid": {"types": "str"} 231 | } 232 | }, 233 | "CreateGroupVoiceChat": { 234 | "params": { 235 | "group_guid": {"types": "str"}, 236 | } 237 | }, 238 | "SetGroupVoiceChatSetting": { 239 | "updated_parameters": True, 240 | "params": { 241 | "group_guid": {"types": "str"}, 242 | "voice_chat_id": {"types": "str"}, 243 | "title": {"types": "str"}, 244 | "updated_parameters": {"types": ["list"], "alloweds": ["title"]} 245 | } 246 | } 247 | }, 248 | "messages": { 249 | "Values": [ 250 | "Pin","Unpin", "Text", "Gif", "File", "Image", "Voice", "Music", "Video", "FileInline", "Quiz", "Regular", "FromMin", "FromMax", "Local", "Global"], 251 | "SendMessage": { 252 | "params": { 253 | "object_guid": {"types": "str"}, 254 | "message": { 255 | "types": ["dict", "Struct", "str", "optional"], 256 | "ifs": { 257 | "str": {"func": "to_metadata", "unpack": True}, 258 | "otherwise": {"cname": "sticker", "func": "to_array"} 259 | } 260 | }, 261 | "reply_to_message_id": { 262 | "types": ["str", "int", "optional"], 263 | "func": "to_string" 264 | }, 265 | "file_inline": { 266 | "types": ["Struct", "dict", "optional"], 267 | "func": "to_array" 268 | }, 269 | "type": {"types": ["str", "optional"], "alloweds": ["FileInlineCaption", "FileInline"], "default": "FileInline"}, 270 | "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} 271 | } 272 | }, 273 | "EditMessage": { 274 | "params": { 275 | "object_guid": {"types": "str"}, 276 | "message_id": {"types": ["str", "int"], "func": "to_string"}, 277 | "text": {"types": "str", "func": "to_metadata", "unpack": True} 278 | } 279 | }, 280 | "DeleteMessages": { 281 | "params": { 282 | "object_guid": {"types": "str"}, 283 | "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, 284 | "type": {"types": ["str", "optional"], "alloweds": ["Local", "Global"], "default": "Global"} 285 | } 286 | }, 287 | "RequestSendFile": { 288 | "params": { 289 | "file_name": {"types": "str"}, 290 | "size": {"types": ["str", "int", "float"], "func": "to_number"}, 291 | "mime": {"types": ["str", "optional"], "heirship": ["file_name"], "func": "get_format"} 292 | } 293 | }, 294 | "ForwardMessages": { 295 | "params": { 296 | "from_object_guid": {"types": "str"}, 297 | "to_object_guid": {"types": "str"}, 298 | "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, 299 | "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} 300 | } 301 | }, 302 | "CreatePoll": { 303 | "params": { 304 | "object_guid": {"types": "str"}, 305 | "question": {"types": "str", }, 306 | "options": {"types": "list", "minimum": 2}, 307 | "type": {"types": ["str", "optional"], "alloweds": ["Quiz", "Regular"], "default": "Regular"}, 308 | "is_anonymous": {"types": ["bool", "optional"]}, 309 | "allows_multiple_answers": {"types": ["bool", "optional"]}, 310 | "correct_option_index": {"types": ["str", "int", "optional"], "func": "to_number"}, 311 | "explanation": {"types": ["str", "optional"]}, 312 | "reply_to_message_id": {"types": ["int", "optional"], "default": 0}, 313 | "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} 314 | } 315 | }, 316 | "VotePoll": { 317 | "params": { 318 | "poll_id": {"types": "str"}, 319 | "selection_index": {"types": ["int", "str"], "func": "to_number"} 320 | } 321 | }, 322 | "GetPollStatus": { 323 | "params": { 324 | "poll_id": {"types": "str"} 325 | } 326 | }, 327 | "GetPollOptionVoters": { 328 | "params": { 329 | "poll_id": {"types": "str"}, 330 | "selection_index": {"types": ["int", "str"], "func": "to_number"}, 331 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 332 | } 333 | }, 334 | "SetPinMessage": { 335 | "params": { 336 | "object_guid": {"types": "str"}, 337 | "message_id": {"types": ["str", "int"], "func": "to_string"}, 338 | "action": {"types": ["str", "optional"], "alloweds": ["Pin", "Unpin"], "default": "Pin"} 339 | } 340 | }, 341 | "GetMessagesUpdates": { 342 | "params": { 343 | "object_guid": {"types": "str"}, 344 | "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} 345 | } 346 | }, 347 | "SearchGlobalMessages": { 348 | "params": { 349 | "search_text": {"types": "str"}, 350 | "type": {"types": ["str", "optional"], "alloweds": ["Text"], "default": "Text"} 351 | } 352 | }, 353 | "ClickMessageUrl": { 354 | "params": { 355 | "object_guid": {"types": "str"}, 356 | "message_id": {"types": ["str", "int"], "func": "to_string"}, 357 | "link_url": {"types": "str"} 358 | } 359 | }, 360 | "GetMessagesByID": { 361 | "params": { 362 | "object_guid": {"types": "str"}, 363 | "message_ids": {"types": ["int", "str", "list"], "func": "to_array"} 364 | } 365 | }, 366 | "GetMessages": { 367 | "params": { 368 | "object_guid": {"types": "str"}, 369 | "min_id": {"types": ["str", "int"], "func": "to_number"}, 370 | "max_id": {"types": ["str", "int"], "func": "to_number"}, 371 | "sort": {"types": ["str", "optional"], "alloweds": ["FromMin", "FromMax"], "default": "FromMin"}, 372 | "limit": {"types": ["str", "int"], "func": "to_number", "default": 10}, 373 | } 374 | }, 375 | "GetMessagesInterval": { 376 | "params": { 377 | "object_guid": {"types": "str"}, 378 | "middle_message_id": {"types": ["str", "int"], "func": "to_string"}, 379 | } 380 | }, 381 | "GetMessageShareUrl": { 382 | "params": { 383 | "object_guid": {"types": "str"}, 384 | "message_id": {"types": ["str", "int"]}, 385 | } 386 | } 387 | }, 388 | "channels": { 389 | "Values": ["Join", "Remove", "Archive", "Set", "Unset"], 390 | "AddChannel": { 391 | "params": { 392 | "title": {"types": "str"}, 393 | "description": {"types": ["str", "optional"]} 394 | } 395 | }, 396 | "RemoveChannel": { 397 | "params": { 398 | "channel_guid": {"types": "str"} 399 | } 400 | }, 401 | "GetChannelInfo": { 402 | "params": { 403 | "channel_guid": {"types": "str"} 404 | } 405 | }, 406 | "EditChannelInfo": { 407 | "updated_parameters": True, 408 | "params": { 409 | "channel_guid": {"types": "str"}, 410 | "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "channel_type", "sign_messages"]}, 411 | "title": {"types": "str"}, 412 | "description": {"types": ["str", "optional"]}, 413 | "channel_type": {"types": ["str", "optional"], "alloweds": ["Public", "Private"], "default": "Public" }, 414 | "sign_messages": {"types": ["str", "optional"]} 415 | } 416 | }, 417 | "JoinChannelAction": { 418 | "params": { 419 | "channel_guid": {"types": "str"}, 420 | "action": {"types": ["str", "optional"], "alloweds": ["Join", "Remove", "Archive"], "default": "Join"} 421 | } 422 | }, 423 | "JoinChannelByLink": { 424 | "params": { 425 | "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} 426 | } 427 | }, 428 | "AddChannelMembers": { 429 | "params": { 430 | "channel_guid": {"types": "str"}, 431 | "member_guids": {"types": ["str", "list"], "func": "to_array"} 432 | } 433 | }, 434 | "BanChannelMember": { 435 | "params": { 436 | "channel_guid": {"types": "str"}, 437 | "member_guid": {"types": "str"}, 438 | "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} 439 | } 440 | }, 441 | "CheckChannelUsername": { 442 | "params": { 443 | "username": {"types": "str"} 444 | } 445 | }, 446 | "ChannelPreviewByJoinLink": { 447 | "params": { 448 | "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} 449 | } 450 | }, 451 | "GetChannelAllMembers": { 452 | "params": { 453 | "channel_guid": {"types": "str"}, 454 | "search_text": {"types": ["str", "optional"]}, 455 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 456 | } 457 | }, 458 | "GetChannelAdminMembers": { 459 | "params": { 460 | "channel_guid": {"types": "str"}, 461 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 462 | } 463 | }, 464 | "UpdateChannelUsername": { 465 | "params": { 466 | "channel_guid": {"types": "str"}, 467 | "username": {"types": "str"} 468 | } 469 | }, 470 | "GetChannelLink": { 471 | "params": { 472 | "channel_guid": {"types": "str"} 473 | } 474 | }, 475 | "SetChannelLink": { 476 | "params": { 477 | "channel_guid": {"types": "str"} 478 | } 479 | }, 480 | "GetChannelAdminAccessList": { 481 | "params": { 482 | "channel_guid": {"types": "str"}, 483 | "member_guid": {"types": "str"} 484 | } 485 | }, 486 | "CreateChannelVoiceChat": { 487 | "params": { 488 | "object_guid": {"types": "str"}, 489 | } 490 | }, 491 | "SetChannelVoiceChatSetting": { 492 | "updated_parameters": True, 493 | "params": { 494 | "channel_guid": {"types": "str"}, 495 | "voice_chat_id": {"types": "str"}, 496 | "title": {"types": "str"}, 497 | "updated_parameters": {"types": ["list"], "alloweds": ["title"]} 498 | } 499 | } 500 | }, 501 | "contacts": { 502 | "Values": [], 503 | "DeleteContact": { 504 | "params": { 505 | "user_guid": {"types": "str"} 506 | } 507 | }, 508 | "AddAddressBook": { 509 | "params": { 510 | "phone": {"types": "str", "func": "get_phone"}, 511 | "first_name": {"types": "str"}, 512 | "last_name": {"types": ["str", "optional"], "default": ""} 513 | } 514 | }, 515 | "GetContactsUpdates": { 516 | "params": { 517 | "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} 518 | } 519 | }, 520 | "GetContacts": { 521 | "params": { 522 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 523 | } 524 | } 525 | }, 526 | "settings": { 527 | "Values": ["Nobody", "Everybody", "MyContacts", "Bots", "Groups", "Contacts", "Channels", "NonConatcts"], 528 | "SetSetting": { 529 | "updated_parameters": True, 530 | "params": { 531 | "updated_parameters": {"types": ["list"], "alloweds": ["show_my_last_online", "show_my_phone_number", "show_my_profile_photo", "link_forward_message", "can_join_chat_by"]}, 532 | "show_my_last_online": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, 533 | "show_my_phone_number": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, 534 | "show_my_profile_photo": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]}, 535 | "link_forward_message": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, 536 | "can_join_chat_by": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]} 537 | } 538 | }, 539 | "AddFolder": { 540 | "params": { 541 | "cname": {"types": "str"}, 542 | "include_chat_types": { 543 | "types": ["str", "list", "optional"], 544 | "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, 545 | "exclude_chat_types": { 546 | "types": ["str", "list", "optional"], 547 | "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, 548 | "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, 549 | "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} 550 | } 551 | }, 552 | "GetFolders": { 553 | "params": { 554 | "last_state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} 555 | } 556 | }, 557 | "EditFolder": { 558 | "updated_parameters": True, 559 | "params": { 560 | "updated_parameters": {"types": ["list"], "alloweds": ["include_chat_types", "exclude_chat_types", "include_object_guids", "exclude_object_guids"]}, 561 | "cname": {"types": "str"}, 562 | "include_chat_types": { 563 | "types": ["str", "list", "optional"], 564 | "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, 565 | "exclude_chat_types": { 566 | "types": ["str", "list", "optional"], 567 | "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, 568 | "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, 569 | "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} 570 | } 571 | }, 572 | "DeleteFolder": { 573 | "params": { 574 | "folder_id": {"types": "str"} 575 | } 576 | }, 577 | "UpdateProfile": { 578 | "updated_parameters": True, 579 | "params": { 580 | "updated_parameters": {"types": ["list"], "alloweds": ["first_name", "last_name", "bio"]}, 581 | "first_name": {"types": ["str", "optional"]}, 582 | "last_name": {"types": ["str", "optional"]}, 583 | "bio": {"types": ["str", "optional"]} 584 | } 585 | }, 586 | "UpdateUsername": { 587 | "params": { 588 | "username": {"types": "str"} 589 | } 590 | }, 591 | "GetTwoPasscodeStatus": None, 592 | "GetSuggestedFolders": None, 593 | "GetPrivacySetting": None, 594 | "GetBlockedUsers": None, 595 | "GetMySessions": None, 596 | "TerminateSession": { 597 | "params": { 598 | "session_key": {"types": "str"} 599 | } 600 | }, 601 | "SetupTwoStepVerification": { 602 | "params": { 603 | "password": {"types": ["str", "int"], "func": "to_string"}, 604 | "hint": {"types": ["str", "int"], "func": "to_string"}, 605 | "recovery_email": {"types": "str"} 606 | } 607 | } 608 | }, 609 | "stickers": { 610 | "Values": ["All", "Add", "Remove"], 611 | "GetMyStickerSets": None, 612 | "SearchStickers": { 613 | "params": { 614 | "search_text": {"types": ["str", "optional"], "default": ""}, 615 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 616 | } 617 | }, 618 | "GetStickerSetByID": { 619 | "params": { 620 | "sticker_set_id": {"types": "str"} 621 | } 622 | }, 623 | "ActionOnStickerSet": { 624 | "params": { 625 | "sticker_set_id": {"types": "str"}, 626 | "action": {"types": ["str", "optional"], "alloweds": ["Add", "Remove"], "default": "Add"} 627 | } 628 | }, 629 | "GetStickersByEmoji": { 630 | "params": { 631 | "emoji": {"types": "str", "cname": "emoji_character"}, 632 | "suggest_by": {"types": ["str", "optional"], "default": "Add"} 633 | } 634 | }, 635 | "GetStickersBySetIDs": { 636 | "params": { 637 | "sticker_set_ids": {"types": ["str", "list"], "func": "to_array"} 638 | } 639 | }, 640 | "GetTrendStickerSets": { 641 | "params": { 642 | "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} 643 | } 644 | } 645 | 646 | }, 647 | "authorisations": { 648 | "Values": ["SMS", "Internal"], 649 | "GetDCs": { 650 | "urls": ["https://getdcmess.iranlms.ir/", "https://getdcmess1.iranlms.ir/", "https://getdcmess2.iranlms.ir/"], 651 | "encrypt": False, 652 | "params": { 653 | "api_version": {"types": ["int", "str"], "func": "to_string", "default": "4"} 654 | } 655 | }, 656 | "SignIn": { 657 | "tmp_session": True, 658 | "params": { 659 | "phone_code": {"types": "str"}, 660 | "phone_number": {"types": "str", "func": "get_phone"}, 661 | "phone_code_hash": {"types": "str"}, 662 | "public_key": {"types": "str"}, 663 | } 664 | }, 665 | "SendCode": { 666 | "tmp_session": True, 667 | "params": { 668 | "phone_number": {"types": "str", "func": "get_phone"}, 669 | "pass_key": {"types": ["str", "optional"], "default": None}, 670 | "send_type": {"types": ["str", "optional"], "alloweds": ["SMS", "Internal"], "default": "SMS"} 671 | } 672 | }, 673 | "RegisterDevice": { 674 | "params": { 675 | "uaer_agent": {"types": "str", "func": "get_browser", "unpack": True}, 676 | "app_version": {"types": "str"}, 677 | "lang_code": {"types": ["str", "optional"], "default": "fa"} 678 | } 679 | }, 680 | "LoginDisableTwoStep": { 681 | "tmp_session": True, 682 | "params": { 683 | "phone_number": {"types": "str", "func": "get_phone"}, 684 | "email_code": {"types": ["str", "int"], "func": "to_string"}, 685 | "forget_password_code_hash": {"types": "str"} 686 | } 687 | } 688 | } 689 | } -------------------------------------------------------------------------------- /rubpy/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | #import io 3 | import aiofiles 4 | import asyncio 5 | #import asyncio 6 | import logging 7 | from .crypto import Crypto 8 | from .structs import Struct 9 | from Crypto.PublicKey import RSA 10 | from . import __name__ as logger_name 11 | from .network import Connection, Proxies 12 | from .gadgets import exceptions, methods, thumbnail 13 | from .sessions import StringSession, SQLiteSession 14 | 15 | 16 | class Client: 17 | configuire = { 18 | 'package': 'web.rubika.ir', 19 | 'platform': 'Web', 20 | 'app_name': 'Main', 21 | 'user_agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 22 | 'AppleWebKit/537.36 (KHTML, like Gecko)' 23 | 'Chrome/102.0.0.0 Safari/537.36'), 24 | 'api_version': '6', 25 | 'app_version': '4.3.3' 26 | } 27 | 28 | def __init__(self, 29 | session, 30 | proxy=None, 31 | logger=None, 32 | timeout=20, 33 | lang_code='fa', 34 | user_agent=None, 35 | request_retries=5, *args, **kwargs): 36 | 37 | """Client 38 | Args: 39 | session_name (`str` | `rubpy.sessions.StringSession`): 40 | The file name of the session file that is used 41 | if there is a string Given (may be a complete path) 42 | or it could be a string session 43 | [rubpy.sessions.StringSession] 44 | 45 | proxy (` rubpy.network.Proxies `, optional): To set up a proxy 46 | 47 | user_agent (`str`, optional): 48 | Client uses the web version, You can set the usr-user_agent 49 | 50 | timeout (`int` | `float`, optional): 51 | To set the timeout `` default( `20 seconds` )`` 52 | 53 | logger (`logging.Logger`, optional): 54 | Logger base for use. 55 | 56 | lang_code(`str`, optional): 57 | To set the lang_code `` default( `fa` ) `` 58 | """ 59 | 60 | if isinstance(session, str): 61 | session = SQLiteSession(session) 62 | 63 | elif not isinstance(session, StringSession): 64 | raise TypeError('The given session must be a ' 65 | 'str or [rubpy.sessions.StringSession]') 66 | 67 | if not isinstance(logger, logging.Logger): 68 | logger = logging.getLogger(logger_name) 69 | 70 | if proxy and not isinstance(proxy, Proxies): 71 | raise TypeError( 72 | 'The given proxy must be a [rubpy.network.Proxies]') 73 | 74 | self._dcs = None 75 | self._key = None 76 | self._auth = None 77 | self._guid = None 78 | self._proxy = proxy 79 | self._logger = logger 80 | self._timeout = timeout 81 | self._session = session 82 | self._handlers = {} 83 | self._request_retries = request_retries 84 | self._user_agent = user_agent or self.configuire['user_agent'] 85 | self._platform = { 86 | 'package': kwargs.get('package', self.configuire['package']), 87 | 'platform': kwargs.get('platform', self.configuire['platform']), 88 | 'app_name': kwargs.get('app_name', self.configuire['app_name']), 89 | 'app_version': kwargs.get('app_version', 90 | self.configuire['app_version']), 91 | 'lang_code': lang_code} 92 | 93 | async def __call__(self, request: object): 94 | try: 95 | result = await self._connection.execute(request) 96 | 97 | # update session 98 | if result.__name__ == 'signIn' and result.status == 'OK': 99 | result.auth = Crypto.decrypt_RSA_OAEP(self._private_key, result.auth) 100 | self._key = Crypto.passphrase(result.auth) 101 | self._auth = result.auth 102 | self._session.insert( 103 | auth=self._auth, 104 | guid=result.user.user_guid, 105 | user_agent=self._user_agent, 106 | phone_number=result.user.phone, 107 | private_key=self._private_key) 108 | 109 | await self( 110 | methods.authorisations.RegisterDevice( 111 | self._user_agent, 112 | lang_code=self._platform['lang_code'], 113 | app_version=self._platform['app_version']) 114 | ) 115 | 116 | return result 117 | 118 | except AttributeError: 119 | raise exceptions.NoConnection( 120 | 'You must first connect the Client' 121 | ' with the *.connect() method') 122 | 123 | async def __aenter__(self): 124 | return await self.start(phone_number=None) 125 | 126 | async def __aexit__(self, *args, **kwargs): 127 | return await self.disconnect() 128 | 129 | async def start(self, phone_number: str, *args, **kwargs): 130 | if not hasattr(self, '_connection'): 131 | await self.connect() 132 | 133 | try: 134 | self._logger.info('user info', extra={'data': await self.get_me()}) 135 | 136 | except (exceptions.NotRegistrred, exceptions.InvalidInput): 137 | self._logger.debug('user not registered!') 138 | if phone_number is None: 139 | phone_number = input('Phone Number: ') 140 | is_phone_number_true = True 141 | while is_phone_number_true: 142 | if input(f'Is the {phone_number} correct[y or n] > ').lower() == 'y': 143 | is_phone_number_true = False 144 | else: 145 | phone_number = input('Phone Number: ') 146 | 147 | if phone_number.startswith('0'): 148 | phone_number = '98{}'.format(phone_number[1:]) 149 | elif phone_number.startswith('+98'): 150 | phone_number = phone_number[1:] 151 | elif phone_number.startswith('0098'): 152 | phone_number = phone_number[2:] 153 | 154 | result = await self( 155 | methods.authorisations.SendCode( 156 | phone_number=phone_number, *args, **kwargs)) 157 | 158 | if result.status == 'SendPassKey': 159 | while True: 160 | pass_key = input(f'Password [{result.hint_pass_key}] > ') 161 | result = await self( 162 | methods.authorisations.SendCode( 163 | phone_number=phone_number, 164 | pass_key=pass_key, *args, **kwargs)) 165 | 166 | if result.status == 'OK': 167 | break 168 | 169 | public_key, self._private_key = Crypto.create_keys() 170 | while True: 171 | phone_code = input('Code: ') 172 | result = await self( 173 | methods.authorisations.SignIn( 174 | phone_code=phone_code, 175 | phone_number=phone_number, 176 | phone_code_hash=result.phone_code_hash, 177 | public_key=public_key, 178 | *args, **kwargs)) 179 | 180 | if result.status == 'OK': 181 | break 182 | 183 | return self 184 | 185 | async def connect(self): 186 | self._connection = Connection(client=self) 187 | information = self._session.information() 188 | self._logger.info(f'the session information was read {information}') 189 | if information: 190 | self._auth = information[1] 191 | self._guid = information[2] 192 | self._private_key = information[4] 193 | if isinstance(information[3], str): 194 | self._user_agent = information[3] 195 | 196 | return self 197 | 198 | async def disconnect(self): 199 | try: 200 | await self._connection.close() 201 | self._logger.info(f'the client was disconnected') 202 | 203 | except AttributeError: 204 | raise exceptions.NoConnection( 205 | 'You must first connect the Client' 206 | ' with the *.connect() method') 207 | 208 | async def run_until_disconnected(self): 209 | return await self._connection.receive_updates() 210 | 211 | # handler methods 212 | 213 | def on(self, handler): 214 | def MetaHandler(func): 215 | self.add_handler(func, handler) 216 | return func 217 | return MetaHandler 218 | 219 | def add_handler(self, func, handler): 220 | self._handlers[func] = handler 221 | 222 | def remove_handler(self, func): 223 | try: 224 | self._handlers.pop(func) 225 | except KeyError: 226 | pass 227 | 228 | # async methods 229 | 230 | async def get_me(self, *args, **kwargs): 231 | return await self(methods.users.GetUserInfo(self._guid)) 232 | 233 | async def upload(self, file, *args, **kwargs): 234 | return await self._connection.upload_file(file=file, *args, **kwargs) 235 | 236 | async def download_file_inline(self, file_inline, file: str = None, *args, **kwargs): 237 | result = await self._connection.download( 238 | file_inline.dc_id, 239 | file_inline.file_id, 240 | file_inline.access_hash_rec) 241 | 242 | if isinstance(file, str): 243 | with open(file, 'wb+') as _file: 244 | _file.write(result) 245 | return file 246 | 247 | return result 248 | 249 | # ---------------- Users Methods ---------------- 250 | 251 | async def get_user_info(self, user_guid: str): 252 | return await self(methods.users.GetUserInfo(user_guid)) 253 | 254 | async def block_user(self, user_guid: str): 255 | return await self(methods.users.SetBlockUser(user_guid, 'Block')) 256 | 257 | async def unblock_user(self, user_guid: str): 258 | return await self(methods.users.SetBlockUser(user_guid, 'Unblock')) 259 | 260 | async def delete_user_chat(self, user_guid: str, last_deleted_message_id: str): 261 | return await self(methods.users.DeleteUserChat(user_guid, last_deleted_message_id)) 262 | 263 | async def check_user_username(self, username: str): 264 | return await self(methods.users.CheckUserUsername(username.replace('@', ''))) 265 | 266 | # ---------------- Chats Methods ---------------- 267 | 268 | async def upload_avatar(self, object_guid: str, main_file_id: str, thumbnail_file_id: str): 269 | return await self(methods.chats.UploadAvatar(object_guid, main_file_id, thumbnail_file_id)) 270 | 271 | async def delete_avatar(self, object_guid: str, avatar_id: str): 272 | return await self(methods.chats.DeleteAvatar(object_guid, avatar_id)) 273 | 274 | async def get_avatars(self, object_guid: str): 275 | return await self(methods.chats.GetAvatars(object_guid)) 276 | 277 | async def get_chats(self, start_id: int = None): 278 | return await self(methods.chats.GetChats(start_id)) 279 | 280 | async def seen_chats(self, seen_list: dict): 281 | return await self(methods.chats.SeenChats(seen_list)) 282 | 283 | async def get_chat_ads(self, state: int): 284 | return await self(methods.chats.GetChatAds(state)) 285 | 286 | async def set_action_chat(self, object_guid: str, action: str): 287 | ''' 288 | alloweds: ["Mute", "Unmute"] 289 | result = await client.set_action_chat('object_guid', 'Mute') 290 | print(result) 291 | ''' 292 | return await self(methods.chats.SetActionChat(object_guid, action)) 293 | 294 | async def get_chats_updates(self, state: int = None): 295 | return await self(methods.chats.GetChatsUpdates(state)) 296 | 297 | async def send_chat_activity(self, object_guid: str, activity: str = None): 298 | return await self(methods.chats.SendChatActivity(object_guid, activity)) 299 | 300 | async def delete_chat_history(self, object_guid: str): 301 | return await self(methods.chats.DeleteChatHistory(object_guid)) 302 | 303 | async def search_chat_messages(self, object_guid: str, search_text: str, type: str = 'Hashtag'): 304 | return await self(methods.chats.SearchChatMessages(object_guid, search_text, type)) 305 | 306 | # ---------------- Extras Methods ---------------- 307 | 308 | async def search_global_objects(self, search_text: str): 309 | return await self(methods.extras.SearchGlobalObjects(search_text)) 310 | 311 | async def get_abs_objects(self, objects_guids: list): 312 | return await self(methods.extras.GetAbsObjects(objects_guids)) 313 | 314 | async def get_object_by_username(self, username: str): 315 | return await self(methods.extras.GetObjectByUsername(username.replace('@', ''))) 316 | 317 | async def get_link_from_app_url(self, app_url: str): 318 | return await self(methods.extras.GetLinkFromAppUrl(app_url)) 319 | 320 | async def create_voice_call(self, object_guid: str): 321 | if object_guid.startswith('c'): 322 | return await self(methods.channels.CreateChannelVoiceChat(object_guid)) 323 | elif object_guid.startswith('g'): 324 | return await self(methods.groups.CreateGroupVoiceChat(object_guid)) 325 | else: 326 | print('Invalid Object Guid') 327 | return False 328 | 329 | async def set_voice_chat_setting(self, object_guid: str, voice_chat_id: str, title: str = None): 330 | if object_guid.startswith('c'): 331 | return await self(methods.channels.SetChannelVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) 332 | elif object_guid.startswith('g'): 333 | return await self(methods.groups.SetGroupVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) 334 | else: 335 | print('Invalid Object Guid') 336 | return False 337 | 338 | # ---------------- Groups Methods ---------------- 339 | 340 | async def add_group(self, title: str, member_guids: list): 341 | return await self(methods.groups.AddGroup(title, member_guids)) 342 | 343 | async def join_group(self, link: str): 344 | return await self(methods.groups.JoinGroup(link)) 345 | 346 | async def leave_group(self, group_guid: str): 347 | return await self(methods.groups.LeaveGroup(group_guid)) 348 | 349 | async def remove_group(self, group_guid: str): 350 | return await self(methods.groups.RemoveGroup(group_guid)) 351 | 352 | async def get_group_info(self, group_guid: str): 353 | return await self(methods.groups.GetGroupInfo(group_guid)) 354 | 355 | async def get_group_link(self, group_guid: str): 356 | return await self(methods.groups.GetGroupLink(group_guid)) 357 | 358 | async def set_group_link(self, group_guid: str): 359 | return await self(methods.groups.SetGroupLink(group_guid)) 360 | 361 | async def edit_group_info(self, 362 | group_guid: str, 363 | title: str = None, 364 | description: str = None, 365 | chat_history_for_new_members: str = None, 366 | ): 367 | updated_parameters = [] 368 | 369 | if title: 370 | updated_parameters.append('title') 371 | if description: 372 | updated_parameters.append('description') 373 | if chat_history_for_new_members: 374 | updated_parameters.append('chat_history_for_new_members') 375 | 376 | return await self(methods.groups.EditGroupInfo( 377 | group_guid, updated_parameters, title, description, chat_history_for_new_members)) 378 | 379 | async def set_group_admin(self, 380 | group_guid: str, 381 | member_guid: str, 382 | access_list: list, 383 | action: str = 'SetAdmin', 384 | ): 385 | return await self(methods.groups.SetGroupAdmin(group_guid, member_guid, access_list, action)) 386 | 387 | async def ban_group_member(self, group_guid: str, member_guid: str): 388 | return await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Set')) 389 | 390 | async def unban_group_member(self, group_guid: str, member_guid: str): 391 | return await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Unset')) 392 | 393 | async def add_group_members(self, group_guid: str, member_guids: list): 394 | return await self(methods.groups.AddGroupMembers(group_guid, member_guids)) 395 | 396 | async def get_group_all_members(self, group_guid: str, search_text: str = '', start_id: int = None): 397 | return await self(methods.groups.GetGroupAllMembers(group_guid, search_text, start_id)) 398 | 399 | async def get_group_admin_members(self, group_guid: str, start_id: int = None): 400 | return await self(methods.groups.GetGroupAdminMembers(group_guid, start_id)) 401 | 402 | async def get_group_mention_list(self, group_guid: str, search_mention: str = None): 403 | return await self(methods.groups.GetGroupMentionList(group_guid, search_mention)) 404 | 405 | async def get_group_default_access(self, group_guid: str): 406 | return await self(methods.groups.GetGroupDefaultAccess(group_guid)) 407 | 408 | async def set_group_default_access(self, group_guid: str, access_list: list): 409 | return await self(methods.groups.SetGroupDefaultAccess(group_guid, access_list)) 410 | 411 | async def group_preview_by_join_link(self, group_link: str): 412 | return await self(methods.groups.GroupPreviewByJoinLink(group_link)) 413 | 414 | async def delete_no_access_group_chat(self, group_guid: str): 415 | return await self(methods.groups.DeleteNoAccessGroupChat(group_guid)) 416 | 417 | async def get_group_admin_access_list(self, group_guid: str, member_guid: str): 418 | return await self(methods.groups.GetGroupAdminAccessList(group_guid, member_guid)) 419 | 420 | async def set_group_timer(self, group_guid: str, time: int): 421 | return await self(methods.groups.EditGroupInfo(group_guid, slow_mode=time, updated_parameters=['slow_mode'])) 422 | 423 | # ---------------- Messages Methods ---------------- 424 | 425 | async def send_message(self, 426 | object_guid: str, 427 | message=None, 428 | reply_to_message_id: str = None, 429 | file_inline=None, 430 | type: str = methods.messages.File, 431 | thumb: bool = True, *args, **kwargs 432 | ): 433 | """_send message_ 434 | 435 | Args: 436 | object_guid (str): 437 | _object guid_ 438 | 439 | message (Any, optional): 440 | _message or cation or sticker_ . Defaults to None. 441 | 442 | reply_to_message_id (str, optional): 443 | _reply to message id_. Defaults to None. 444 | 445 | file_inline (typing.Union[pathlib.Path, bytes], optional): 446 | _file_. Defaults to None. 447 | 448 | type (str, optional): 449 | _file type_. Defaults to methods.messages.File.( 450 | methods.messages.Gif, 451 | methods.messages.Image, 452 | methods.messages.Voice, 453 | methods.messages.Music, 454 | methods.messages.Video 455 | ) 456 | 457 | thumb (bool, optional): 458 | if value is "True", 459 | the lib will try to build the thumb ( require cv2 ) 460 | if value is thumbnail.Thumbnail, to set custom 461 | Defaults to True. 462 | """ 463 | 464 | if object_guid.lower() in ['me', 'self', 'cloud']: 465 | object_guid = self._guid 466 | 467 | if file_inline is not None: 468 | if not isinstance(file_inline, Struct): 469 | if isinstance(file_inline, str): 470 | with open(file_inline, 'rb') as file: 471 | kwargs['file_name'] = kwargs.get( 472 | 'file_name', os.path.basename(file_inline)) 473 | file_inline = file.read() 474 | 475 | if thumb is True: 476 | if type == methods.messages.Image: 477 | thumb = thumbnail.MakeThumbnail(file_inline) 478 | 479 | elif type in [methods.messages.Gif, methods.messages.Video]: 480 | thumb = thumbnail.MakeThumbnail.from_video(file_inline) 481 | 482 | if thumb.image is None: 483 | type = methods.messages.File 484 | thumb = None 485 | 486 | # the problem will be fixed in the next version #debug 487 | # to avoid getting InputError 488 | # values are not checked in Rubika (optional) 489 | file_inline = await self.upload(file_inline, *args, **kwargs) 490 | file_inline['type'] = type 491 | file_inline['time'] = kwargs.get('time', 1) 492 | file_inline['width'] = kwargs.get('width', 200) 493 | file_inline['height'] = kwargs.get('height', 200) 494 | file_inline['music_performer'] = kwargs.get('performer', '') 495 | 496 | if isinstance(thumb, thumbnail.Thumbnail): 497 | file_inline['time'] = thumb.seconds 498 | file_inline['width'] = thumb.width 499 | file_inline['height'] = thumb.height 500 | file_inline['thumb_inline'] = thumb.to_base64() 501 | 502 | return await self( 503 | methods.messages.SendMessage( 504 | object_guid, 505 | message=message, 506 | file_inline=file_inline, 507 | reply_to_message_id=reply_to_message_id)) 508 | 509 | async def send_photo(self, 510 | object_guid: str, 511 | photo: bytes, 512 | caption: str = None, 513 | file_name: str = None, 514 | width: int = None, 515 | height: int = None, 516 | thumb: str = None, 517 | reply_to_message_id: str = None, 518 | *args, 519 | **kwargs, 520 | ): 521 | if object_guid.lower() in ('me', 'self', 'cloud'): 522 | object_guid = self._guid 523 | 524 | if type(photo) != bytes: 525 | async with aiofiles.open(photo, 'rb') as file: 526 | file_name = os.path.basename(photo) 527 | kwargs['file_name'] = kwargs.get('file_name', file_name) 528 | photo = await file.read() 529 | await file.close() 530 | else: 531 | kwargs['file_name'] = kwargs.get('file_name', file_name) 532 | 533 | thumb = thumbnail.MakeThumbnail(photo) 534 | 535 | file_inline = await self.upload(photo, *args, **kwargs) 536 | file_inline['type'] = 'Image' 537 | 538 | if thumb and width and height != None: 539 | file_inline['width'] = width 540 | file_inline['height'] = height 541 | file_inline['thumb_inline'] = thumb 542 | else: 543 | if isinstance(thumb, thumbnail.Thumbnail): 544 | file_inline['width'] = thumb.width 545 | file_inline['height'] = thumb.height 546 | file_inline['thumb_inline'] = thumb.to_base64() 547 | 548 | return await self( 549 | methods.messages.SendMessage( 550 | object_guid, 551 | message=caption, 552 | file_inline=file_inline, 553 | reply_to_message_id=reply_to_message_id)) 554 | 555 | async def send_file(self, 556 | object_guid: str, 557 | file: bytes, 558 | caption: str = None, 559 | file_name: str = None, 560 | reply_to_message_id: str = None, 561 | *args, 562 | **kwargs, 563 | ): 564 | if object_guid.lower() in ('me', 'self', 'cloud'): 565 | object_guid = self._guid 566 | 567 | if type(file) != bytes: 568 | async with aiofiles.open(file, 'rb') as ofile: 569 | file_name = os.path.basename(file) 570 | kwargs['file_name'] = kwargs.get('file_name', file_name) 571 | file = await ofile.read() 572 | await ofile.close() 573 | else: 574 | kwargs['file_name'] = kwargs.get('file_name', file_name) 575 | 576 | file_inline = await self.upload(file, *args, **kwargs) 577 | file_inline['type'] = 'File' 578 | 579 | return await self( 580 | methods.messages.SendMessage( 581 | object_guid, 582 | message=caption, 583 | file_inline=file_inline, 584 | reply_to_message_id=reply_to_message_id)) 585 | 586 | async def send_gif(self, 587 | object_guid: str, 588 | gif: bytes, 589 | caption: str = None, 590 | file_name: str = None, 591 | thumb: str = None, 592 | time: str = None, 593 | width: int = None, 594 | height: int = None, 595 | reply_to_message_id: str = None, 596 | *args, 597 | **kwargs, 598 | ): 599 | if object_guid.lower() in ('me', 'self', 'cloud'): 600 | object_guid = self._guid 601 | 602 | if type(gif) != bytes: 603 | async with aiofiles.open(gif, 'rb') as file: 604 | file_name = os.path.basename(gif) 605 | kwargs['file_name'] = kwargs.get('file_name', file_name) 606 | file = await file.read() 607 | await file.close() 608 | else: 609 | kwargs['file_name'] = kwargs.get('file_name', file_name) 610 | 611 | file_inline = await self.upload(gif, *args, **kwargs) 612 | file_inline['type'] = 'Gif' 613 | 614 | thumb = thumbnail.MakeThumbnail.from_video(gif) 615 | 616 | if thumb and width and height and time != None: 617 | file_inline['width'] = width 618 | file_inline['height'] = height 619 | file_inline['thumb_inline'] = thumb 620 | file_inline['time'] = time 621 | else: 622 | if isinstance(thumb, thumbnail.Thumbnail): 623 | file_inline['time'] = thumb.seconds 624 | file_inline['width'] = thumb.width 625 | file_inline['height'] = thumb.height 626 | file_inline['thumb_inline'] = thumb.to_base64() 627 | 628 | return await self( 629 | methods.messages.SendMessage( 630 | object_guid, 631 | message=caption, 632 | file_inline=file_inline, 633 | reply_to_message_id=reply_to_message_id)) 634 | 635 | async def send_video(self, 636 | object_guid: str, 637 | video: bytes, 638 | caption: str = None, 639 | file_name: str = None, 640 | thumb: str = None, 641 | time: str = None, 642 | width: int = None, 643 | height: int = None, 644 | reply_to_message_id: str = None, 645 | *args, 646 | **kwargs, 647 | ): 648 | if object_guid.lower() in ('me', 'self', 'cloud'): 649 | object_guid = self._guid 650 | 651 | if type(video) != bytes: 652 | async with aiofiles.open(video, 'rb') as file: 653 | file_name = os.path.basename(video) 654 | kwargs['file_name'] = kwargs.get('file_name', file_name) 655 | video = await file.read() 656 | await file.close() 657 | else: 658 | kwargs['file_name'] = kwargs.get('file_name', file_name) 659 | 660 | file_inline = await self.upload(video, *args, **kwargs) 661 | file_inline['type'] = 'Video' 662 | 663 | thumb = thumbnail.MakeThumbnail.from_video(video) 664 | 665 | if thumb and width and height and time != None: 666 | file_inline['width'] = width 667 | file_inline['height'] = height 668 | file_inline['thumb_inline'] = thumb 669 | file_inline['time'] = time 670 | else: 671 | if isinstance(thumb, thumbnail.Thumbnail): 672 | file_inline['time'] = thumb.seconds 673 | file_inline['width'] = thumb.width 674 | file_inline['height'] = thumb.height 675 | file_inline['thumb_inline'] = thumb.to_base64() 676 | 677 | return await self( 678 | methods.messages.SendMessage( 679 | object_guid, 680 | message=caption, 681 | file_inline=file_inline, 682 | reply_to_message_id=reply_to_message_id)) 683 | 684 | async def send_music(self, 685 | object_guid: str, 686 | music: bytes, 687 | caption: str = None, 688 | file_name: str = None, 689 | time: str = None, 690 | music_performer: str = None, 691 | reply_to_message_id: str = None, 692 | *args, 693 | **kwargs, 694 | ): 695 | if object_guid.lower() in ('me', 'self', 'cloud'): 696 | object_guid = self._guid 697 | 698 | if type(music) != bytes: 699 | async with aiofiles.open(music, 'rb') as music_file: 700 | file_name = os.path.basename(music) 701 | kwargs['file_name'] = kwargs.get('file_name', file_name) 702 | music = await music_file.read() 703 | 704 | else: 705 | kwargs['file_name'] = kwargs.get('file_name', file_name) 706 | 707 | file_inline = await self.upload(music, *args, **kwargs) 708 | file_inline['type'] = 'Music' 709 | file_inline['auto_play'] = False 710 | file_inline['music_performer'] = kwargs.get('performer', music_performer or '') 711 | 712 | if time != None: 713 | file_inline['time'] = time 714 | else: 715 | file_inline['time'] = '60' 716 | 717 | return await self( 718 | methods.messages.SendMessage( 719 | object_guid, 720 | message=caption, 721 | file_inline=file_inline, 722 | reply_to_message_id=reply_to_message_id)) 723 | 724 | async def send_voice(self, 725 | object_guid: str, 726 | music: bytes, 727 | caption: str = None, 728 | file_name: str = None, 729 | time: str = None, 730 | reply_to_message_id: str = None, 731 | *args, 732 | **kwargs, 733 | ): 734 | if object_guid.lower() in ('me', 'self', 'cloud'): 735 | object_guid = self._guid 736 | 737 | if type(music) != bytes: 738 | async with aiofiles.open(music, 'rb') as file: 739 | file_name = os.path.basename(music) 740 | kwargs['file_name'] = kwargs.get('file_name', file_name) 741 | music = await file.read() 742 | else: 743 | kwargs['file_name'] = kwargs.get('file_name', file_name) 744 | 745 | 746 | file_inline = await self.upload(music, *args, **kwargs) 747 | file_inline['type'] = 'Voice' 748 | file_inline['mime'] = 'ogg' 749 | file_inline['auto_play'] = False 750 | 751 | if time != None: 752 | file_inline['time'] = time 753 | else: 754 | file_inline['time'] = '60' 755 | 756 | return await self( 757 | methods.messages.SendMessage( 758 | object_guid, 759 | message=caption, 760 | file_inline=file_inline, 761 | reply_to_message_id=reply_to_message_id)) 762 | 763 | async def edit_message(self, object_guid: str, message_id: str, text: str): 764 | return await self(methods.messages.EditMessage(object_guid, message_id, text)) 765 | 766 | async def delete_messages(self, object_guid: str, message_ids: list, type: str = 'Global'): 767 | return await self(methods.messages.DeleteMessages(object_guid, message_ids, type)) 768 | 769 | async def request_send_file(self, file_name: str, size: int, mime: str): 770 | return await self(methods.messages.RequestSendFile(file_name, size, mime)) 771 | 772 | async def forward_messages(self, from_object_guid: str, to_object_guid: str, message_ids: list): 773 | return await self(methods.messages.ForwardMessages(from_object_guid, to_object_guid, message_ids)) 774 | 775 | async def create_poll(self, 776 | object_guid: str, 777 | question: str, 778 | options: list, 779 | type: str = 'Regular', 780 | is_anonymous: bool = True, 781 | allows_multiple_answers: bool = False, 782 | correct_option_index: int = 0, 783 | explanation: str = None, 784 | reply_to_message_id: int = 0, 785 | ): 786 | if type == 'Regular': 787 | return await self(methods.messages.CreatePoll( 788 | object_guid=object_guid, 789 | question=question, 790 | options=options, 791 | allows_multiple_answers=allows_multiple_answers, 792 | is_anonymous=is_anonymous, 793 | reply_to_message_id=reply_to_message_id, 794 | type=type, 795 | )) 796 | else: 797 | return await self(methods.messages.CreatePoll( 798 | object_guid=object_guid, 799 | question=question, 800 | options=options, 801 | allows_multiple_answers=allows_multiple_answers, 802 | is_anonymous=is_anonymous, 803 | reply_to_message_id=reply_to_message_id, 804 | correct_option_index=correct_option_index, 805 | explanation=explanation, 806 | type=type, 807 | )) 808 | 809 | async def vote_poll(self, poll_id: str, selection_index: int): 810 | return await self(methods.messages.VotePoll(poll_id, selection_index)) 811 | 812 | async def get_poll_status(self, poll_id: str): 813 | return await self(methods.messages.GetPollStatus(poll_id)) 814 | 815 | async def get_poll_option_voters(self, poll_id: str, selection_index: int, start_id: int = None): 816 | return await self(methods.messages.GetPollOptionVoters(poll_id, selection_index, start_id)) 817 | 818 | async def set_pin_message(self, object_guid: str, message_id: str, action: str = 'Pin'): 819 | return await self(methods.messages.SetPinMessage(object_guid, message_id, action)) 820 | 821 | async def unset_pin_message(self, object_guid: str, message_id: str, action: str = 'Unpin'): 822 | return await self(methods.messages.SetPinMessage(object_guid, message_id, action)) 823 | 824 | async def get_messages_updates(self, object_guid: str, state: int = None): 825 | return await self(methods.messages.GetMessagesUpdates(object_guid, state)) 826 | 827 | async def search_global_messages(self, search_text: str, type: str = 'Text'): 828 | return await self(methods.messages.SearchGlobalMessages(search_text, type)) 829 | 830 | async def click_message_url(self, object_guid: str, message_id: str, link_url: str): 831 | return await self(methods.messages.ClickMessageUrl(object_guid, message_id, link_url)) 832 | 833 | async def get_messages_by_ID(self, object_guid: str, message_ids: list): 834 | return await self(methods.messages.GetMessagesByID(object_guid, message_ids)) 835 | 836 | async def get_messages(self, object_guid: str, min_id: int, max_id: int, sort: str = 'FromMin', limit: int = 10): 837 | return await self(methods.messages.GetMessages(object_guid, min_id, max_id, sort, limit)) 838 | 839 | async def get_messages_interval(self, object_guid: str, middle_message_id: str): 840 | return await self(methods.messages.GetMessagesInterval(object_guid, middle_message_id)) 841 | 842 | async def get_message_url(self, object_guid: str, message_id: int): 843 | if type(message_id) == str: 844 | message_id = int(message_id) 845 | return await self(methods.messages.GetMessageShareUrl(object_guid, message_id)) 846 | 847 | # ---------------- Channels Methods ---------------- 848 | 849 | async def add_channel(self, title: str, description: str = None): 850 | return await self(methods.channels.AddChannel(title, description)) 851 | 852 | async def remove_channel(self, channel_guid: str): 853 | return await self(methods.channels.RemoveChannel(channel_guid)) 854 | 855 | async def get_channel_info(self, channel_guid: str): 856 | return await self(methods.channels.GetChannelInfo(channel_guid)) 857 | 858 | async def edit_channel_info(self, 859 | channel_guid: str, 860 | title: str = None, 861 | description: str = None, 862 | channel_type: str = None, 863 | sign_messages: str = None, 864 | ): 865 | updated_parameters = [] 866 | 867 | if title: 868 | updated_parameters.append('title') 869 | if description: 870 | updated_parameters.append('description') 871 | if channel_type: 872 | updated_parameters.append('channel_type') 873 | if sign_messages: 874 | updated_parameters.append('sign_messages') 875 | 876 | return await self(methods.channels.EditChannelInfo( 877 | channel_guid, updated_parameters, title, description, channel_type, sign_messages)) 878 | 879 | async def join_channel(self, channel_guid: str): 880 | return await self(methods.channels.JoinChannelAction(channel_guid, 'Join')) 881 | 882 | async def leave_channel(self, channel_guid: str): 883 | return await self(methods.channels.JoinChannelAction(channel_guid, 'Remove')) 884 | 885 | async def archive_channel(self, channel_guid: str): 886 | return await self(methods.channels.JoinChannelAction(channel_guid, 'Archive')) 887 | 888 | async def join_channel_by_link(self, link: str): 889 | return await self(methods.channels.JoinChannelByLink(link)) 890 | 891 | async def add_channel_members(self, channel_guid: str, member_guids: list): 892 | return await self(methods.channels.AddChannelMembers(channel_guid, member_guids)) 893 | 894 | async def ban_channel_member(self, channel_guid: str, member_guid: str): 895 | return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Set')) 896 | 897 | async def unban_channel_member(self, channel_guid: str, member_guid: str): 898 | return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Unset')) 899 | 900 | async def check_channel_username(self, username: str): 901 | return await self(methods.channels.CheckChannelUsername(username)) 902 | 903 | async def channel_preview_by_join_link(self, link: str): 904 | return await self(methods.channels.ChannelPreviewByJoinLink(link)) 905 | 906 | async def get_channel_all_members(self, channel_guid: str, search_text: str = None, start_id: int = None): 907 | return await self(methods.channels.GetChannelAllMembers(channel_guid, search_text, start_id)) 908 | 909 | async def get_channel_admin_members(self, channel_guid: str, start_id: int = None): 910 | return await self(methods.channels.GetChannelAdminMembers(channel_guid, start_id)) 911 | 912 | async def update_channel_username(self, channel_guid: str, username: str): 913 | return await self(methods.channels.UpdateChannelUsername(channel_guid, username)) 914 | 915 | async def get_channel_link(self, channel_guid: str): 916 | return await self(methods.channels.GetChannelLink(channel_guid)) 917 | 918 | async def set_channel_link(self, channel_guid: str): 919 | return await self(methods.channels.SetChannelLink(channel_guid)) 920 | 921 | async def get_channel_admin_access_list(self, channel_guid: str, member_guid: str): 922 | return await self(methods.channels.GetChannelAdminAccessList(channel_guid, member_guid)) 923 | 924 | # ---------------- Contacts Methods ---------------- 925 | 926 | async def delete_contact(self, user_guid: str): 927 | return await self(methods.contacts.DeleteContact(user_guid)) 928 | 929 | async def add_address_book(self, phone: str, first_name: str, last_name: str = ''): 930 | return await self(methods.contacts.AddAddressBook(phone, first_name, last_name)) 931 | 932 | async def get_contacts_updates(self, state: int = None): 933 | return await self(methods.contacts.GetContactsUpdates(state)) 934 | 935 | async def get_contacts(self, start_id: int = None): 936 | return await self(methods.contacts.GetContacts(start_id)) 937 | 938 | # ---------------- Settings Methods ---------------- 939 | 940 | async def set_setting(self, 941 | show_my_last_online: str = None, 942 | show_my_phone_number: str = None, 943 | show_my_profile_photo: str = None, 944 | link_forward_message: str = None, 945 | can_join_chat_by: str = None 946 | ): 947 | updated_parameters = [] 948 | 949 | if show_my_last_online: 950 | updated_parameters.append('show_my_last_online') 951 | if show_my_phone_number: 952 | updated_parameters.append('show_my_phone_number') 953 | if show_my_profile_photo: 954 | updated_parameters.append('show_my_profile_photo') 955 | if link_forward_message: 956 | updated_parameters.append('link_forward_message') 957 | if can_join_chat_by: 958 | updated_parameters.append('can_join_chat_by') 959 | 960 | return await self(methods.settings.SetSetting( 961 | updated_parameters, 962 | show_my_last_online, 963 | show_my_phone_number, 964 | show_my_profile_photo, 965 | link_forward_message, 966 | can_join_chat_by)) 967 | 968 | async def add_folder(self, 969 | include_chat_types: list = None, 970 | exclude_chat_types: list = None, 971 | include_object_guids: list = None, 972 | exclude_object_guids: list = None 973 | ): 974 | return await self(methods.settings.AddFolder( 975 | include_chat_types, 976 | exclude_chat_types, 977 | include_object_guids, 978 | exclude_object_guids)) 979 | 980 | async def get_folders(self, last_state: int): 981 | return await self(methods.settings.GetFolders(last_state)) 982 | 983 | async def edit_folder(self, 984 | include_chat_types: list = None, 985 | exclude_chat_types: list = None, 986 | include_object_guids: list = None, 987 | exclude_object_guids: list = None 988 | ): 989 | updated_parameters = [] 990 | 991 | if include_chat_types: 992 | updated_parameters.append('include_chat_types') 993 | if exclude_chat_types: 994 | updated_parameters.append('exclude_chat_types') 995 | if include_object_guids: 996 | updated_parameters.append('include_object_guids') 997 | if exclude_object_guids: 998 | updated_parameters.append('exclude_object_guids') 999 | 1000 | return await self(methods.settings.EditFolder( 1001 | updated_parameters, 1002 | include_chat_types, 1003 | exclude_chat_types, 1004 | include_object_guids, 1005 | exclude_object_guids)) 1006 | 1007 | async def delete_folder(self, folder_id: str): 1008 | return await self(methods.settings.DeleteFolder(folder_id)) 1009 | 1010 | async def update_profile(self, first_name: str = None, last_name: str = None, bio: str = None): 1011 | updated_parameters = [] 1012 | 1013 | if first_name: 1014 | updated_parameters.append('first_name') 1015 | if last_name: 1016 | updated_parameters.append('last_name') 1017 | if bio: 1018 | updated_parameters.append('bio') 1019 | 1020 | return await self(methods.settings.UpdateProfile(updated_parameters, first_name, last_name, bio)) 1021 | 1022 | async def update_username(self, username: str): 1023 | return await self(methods.settings.UpdateUsername(username)) 1024 | 1025 | async def get_two_passcode_status(self): 1026 | return await self(methods.settings.GetTwoPasscodeStatus()) 1027 | 1028 | async def get_suggested_folders(self): 1029 | return await self(methods.settings.GetSuggestedFolders()) 1030 | 1031 | async def get_privacy_setting(self): 1032 | return await self(methods.settings.GetPrivacySetting()) 1033 | 1034 | async def get_blocked_users(self): 1035 | return await self(methods.settings.GetBlockedUsers()) 1036 | 1037 | async def get_my_sessions(self): 1038 | return await self(methods.settings.GetMySessions()) 1039 | 1040 | async def terminate_session(self, session_key: str): 1041 | return await self(methods.settings.TerminateSession(session_key)) 1042 | 1043 | async def setup_two_step_verification(self, password: str, hint: str, recovery_email: str): 1044 | return await self(methods.settings.SetupTwoStepVerification(password, hint, recovery_email)) 1045 | 1046 | # ---------------- Stickers Methods ---------------- 1047 | 1048 | async def get_my_sticker_sets(self): 1049 | return await self(methods.stickers.GetMyStickerSets()) 1050 | 1051 | async def search_stickers(self, search_text: str = '', start_id: int = None): 1052 | return await self(methods.stickers.SearchStickers(search_text, start_id)) 1053 | 1054 | async def get_sticker_set_by_ID(self, sticker_set_id: str): 1055 | return await self(methods.stickers.GetStickerSetByID(sticker_set_id)) 1056 | 1057 | async def action_on_sticker_set(self, sticker_set_id: str, action: str = 'Add'): 1058 | return await self(methods.stickers.ActionOnStickerSet(sticker_set_id, action)) 1059 | 1060 | async def get_stickers_by_emoji(self, emoji: str, suggest_by: str = 'Add'): 1061 | return await self(methods.stickers.GetStickersByEmoji(emoji, suggest_by)) 1062 | 1063 | async def get_stickers_by_set_IDs(self, sticker_set_ids: list): 1064 | return await self(methods.stickers.GetStickersBySetIDs(sticker_set_ids)) 1065 | 1066 | async def get_trend_sticker_sets(self, start_id: int = None): 1067 | return await self(methods.stickers.GetTrendStickerSets(start_id)) --------------------------------------------------------------------------------