pyrogram-client 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyro_client/client/base.py +82 -0
- pyro_client/client/bot.py +11 -0
- pyro_client/client/dc.json +180 -0
- pyro_client/client/file.py +58 -0
- pyro_client/client/single.py +38 -0
- pyro_client/client/user.py +199 -0
- pyro_client/loader.py +11 -0
- pyro_client/storage.py +130 -0
- pyrogram_client-0.0.2.dist-info/METADATA +16 -0
- pyrogram_client-0.0.2.dist-info/RECORD +12 -0
- pyrogram_client-0.0.2.dist-info/WHEEL +5 -0
- pyrogram_client-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pyrogram import Client
|
|
7
|
+
from pyrogram.filters import chat, contact, AndFilter
|
|
8
|
+
from pyrogram.handlers import MessageHandler
|
|
9
|
+
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton
|
|
10
|
+
|
|
11
|
+
from pyro_client.client.single import SingleMeta
|
|
12
|
+
from pyro_client.storage import PgStorage
|
|
13
|
+
|
|
14
|
+
AuthTopic = Literal["phone", "code", "pass"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sync_call_async(async_func, *args):
|
|
18
|
+
loop = asyncio.get_event_loop() # Получаем текущий запущенный цикл
|
|
19
|
+
if loop.is_running():
|
|
20
|
+
future = asyncio.run_coroutine_threadsafe(async_func(*args), loop)
|
|
21
|
+
return future.result(5) # Блокируемся, пока корутина не выполнится
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseClient(Client, metaclass=SingleMeta):
|
|
25
|
+
storage: PgStorage
|
|
26
|
+
|
|
27
|
+
def __init__(self, name: str, api_id, api_hash, *args, **kwargs):
|
|
28
|
+
super().__init__(name, api_id=api_id, api_hash=api_hash, *args, storage_engine=PgStorage(name), **kwargs)
|
|
29
|
+
|
|
30
|
+
async def send(
|
|
31
|
+
self,
|
|
32
|
+
txt: str,
|
|
33
|
+
uid: int | str = "me",
|
|
34
|
+
btns: list[InlineKeyboardButton | KeyboardButton] = None,
|
|
35
|
+
photo: bytes = None,
|
|
36
|
+
video: bytes = None,
|
|
37
|
+
) -> Message:
|
|
38
|
+
ikm = (
|
|
39
|
+
(
|
|
40
|
+
InlineKeyboardMarkup([btns])
|
|
41
|
+
if isinstance(btns[0], InlineKeyboardButton)
|
|
42
|
+
else ReplyKeyboardMarkup([btns], one_time_keyboard=True)
|
|
43
|
+
)
|
|
44
|
+
if btns
|
|
45
|
+
else None
|
|
46
|
+
)
|
|
47
|
+
if photo:
|
|
48
|
+
return await self.send_photo(uid, BytesIO(photo), txt, reply_markup=ikm)
|
|
49
|
+
elif video:
|
|
50
|
+
return await self.send_video(uid, BytesIO(video), txt, reply_markup=ikm)
|
|
51
|
+
else:
|
|
52
|
+
return await self.send_message(uid, txt, reply_markup=ikm)
|
|
53
|
+
|
|
54
|
+
async def wait_from(self, uid: int, topic: str, past: int = 0, timeout: int = 10) -> str | None:
|
|
55
|
+
fltr = chat(uid)
|
|
56
|
+
if topic == "phone":
|
|
57
|
+
fltr &= contact
|
|
58
|
+
handler = MessageHandler(self.got_msg, fltr)
|
|
59
|
+
# handler, g = self.add_handler(handler, 1)
|
|
60
|
+
g = 0
|
|
61
|
+
if g not in self.dispatcher.groups:
|
|
62
|
+
self.dispatcher.groups[g] = []
|
|
63
|
+
self.dispatcher.groups = OrderedDict(sorted(self.dispatcher.groups.items()))
|
|
64
|
+
self.dispatcher.groups[g].append(handler)
|
|
65
|
+
#
|
|
66
|
+
while past < timeout:
|
|
67
|
+
if txt := self.storage.session.state.get(uid, {}).pop(topic, None):
|
|
68
|
+
# self.remove_handler(handler)
|
|
69
|
+
self.dispatcher.groups[g].remove(handler)
|
|
70
|
+
#
|
|
71
|
+
return txt
|
|
72
|
+
await asyncio.sleep(1)
|
|
73
|
+
past += 1
|
|
74
|
+
return self.remove_handler(handler, g)
|
|
75
|
+
|
|
76
|
+
async def got_msg(self, _, msg: Message):
|
|
77
|
+
if tpc := self.storage.session.state.get(msg.from_user.id, {}).pop("waiting_for", None):
|
|
78
|
+
self.storage.session.state[msg.from_user.id][tpc] = msg.contact.phone_number if tpc == "phone" else msg.text
|
|
79
|
+
|
|
80
|
+
def rm_handler(self, uid: int):
|
|
81
|
+
for gi, grp in self.dispatcher.groups.items():
|
|
82
|
+
[self.remove_handler(h, gi) for h in grp if isinstance(h.filters, AndFilter) and uid in h.filters.base]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from x_auth.models import Session
|
|
2
|
+
|
|
3
|
+
from pyro_client.client.base import BaseClient, AuthTopic
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BotClient(BaseClient):
|
|
7
|
+
def __init__(self, sess: Session, _=None):
|
|
8
|
+
super().__init__(sess.id, sess.api.id, sess.api.hsh, bot_token=f"{sess.id}:{sess.is_bot}")
|
|
9
|
+
|
|
10
|
+
async def wait_auth_from(self, uid: int, topic: AuthTopic, past: int = 0, timeout: int = 60) -> str:
|
|
11
|
+
return await super().wait_from(uid, topic, past, timeout)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"7": 2,
|
|
3
|
+
"1": 1,
|
|
4
|
+
"20": 4,
|
|
5
|
+
"211": 2,
|
|
6
|
+
"212": 4,
|
|
7
|
+
"213": 4,
|
|
8
|
+
"216": 4,
|
|
9
|
+
"218": 4,
|
|
10
|
+
"220": 4,
|
|
11
|
+
"221": 4,
|
|
12
|
+
"222": 4,
|
|
13
|
+
"223": 4,
|
|
14
|
+
"224": 4,
|
|
15
|
+
"225": 4,
|
|
16
|
+
"226": 4,
|
|
17
|
+
"227": 4,
|
|
18
|
+
"228": 4,
|
|
19
|
+
"229": 4,
|
|
20
|
+
"230": 4,
|
|
21
|
+
"231": 4,
|
|
22
|
+
"232": 4,
|
|
23
|
+
"233": 4,
|
|
24
|
+
"234": 4,
|
|
25
|
+
"235": 4,
|
|
26
|
+
"236": 4,
|
|
27
|
+
"237": 4,
|
|
28
|
+
"238": 4,
|
|
29
|
+
"239": 4,
|
|
30
|
+
"240": 4,
|
|
31
|
+
"241": 4,
|
|
32
|
+
"242": 4,
|
|
33
|
+
"243": 4,
|
|
34
|
+
"244": 4,
|
|
35
|
+
"245": 4,
|
|
36
|
+
"246": 2,
|
|
37
|
+
"247": 4,
|
|
38
|
+
"248": 4,
|
|
39
|
+
"249": 4,
|
|
40
|
+
"250": 4,
|
|
41
|
+
"251": 4,
|
|
42
|
+
"252": 4,
|
|
43
|
+
"253": 4,
|
|
44
|
+
"254": 4,
|
|
45
|
+
"255": 4,
|
|
46
|
+
"256": 4,
|
|
47
|
+
"257": 4,
|
|
48
|
+
"258": 4,
|
|
49
|
+
"260": 4,
|
|
50
|
+
"261": 4,
|
|
51
|
+
"262": 4,
|
|
52
|
+
"263": 4,
|
|
53
|
+
"264": 4,
|
|
54
|
+
"265": 4,
|
|
55
|
+
"266": 4,
|
|
56
|
+
"267": 4,
|
|
57
|
+
"268": 4,
|
|
58
|
+
"269": 4,
|
|
59
|
+
"27": 4,
|
|
60
|
+
"290": 4,
|
|
61
|
+
"291": 4,
|
|
62
|
+
"297": 1,
|
|
63
|
+
"298": 4,
|
|
64
|
+
"299": 1,
|
|
65
|
+
"30": 4,
|
|
66
|
+
"31": 4,
|
|
67
|
+
"32": 4,
|
|
68
|
+
"33": 4,
|
|
69
|
+
"34": 4,
|
|
70
|
+
"36": 4,
|
|
71
|
+
"373": 2,
|
|
72
|
+
"374": 2,
|
|
73
|
+
"375": 2,
|
|
74
|
+
"380": 2,
|
|
75
|
+
"39": 4,
|
|
76
|
+
"40": 4,
|
|
77
|
+
"41": 4,
|
|
78
|
+
"43": 4,
|
|
79
|
+
"44": 4,
|
|
80
|
+
"45": 4,
|
|
81
|
+
"46": 4,
|
|
82
|
+
"47": 4,
|
|
83
|
+
"48": 4,
|
|
84
|
+
"49": 2,
|
|
85
|
+
"500": 1,
|
|
86
|
+
"501": 1,
|
|
87
|
+
"502": 1,
|
|
88
|
+
"503": 1,
|
|
89
|
+
"504": 1,
|
|
90
|
+
"505": 1,
|
|
91
|
+
"506": 1,
|
|
92
|
+
"507": 1,
|
|
93
|
+
"508": 1,
|
|
94
|
+
"509": 1,
|
|
95
|
+
"51": 1,
|
|
96
|
+
"52": 1,
|
|
97
|
+
"53": 1,
|
|
98
|
+
"54": 1,
|
|
99
|
+
"55": 1,
|
|
100
|
+
"56": 1,
|
|
101
|
+
"57": 1,
|
|
102
|
+
"58": 1,
|
|
103
|
+
"590": 1,
|
|
104
|
+
"591": 1,
|
|
105
|
+
"592": 1,
|
|
106
|
+
"593": 1,
|
|
107
|
+
"594": 1,
|
|
108
|
+
"595": 1,
|
|
109
|
+
"596": 1,
|
|
110
|
+
"597": 1,
|
|
111
|
+
"598": 1,
|
|
112
|
+
"599": 2,
|
|
113
|
+
"60": 5,
|
|
114
|
+
"61": 5,
|
|
115
|
+
"62": 5,
|
|
116
|
+
"63": 5,
|
|
117
|
+
"64": 5,
|
|
118
|
+
"65": 5,
|
|
119
|
+
"66": 5,
|
|
120
|
+
"672": 1,
|
|
121
|
+
"673": 5,
|
|
122
|
+
"674": 1,
|
|
123
|
+
"675": 5,
|
|
124
|
+
"676": 5,
|
|
125
|
+
"677": 5,
|
|
126
|
+
"678": 1,
|
|
127
|
+
"679": 1,
|
|
128
|
+
"680": 1,
|
|
129
|
+
"681": 1,
|
|
130
|
+
"682": 1,
|
|
131
|
+
"683": 1,
|
|
132
|
+
"684": 1,
|
|
133
|
+
"685": 1,
|
|
134
|
+
"686": 1,
|
|
135
|
+
"687": 1,
|
|
136
|
+
"688": 1,
|
|
137
|
+
"689": 1,
|
|
138
|
+
"690": 1,
|
|
139
|
+
"691": 1,
|
|
140
|
+
"692": 1,
|
|
141
|
+
"81": 5,
|
|
142
|
+
"82": 5,
|
|
143
|
+
"84": 5,
|
|
144
|
+
"86": 5,
|
|
145
|
+
"852": 5,
|
|
146
|
+
"853": 5,
|
|
147
|
+
"855": 5,
|
|
148
|
+
"856": 5,
|
|
149
|
+
"880": 5,
|
|
150
|
+
"886": 5,
|
|
151
|
+
"90": 4,
|
|
152
|
+
"91": 4,
|
|
153
|
+
"92": 4,
|
|
154
|
+
"93": 5,
|
|
155
|
+
"94": 5,
|
|
156
|
+
"95": 5,
|
|
157
|
+
"960": 5,
|
|
158
|
+
"961": 4,
|
|
159
|
+
"962": 4,
|
|
160
|
+
"963": 4,
|
|
161
|
+
"964": 2,
|
|
162
|
+
"965": 4,
|
|
163
|
+
"966": 4,
|
|
164
|
+
"967": 4,
|
|
165
|
+
"968": 4,
|
|
166
|
+
"970": 2,
|
|
167
|
+
"971": 4,
|
|
168
|
+
"972": 4,
|
|
169
|
+
"973": 4,
|
|
170
|
+
"974": 4,
|
|
171
|
+
"975": 5,
|
|
172
|
+
"976": 5,
|
|
173
|
+
"977": 5,
|
|
174
|
+
"992": 2,
|
|
175
|
+
"993": 2,
|
|
176
|
+
"995": 2,
|
|
177
|
+
"994": 2,
|
|
178
|
+
"996": 2,
|
|
179
|
+
"998": 2
|
|
180
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
3
|
+
from pyrogram.raw.functions.messages import UploadMedia
|
|
4
|
+
from pyrogram.raw.functions.upload import GetFile
|
|
5
|
+
from pyrogram.raw.types import (
|
|
6
|
+
MessageMediaDocument,
|
|
7
|
+
InputMediaUploadedDocument,
|
|
8
|
+
InputPeerSelf,
|
|
9
|
+
MessageMediaPhoto,
|
|
10
|
+
InputMediaUploadedPhoto,
|
|
11
|
+
InputDocumentFileLocation,
|
|
12
|
+
InputPhotoFileLocation,
|
|
13
|
+
)
|
|
14
|
+
from pyrogram.raw.types.upload import File
|
|
15
|
+
from pyrogram.types import Message
|
|
16
|
+
|
|
17
|
+
from pyro_client.client.bot import BotClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FileClient(BotClient):
|
|
21
|
+
@staticmethod
|
|
22
|
+
def ref_enc(ph_id: int, access_hash: int, ref: bytes) -> bytes:
|
|
23
|
+
return ph_id.to_bytes(8, "big") + access_hash.to_bytes(8, "big", signed=True) + ref
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def ref_dec(full_ref: bytes) -> tuple[int, int, bytes]:
|
|
27
|
+
pid, ah = int.from_bytes(full_ref[:8], "big"), int.from_bytes(full_ref[8:16], "big", signed=True)
|
|
28
|
+
return pid, ah, full_ref[16:]
|
|
29
|
+
|
|
30
|
+
async def save_doc(self, byts: bytes, ctype: str) -> tuple[MessageMediaDocument, bytes]:
|
|
31
|
+
in_file = await self.save_file(BytesIO(byts))
|
|
32
|
+
imud = InputMediaUploadedDocument(file=in_file, mime_type=ctype, attributes=[])
|
|
33
|
+
upf: MessageMediaDocument = await self.invoke(UploadMedia(peer=InputPeerSelf(), media=imud))
|
|
34
|
+
return upf, (
|
|
35
|
+
upf.document.id.to_bytes(8, "big")
|
|
36
|
+
+ upf.document.access_hash.to_bytes(8, "big", signed=True)
|
|
37
|
+
+ upf.document.file_reference
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
async def save_photo(self, file: bytes) -> tuple[MessageMediaPhoto, bytes]:
|
|
41
|
+
in_file = await self.save_file(BytesIO(file))
|
|
42
|
+
upm = UploadMedia(peer=InputPeerSelf(), media=InputMediaUploadedPhoto(file=in_file))
|
|
43
|
+
upp: MessageMediaPhoto = await self.invoke(upm)
|
|
44
|
+
return upp, self.ref_enc(upp.photo.id, upp.photo.access_hash, upp.photo.file_reference)
|
|
45
|
+
|
|
46
|
+
async def get_doc(self, fid: bytes) -> File:
|
|
47
|
+
pid, ah, ref = self.ref_dec(fid)
|
|
48
|
+
loc = InputDocumentFileLocation(id=pid, access_hash=ah, file_reference=ref, thumb_size="x")
|
|
49
|
+
return await self.invoke(GetFile(location=loc, offset=0, limit=512 * 1024))
|
|
50
|
+
|
|
51
|
+
async def get_photo(self, fid: bytes, st: str) -> File:
|
|
52
|
+
pid, ah, ref = self.ref_dec(fid)
|
|
53
|
+
loc = InputPhotoFileLocation(id=pid, access_hash=ah, file_reference=ref, thumb_size=st)
|
|
54
|
+
return await self.invoke(GetFile(location=loc, offset=0, limit=512 * 1024))
|
|
55
|
+
|
|
56
|
+
async def bot_got_msg(self, _, msg: Message):
|
|
57
|
+
if state := self.storage.session.state.pop("bot", None):
|
|
58
|
+
self.storage.session.state[state] = msg.text
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from tortoise.functions import Count
|
|
2
|
+
from x_auth.models import Proxy, Session, Username, App
|
|
3
|
+
|
|
4
|
+
from pyro_client.loader import WSToken
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SingleMeta(type):
|
|
8
|
+
_instances = {}
|
|
9
|
+
|
|
10
|
+
async def __call__(cls, uid: int | str, bot=None):
|
|
11
|
+
if cls not in cls._instances:
|
|
12
|
+
bt = None
|
|
13
|
+
if isinstance(uid, str):
|
|
14
|
+
if len(ub := uid.split(":")) == 2:
|
|
15
|
+
uid, bt = ub
|
|
16
|
+
if uid.isnumeric():
|
|
17
|
+
uid = int(uid)
|
|
18
|
+
sess = await cls._sess(uid, bt, None if bot else ...)
|
|
19
|
+
cls._instances[cls] = super().__call__(sess, bot)
|
|
20
|
+
return cls._instances[cls]
|
|
21
|
+
|
|
22
|
+
async def _sess(self, uid: int | str, bt: str = None, px: Proxy | None = ..., dc: int = 2) -> Session:
|
|
23
|
+
username, _ = await Username.get_or_create(**{"id" if isinstance(uid, int) else "username": uid})
|
|
24
|
+
if not (
|
|
25
|
+
session := await Session.get_or_none(user=username, api__dc=dc) or await Session.get_or_none(id=username.id)
|
|
26
|
+
):
|
|
27
|
+
if px is Ellipsis:
|
|
28
|
+
await Proxy.load_list(WSToken)
|
|
29
|
+
# await Proxy.get_replaced(WSToken)
|
|
30
|
+
px = await Proxy.annotate(sc=Count("sessions")).filter(valid=True).order_by("sc", "-updated_at").first()
|
|
31
|
+
if username.phone:
|
|
32
|
+
# noinspection PyUnresolvedReferences
|
|
33
|
+
dc = dc or self.get_dc()
|
|
34
|
+
session = await Session.create(
|
|
35
|
+
id=username.id, api=await App[20373304], user=username, dc_id=dc, proxy=px, is_bot=bt
|
|
36
|
+
)
|
|
37
|
+
await session.fetch_related("proxy", "api")
|
|
38
|
+
return session
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from json import load
|
|
3
|
+
from os.path import dirname
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from pyrogram import enums
|
|
7
|
+
from pyrogram.enums import ClientPlatform
|
|
8
|
+
from pyrogram.errors import BadRequest, SessionPasswordNeeded, AuthKeyUnregistered, Unauthorized
|
|
9
|
+
from pyrogram.session import Auth, Session as PyroSession
|
|
10
|
+
from pyrogram.types import Message, User, SentCode, InlineKeyboardButton, KeyboardButton
|
|
11
|
+
from x_auth.models import Session, Username, Proxy
|
|
12
|
+
|
|
13
|
+
from pyro_client.client.base import BaseClient, AuthTopic
|
|
14
|
+
from pyro_client.client.bot import BotClient
|
|
15
|
+
from pyro_client.loader import WSToken
|
|
16
|
+
|
|
17
|
+
vers: dict[ClientPlatform, str] = {
|
|
18
|
+
ClientPlatform.IOS: "18.5",
|
|
19
|
+
ClientPlatform.ANDROID: "16",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Prx(BaseModel):
|
|
24
|
+
scheme: str = "socks5"
|
|
25
|
+
hostname: str = Field(validation_alias="host")
|
|
26
|
+
port: int
|
|
27
|
+
username: str
|
|
28
|
+
password: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class UserClient(BaseClient):
|
|
32
|
+
bot: BotClient
|
|
33
|
+
|
|
34
|
+
def __init__(self, sess: Session, bot):
|
|
35
|
+
self.bot = bot
|
|
36
|
+
super().__init__(
|
|
37
|
+
sess.id,
|
|
38
|
+
sess.api.id,
|
|
39
|
+
sess.api.hsh,
|
|
40
|
+
device_model="iPhone 16e",
|
|
41
|
+
app_version=sess.api.ver,
|
|
42
|
+
system_version=vers.get(sess.api.platform),
|
|
43
|
+
client_platform=sess.api.platform,
|
|
44
|
+
proxy=sess.proxy and sess.proxy.dict(),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
async def start(self, use_qr: bool = False, except_ids: list[int] = None):
|
|
48
|
+
if not self.bot.is_connected:
|
|
49
|
+
await self.bot.start()
|
|
50
|
+
# dcs = await self.bot.invoke(GetConfig())
|
|
51
|
+
try:
|
|
52
|
+
await super().start(use_qr=use_qr, except_ids=except_ids or [])
|
|
53
|
+
await self.send("im ok")
|
|
54
|
+
except AuthKeyUnregistered as e:
|
|
55
|
+
await self.storage.session.delete()
|
|
56
|
+
raise e
|
|
57
|
+
except (AuthKeyUnregistered, Unauthorized) as e:
|
|
58
|
+
raise e
|
|
59
|
+
|
|
60
|
+
async def ask_for(
|
|
61
|
+
self, topic: AuthTopic, question: str, btns: list[InlineKeyboardButton, KeyboardButton] = None
|
|
62
|
+
) -> str:
|
|
63
|
+
if topic == "phone":
|
|
64
|
+
btns = btns or [] + [KeyboardButton("Phone", True)]
|
|
65
|
+
await self.receive(question, btns)
|
|
66
|
+
uid = int(self.storage.name)
|
|
67
|
+
self.bot.storage.session.state[uid] = {"waiting_for": topic}
|
|
68
|
+
return await self.bot.wait_auth_from(uid, topic)
|
|
69
|
+
|
|
70
|
+
async def receive(
|
|
71
|
+
self,
|
|
72
|
+
txt: str,
|
|
73
|
+
btns: list[InlineKeyboardButton | KeyboardButton] = None,
|
|
74
|
+
photo: bytes = None,
|
|
75
|
+
video: bytes = None,
|
|
76
|
+
) -> Message:
|
|
77
|
+
return await self.bot.send(txt, int(self.storage.name), btns, photo, video)
|
|
78
|
+
|
|
79
|
+
def get_dc(self):
|
|
80
|
+
if not self.phone_number:
|
|
81
|
+
return 2
|
|
82
|
+
with open(f"{dirname(__file__)}/dc.json", "r") as file:
|
|
83
|
+
jsn = load(file)
|
|
84
|
+
for k, v in jsn.items():
|
|
85
|
+
if self.phone_number.startswith(k):
|
|
86
|
+
return v
|
|
87
|
+
return 2
|
|
88
|
+
|
|
89
|
+
async def authorize(self, sent_code: SentCode = None) -> User:
|
|
90
|
+
sent_code_desc = {
|
|
91
|
+
enums.SentCodeType.APP: "Telegram app",
|
|
92
|
+
enums.SentCodeType.SMS: "SMS",
|
|
93
|
+
enums.SentCodeType.CALL: "phone call",
|
|
94
|
+
enums.SentCodeType.FLASH_CALL: "phone flash call",
|
|
95
|
+
enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS",
|
|
96
|
+
enums.SentCodeType.EMAIL_CODE: "email code",
|
|
97
|
+
}
|
|
98
|
+
# Step 1: Phone
|
|
99
|
+
if not self.phone_number:
|
|
100
|
+
user = await Username[self.storage.session.id]
|
|
101
|
+
if not (user.phone and (phone := str(user.phone))):
|
|
102
|
+
phone = await self.ask_for("phone", "Phone plz")
|
|
103
|
+
user.phone = phone
|
|
104
|
+
await user.save()
|
|
105
|
+
self.phone_number = phone
|
|
106
|
+
if (dc := self.get_dc()) != 2:
|
|
107
|
+
await self.session_update(dc)
|
|
108
|
+
if not self.phone_number:
|
|
109
|
+
await self.authorize()
|
|
110
|
+
try:
|
|
111
|
+
# user.phone = int(self.phone_number.strip("+ ").replace(" ", ""))
|
|
112
|
+
sent_code = await self.send_code(self.phone_number, True, False, True, False, True)
|
|
113
|
+
except BadRequest as e:
|
|
114
|
+
await self.send(e.MESSAGE)
|
|
115
|
+
self.phone_number = None
|
|
116
|
+
return await self.authorize(sent_code)
|
|
117
|
+
# Step 2: Code
|
|
118
|
+
if not self.phone_code:
|
|
119
|
+
if code := await self.ask_for("code", f"Code from {sent_code_desc[sent_code.type]}"):
|
|
120
|
+
self.phone_code = code.replace("_", "")
|
|
121
|
+
try:
|
|
122
|
+
signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code)
|
|
123
|
+
except BadRequest as e:
|
|
124
|
+
await self.receive(e.MESSAGE)
|
|
125
|
+
self.phone_code = None
|
|
126
|
+
return await self.authorize(sent_code)
|
|
127
|
+
except SessionPasswordNeeded:
|
|
128
|
+
# Step 2.1?: Cloud password
|
|
129
|
+
while True:
|
|
130
|
+
self.password = await self.ask_for(
|
|
131
|
+
"pass", f"Enter pass: (hint: {await self.get_password_hint()})"
|
|
132
|
+
)
|
|
133
|
+
try:
|
|
134
|
+
signed_in = await self.check_password(self.password)
|
|
135
|
+
break
|
|
136
|
+
except BadRequest as e:
|
|
137
|
+
await self.send(e.MESSAGE)
|
|
138
|
+
self.password = None
|
|
139
|
+
else:
|
|
140
|
+
raise Exception("User does not sent code")
|
|
141
|
+
if isinstance(signed_in, User):
|
|
142
|
+
await self.send("✅", self.bot.me.id)
|
|
143
|
+
await self.storage.save()
|
|
144
|
+
return signed_in
|
|
145
|
+
|
|
146
|
+
if not signed_in:
|
|
147
|
+
await self.receive("No registered such phone number")
|
|
148
|
+
|
|
149
|
+
async def stop(self, block: bool = True):
|
|
150
|
+
await super().stop(block)
|
|
151
|
+
await self.bot.stop(block)
|
|
152
|
+
|
|
153
|
+
async def session_update(self, dc: int):
|
|
154
|
+
await self.session.stop()
|
|
155
|
+
|
|
156
|
+
await self.storage.dc_id(dc)
|
|
157
|
+
await self.storage.auth_key(
|
|
158
|
+
await Auth(self, await self.storage.dc_id(), await self.storage.test_mode()).create()
|
|
159
|
+
)
|
|
160
|
+
self.session = PyroSession(
|
|
161
|
+
self, await self.storage.dc_id(), await self.storage.auth_key(), await self.storage.test_mode()
|
|
162
|
+
)
|
|
163
|
+
if not self.proxy and not self.storage.session.proxy:
|
|
164
|
+
await Proxy.load_list(WSToken)
|
|
165
|
+
prx: Proxy = await Proxy.filter(valid=True).order_by("-updated_at").first()
|
|
166
|
+
self.storage.session.proxy = prx
|
|
167
|
+
await self.storage.session.save()
|
|
168
|
+
self.proxy = prx.dict()
|
|
169
|
+
await self.session.start()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def main():
|
|
173
|
+
from x_auth import models
|
|
174
|
+
from x_model import init_db
|
|
175
|
+
|
|
176
|
+
from pyro_client.loader import WSToken, PG_DSN
|
|
177
|
+
|
|
178
|
+
_ = await init_db(PG_DSN, models, True)
|
|
179
|
+
|
|
180
|
+
logging.basicConfig(level=logging.INFO)
|
|
181
|
+
|
|
182
|
+
await models.Proxy.load_list(WSToken)
|
|
183
|
+
# session = await models.Session.filter(is_bot__isnull=True).order_by("-date").prefetch_related("proxy").first()
|
|
184
|
+
bc: BotClient = await BotClient("xyncnetbot")
|
|
185
|
+
uc: UserClient = await UserClient(5547330178, bc)
|
|
186
|
+
# try:
|
|
187
|
+
await uc.start()
|
|
188
|
+
# except Exception as e:
|
|
189
|
+
# print(e.MESSAGE)
|
|
190
|
+
# await uc.send(e.MESSAGE)
|
|
191
|
+
# await uc.storage.session.delete()
|
|
192
|
+
# finally:
|
|
193
|
+
await uc.stop()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
from asyncio import run
|
|
198
|
+
|
|
199
|
+
run(main())
|
pyro_client/loader.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
from os import getenv as env
|
|
3
|
+
|
|
4
|
+
load_dotenv()
|
|
5
|
+
|
|
6
|
+
API_ID = env("API_ID")
|
|
7
|
+
API_HASH = env("API_HASH")
|
|
8
|
+
PG_DSN = f"postgres://{env('POSTGRES_USER')}:{env('POSTGRES_PASSWORD')}@{env('POSTGRES_HOST', 'xyncdbs')}:" \
|
|
9
|
+
f"{env('POSTGRES_PORT', 5432)}/{env('POSTGRES_DB', env('POSTGRES_USER'))}"
|
|
10
|
+
TOKEN = env("TOKEN")
|
|
11
|
+
WSToken = env("WST")
|
pyro_client/storage.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from pyrogram import raw, utils
|
|
5
|
+
from pyrogram.storage import Storage
|
|
6
|
+
from x_auth.models import Username, Version, Session, Peer, UpdateState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_input_peer(peer_id: int, access_hash: int, peer_type: Literal["user", "bot", "group", "channel", "supergroup"]):
|
|
10
|
+
if peer_type in ["user", "bot"]:
|
|
11
|
+
return raw.types.InputPeerUser(user_id=peer_id, access_hash=access_hash)
|
|
12
|
+
|
|
13
|
+
if peer_type == "group":
|
|
14
|
+
return raw.types.InputPeerChat(chat_id=-peer_id)
|
|
15
|
+
|
|
16
|
+
if peer_type in ["channel", "supergroup"]:
|
|
17
|
+
return raw.types.InputPeerChannel(channel_id=utils.get_channel_id(peer_id), access_hash=access_hash)
|
|
18
|
+
|
|
19
|
+
raise ValueError(f"Invalid peer type: {peer_type}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PgStorage(Storage):
|
|
23
|
+
VERSION = 1
|
|
24
|
+
USERNAME_TTL = 8 * 60 * 60
|
|
25
|
+
session: Session
|
|
26
|
+
# me_id: int
|
|
27
|
+
|
|
28
|
+
async def open(self):
|
|
29
|
+
# self.me_id = int((uid_dc := self.name.split("_")).pop(0))
|
|
30
|
+
self.session = await Session[self.name]
|
|
31
|
+
|
|
32
|
+
async def save(self):
|
|
33
|
+
await self.date(int(time.time()))
|
|
34
|
+
|
|
35
|
+
async def close(self): ...
|
|
36
|
+
|
|
37
|
+
async def delete(self):
|
|
38
|
+
await Session.filter(id=self.name).delete()
|
|
39
|
+
|
|
40
|
+
async def update_peers(self, peers: list[tuple[int, int, str, str]]):
|
|
41
|
+
for peer in peers:
|
|
42
|
+
uid, ac_hsh, typ, phn = peer
|
|
43
|
+
un, _ = await Username.get_or_create(phn and {"phone": phn}, id=uid)
|
|
44
|
+
await Peer.update_or_create(
|
|
45
|
+
{"username": un, "type": typ, "phone_number": phn}, session_id=self.name, id=ac_hsh
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def update_usernames(self, usernames: list[tuple[int, list[str]]]):
|
|
49
|
+
for telegram_id, user_list in usernames:
|
|
50
|
+
for username in user_list:
|
|
51
|
+
await Username.update_or_create({"username": username}, id=telegram_id)
|
|
52
|
+
|
|
53
|
+
async def get_peer_by_id(self, peer_id_or_username: int | str):
|
|
54
|
+
attr = "id" if isinstance(peer_id_or_username, int) else "username"
|
|
55
|
+
if not (peer := await Peer.get_or_none(session_id=self.name, **{"username__" + attr: peer_id_or_username})):
|
|
56
|
+
raise KeyError(f"User not found: {peer_id_or_username}")
|
|
57
|
+
if peer.last_update_on:
|
|
58
|
+
if abs(time.time() - peer.last_update_on.timestamp()) > self.USERNAME_TTL:
|
|
59
|
+
raise KeyError(f"Username expired: {peer_id_or_username}")
|
|
60
|
+
return get_input_peer(peer.username_id, peer.id, peer.type)
|
|
61
|
+
|
|
62
|
+
async def get_peer_by_username(self, username: str):
|
|
63
|
+
return await self.get_peer_by_id(username)
|
|
64
|
+
|
|
65
|
+
async def update_state(self, value: tuple[int, int, int, int, int] = object):
|
|
66
|
+
if value is None:
|
|
67
|
+
return await UpdateState.filter(session_id=self.name)
|
|
68
|
+
elif isinstance(value, int):
|
|
69
|
+
await UpdateState.filter(session_id=self.name, id=value).delete()
|
|
70
|
+
else:
|
|
71
|
+
sid, pts, qts, date, seq = value
|
|
72
|
+
await UpdateState.get_or_create(
|
|
73
|
+
{"pts": pts, "qts": qts, "date": date, "seq": seq}, session_id=self.name, id=sid
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def get_peer_by_phone_number(self, phone_number: str):
|
|
77
|
+
if not (
|
|
78
|
+
peer := await Peer.filter(session_id=self.name, phone_number=phone_number).values_list(
|
|
79
|
+
"id", "access_hash", "type"
|
|
80
|
+
)
|
|
81
|
+
):
|
|
82
|
+
raise KeyError(f"Phone number not found: {phone_number}")
|
|
83
|
+
return get_input_peer(*peer)
|
|
84
|
+
|
|
85
|
+
async def _get(self, attr: str):
|
|
86
|
+
return await Session.get(id=self.name).values_list(attr, flat=True)
|
|
87
|
+
|
|
88
|
+
async def _set(self, attr: str, value):
|
|
89
|
+
# if "__" in attr:
|
|
90
|
+
# table, attr = attr.split("__")
|
|
91
|
+
# rel = await self.session.__getattribute__(table)
|
|
92
|
+
# rel.__setattr__(attr, value)
|
|
93
|
+
# await rel.save()
|
|
94
|
+
# else:
|
|
95
|
+
await Session.update_or_create({attr: value}, id=self.name)
|
|
96
|
+
|
|
97
|
+
async def _accessor(self, attr: str, value: Any = object):
|
|
98
|
+
if value is object:
|
|
99
|
+
return await self._get(attr)
|
|
100
|
+
else:
|
|
101
|
+
await self._set(attr, value)
|
|
102
|
+
|
|
103
|
+
async def dc_id(self, value: int = object):
|
|
104
|
+
return await self._accessor("dc_id", value)
|
|
105
|
+
|
|
106
|
+
async def api_id(self, value: int = object):
|
|
107
|
+
return await self._accessor("api_id", value)
|
|
108
|
+
|
|
109
|
+
async def test_mode(self, value: bool = object):
|
|
110
|
+
return await self._accessor("test_mode", value)
|
|
111
|
+
|
|
112
|
+
async def auth_key(self, value: bytes = object):
|
|
113
|
+
return await self._accessor("auth_key", value)
|
|
114
|
+
|
|
115
|
+
async def date(self, value: int = object):
|
|
116
|
+
return await self._accessor("date", value)
|
|
117
|
+
|
|
118
|
+
async def user_id(self, value: int = object):
|
|
119
|
+
return await self._accessor("user_id", value)
|
|
120
|
+
|
|
121
|
+
async def is_bot(self, value: bool = object):
|
|
122
|
+
return await self._accessor("is_bot", value)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
async def version(value: int = object):
|
|
126
|
+
if value is object:
|
|
127
|
+
ver = await Version.first()
|
|
128
|
+
return ver.number
|
|
129
|
+
else:
|
|
130
|
+
await Version.update_or_create(id=value)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyrogram-client
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Author-email: Mike Artemiev <mixartemev@gmail.com>
|
|
5
|
+
Project-URL: Homepage, https://gitlab.com/XyncNet/pyro-client
|
|
6
|
+
Project-URL: Repository, https://gitlab.com/XyncNet/pyro-client
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: kurigram
|
|
9
|
+
Requires-Dist: tgcrypto
|
|
10
|
+
Requires-Dist: xn-auth
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: build; extra == "dev"
|
|
13
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
14
|
+
Requires-Dist: python-dotenv; extra == "dev"
|
|
15
|
+
Requires-Dist: setuptools-scm; extra == "dev"
|
|
16
|
+
Requires-Dist: twine; extra == "dev"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pyro_client/loader.py,sha256=CsBBsb8PfEtU4z7eDMlkc0V3x4PfwTvc2R1HbdVhAMg,362
|
|
2
|
+
pyro_client/storage.py,sha256=ch6r-YTYau2TgH59i_VWfLiQ1Sznc-pJ2mAmAReUB7A,4965
|
|
3
|
+
pyro_client/client/base.py,sha256=hSlPrVwtmuO6dhobYTyaHsjyRYEJ6BUqbD1jQzrPo-w,3266
|
|
4
|
+
pyro_client/client/bot.py,sha256=89KG-HSMQP1_263Y1jTR-X4Gb3XBEI1DFp1vSmZHmoo,442
|
|
5
|
+
pyro_client/client/dc.json,sha256=uoRznbTmpLLVNdb0CFbNZIbKtOSSUD-TOc-c0kX207U,2091
|
|
6
|
+
pyro_client/client/file.py,sha256=JzP-BqqOdfP8LfVXI-qnO4hIS68mZaMnk8SVqrSngA0,2548
|
|
7
|
+
pyro_client/client/single.py,sha256=r3ZfVf2Udyo3rJdCR1NoDea6jFtS74JYKhdxurzZ3fc,1625
|
|
8
|
+
pyro_client/client/user.py,sha256=IRXxzMbaO8Wcq_dle8gccEktU8Q-25MtQTQ87YA9hXg,7325
|
|
9
|
+
pyrogram_client-0.0.2.dist-info/METADATA,sha256=TeFP14JXOVp7xAf38E2cJdqKYcRyerp-woTXzDe7zh0,558
|
|
10
|
+
pyrogram_client-0.0.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
11
|
+
pyrogram_client-0.0.2.dist-info/top_level.txt,sha256=pR3onX0Aots7ODOSrd8dssJg3J2kz36Crt_48QSwyi0,12
|
|
12
|
+
pyrogram_client-0.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyro_client
|