├── 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 |
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'