pyrogram-client 0.0.2.dev1__tar.gz → 0.0.4__tar.gz
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.
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/.pre-commit-config.yaml +1 -1
- {pyrogram_client-0.0.2.dev1/pyrogram_client.egg-info → pyrogram_client-0.0.4}/PKG-INFO +1 -1
- pyrogram_client-0.0.4/pyro_client/client/base.py +82 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyro_client/client/bot.py +4 -2
- pyrogram_client-0.0.4/pyro_client/client/dc.json +180 -0
- pyrogram_client-0.0.4/pyro_client/client/single.py +40 -0
- pyrogram_client-0.0.4/pyro_client/client/user.py +199 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyro_client/storage.py +30 -44
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4/pyrogram_client.egg-info}/PKG-INFO +1 -1
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyrogram_client.egg-info/SOURCES.txt +2 -0
- pyrogram_client-0.0.2.dev1/pyro_client/client/base.py +0 -42
- pyrogram_client-0.0.2.dev1/pyro_client/client/user.py +0 -106
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/.env.sample +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/.gitignore +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/makefile +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyproject.toml +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyro_client/client/file.py +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyro_client/loader.py +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyrogram_client.egg-info/dependency_links.txt +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyrogram_client.egg-info/requires.txt +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyrogram_client.egg-info/top_level.txt +0 -0
- {pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/setup.cfg +0 -0
|
@@ -14,7 +14,7 @@ repos:
|
|
|
14
14
|
- id: build
|
|
15
15
|
name: build
|
|
16
16
|
### build & upload package only for "main" branch push
|
|
17
|
-
entry: bash -c 'echo $PRE_COMMIT_LOCAL_BRANCH | grep /
|
|
17
|
+
entry: bash -c 'echo $PRE_COMMIT_LOCAL_BRANCH | grep /main && make twine || echo 0'
|
|
18
18
|
language: system
|
|
19
19
|
pass_filenames: false
|
|
20
20
|
verbose: true
|
|
@@ -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]
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from x_auth.models import Session
|
|
2
|
+
|
|
1
3
|
from pyro_client.client.base import BaseClient, AuthTopic
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class BotClient(BaseClient):
|
|
5
|
-
def __init__(self,
|
|
6
|
-
super().__init__(
|
|
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}")
|
|
7
9
|
|
|
8
10
|
async def wait_auth_from(self, uid: int, topic: AuthTopic, past: int = 0, timeout: int = 60) -> str:
|
|
9
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,40 @@
|
|
|
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
|
+
prx = ...
|
|
13
|
+
bt = None
|
|
14
|
+
if isinstance(uid, str):
|
|
15
|
+
if len(ub := uid.split(":")) == 2:
|
|
16
|
+
uid, bt = ub
|
|
17
|
+
prx = None
|
|
18
|
+
if uid.isnumeric():
|
|
19
|
+
uid = int(uid)
|
|
20
|
+
sess = await cls._sess(uid, bt, prx)
|
|
21
|
+
cls._instances[cls] = super().__call__(sess, bot)
|
|
22
|
+
return cls._instances[cls]
|
|
23
|
+
|
|
24
|
+
async def _sess(self, uid: int | str, bt: str = None, px: Proxy | None = ..., dc: int = 2) -> Session:
|
|
25
|
+
username, _ = await Username.get_or_create(**{"id" if isinstance(uid, int) else "username": uid})
|
|
26
|
+
if not (
|
|
27
|
+
session := await Session.get_or_none(user=username, api__dc=dc) or await Session.get_or_none(id=username.id)
|
|
28
|
+
):
|
|
29
|
+
if px is Ellipsis:
|
|
30
|
+
await Proxy.load_list(WSToken)
|
|
31
|
+
# await Proxy.get_replaced(WSToken)
|
|
32
|
+
px = await Proxy.annotate(sc=Count("sessions")).filter(valid=True).order_by("sc", "-updated_at").first()
|
|
33
|
+
if username.phone:
|
|
34
|
+
# noinspection PyUnresolvedReferences
|
|
35
|
+
dc = dc or self.get_dc()
|
|
36
|
+
session = await Session.create(
|
|
37
|
+
id=username.id, api=await App[20373304], user=username, dc_id=dc, proxy=px, is_bot=bt
|
|
38
|
+
)
|
|
39
|
+
await session.fetch_related("proxy", "api")
|
|
40
|
+
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, TOKEN
|
|
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(TOKEN)
|
|
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())
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import time
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any, Literal
|
|
3
3
|
|
|
4
4
|
from pyrogram import raw, utils
|
|
5
5
|
from pyrogram.storage import Storage
|
|
6
|
-
from
|
|
7
|
-
from x_auth.models import Proxy, Username, Version, Session, Peer, UpdateState
|
|
6
|
+
from x_auth.models import Username, Version, Session, Peer, UpdateState
|
|
8
7
|
|
|
9
|
-
from pyro_client.loader import WSToken
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
def get_input_peer(peer_id: int, access_hash: int, peer_type: str):
|
|
9
|
+
def get_input_peer(peer_id: int, access_hash: int, peer_type: Literal["user", "bot", "group", "channel", "supergroup"]):
|
|
13
10
|
if peer_type in ["user", "bot"]:
|
|
14
11
|
return raw.types.InputPeerUser(user_id=peer_id, access_hash=access_hash)
|
|
15
12
|
|
|
@@ -26,28 +23,12 @@ class PgStorage(Storage):
|
|
|
26
23
|
VERSION = 1
|
|
27
24
|
USERNAME_TTL = 8 * 60 * 60
|
|
28
25
|
session: Session
|
|
29
|
-
|
|
26
|
+
|
|
27
|
+
# me_id: int
|
|
30
28
|
|
|
31
29
|
async def open(self):
|
|
32
|
-
self.me_id = int((uid_dc := self.name.split("_")).pop(0))
|
|
33
|
-
|
|
34
|
-
username, _ = await Username.get_or_create(id=self.me_id)
|
|
35
|
-
await Proxy.load_list(WSToken)
|
|
36
|
-
# await Proxy.get_replaced(WSToken)
|
|
37
|
-
proxy = (
|
|
38
|
-
await Proxy.annotate(sessions_count=Count("sessions"))
|
|
39
|
-
.filter(valid=True)
|
|
40
|
-
.order_by("sessions_count", "-updated_at")
|
|
41
|
-
.first()
|
|
42
|
-
)
|
|
43
|
-
session = await Session.create(
|
|
44
|
-
id=self.name,
|
|
45
|
-
user=username,
|
|
46
|
-
dc_id=int(uid_dc[0]) if uid_dc else None,
|
|
47
|
-
date=int(time.time()),
|
|
48
|
-
proxy=proxy,
|
|
49
|
-
)
|
|
50
|
-
self.session = session
|
|
30
|
+
# self.me_id = int((uid_dc := self.name.split("_")).pop(0))
|
|
31
|
+
self.session = await Session[self.name]
|
|
51
32
|
|
|
52
33
|
async def save(self):
|
|
53
34
|
await self.date(int(time.time()))
|
|
@@ -59,10 +40,10 @@ class PgStorage(Storage):
|
|
|
59
40
|
|
|
60
41
|
async def update_peers(self, peers: list[tuple[int, int, str, str]]):
|
|
61
42
|
for peer in peers:
|
|
62
|
-
uid, ac_hsh, typ,
|
|
63
|
-
un, _ = await Username.
|
|
43
|
+
uid, ac_hsh, typ, phone = peer
|
|
44
|
+
un, _ = await Username.update_or_create(phone and {"phone": phone}, id=uid)
|
|
64
45
|
await Peer.update_or_create(
|
|
65
|
-
{"username": un, "type": typ, "phone_number":
|
|
46
|
+
{"username": un, "type": typ, "phone_number": phone}, session_id=self.name, id=ac_hsh
|
|
66
47
|
)
|
|
67
48
|
|
|
68
49
|
async def update_usernames(self, usernames: list[tuple[int, list[str]]]):
|
|
@@ -70,17 +51,17 @@ class PgStorage(Storage):
|
|
|
70
51
|
for username in user_list:
|
|
71
52
|
await Username.update_or_create({"username": username}, id=telegram_id)
|
|
72
53
|
|
|
73
|
-
async def get_peer_by_id(self,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
async def get_peer_by_id(self, uin: int | str):
|
|
55
|
+
peer = await (
|
|
56
|
+
Peer.get(session_id=self.name, username_id=uin) if isinstance(uin, int) else self.get_peer_by_username(uin)
|
|
57
|
+
)
|
|
77
58
|
if peer.last_update_on:
|
|
78
59
|
if abs(time.time() - peer.last_update_on.timestamp()) > self.USERNAME_TTL:
|
|
79
|
-
raise KeyError(f"Username expired: {
|
|
60
|
+
raise KeyError(f"Username expired: {uin}")
|
|
80
61
|
return get_input_peer(peer.username_id, peer.id, peer.type)
|
|
81
62
|
|
|
82
63
|
async def get_peer_by_username(self, username: str):
|
|
83
|
-
return await self.
|
|
64
|
+
return await Peer.get(session_id=self.name, username__username=username)
|
|
84
65
|
|
|
85
66
|
async def update_state(self, value: tuple[int, int, int, int, int] = object):
|
|
86
67
|
if value is None:
|
|
@@ -94,22 +75,25 @@ class PgStorage(Storage):
|
|
|
94
75
|
)
|
|
95
76
|
|
|
96
77
|
async def get_peer_by_phone_number(self, phone_number: str):
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
):
|
|
102
|
-
raise KeyError(f"Phone number not found: {phone_number}")
|
|
78
|
+
attrs = "id", "access_hash", "type"
|
|
79
|
+
if not (peer := await Peer.get_or_none(session_id=self.name, phone_number=phone_number).values_list(*attrs)):
|
|
80
|
+
peer = await Peer.get(session_id=self.name, username__phone=phone_number).values_list(*attrs)
|
|
103
81
|
return get_input_peer(*peer)
|
|
104
82
|
|
|
105
83
|
async def _get(self, attr: str):
|
|
106
84
|
return await Session.get(id=self.name).values_list(attr, flat=True)
|
|
107
85
|
|
|
108
86
|
async def _set(self, attr: str, value):
|
|
87
|
+
# if "__" in attr:
|
|
88
|
+
# table, attr = attr.split("__")
|
|
89
|
+
# rel = await self.session.__getattribute__(table)
|
|
90
|
+
# rel.__setattr__(attr, value)
|
|
91
|
+
# await rel.save()
|
|
92
|
+
# else:
|
|
109
93
|
await Session.update_or_create({attr: value}, id=self.name)
|
|
110
94
|
|
|
111
95
|
async def _accessor(self, attr: str, value: Any = object):
|
|
112
|
-
if value
|
|
96
|
+
if value is object:
|
|
113
97
|
return await self._get(attr)
|
|
114
98
|
else:
|
|
115
99
|
await self._set(attr, value)
|
|
@@ -133,11 +117,13 @@ class PgStorage(Storage):
|
|
|
133
117
|
return await self._accessor("user_id", value)
|
|
134
118
|
|
|
135
119
|
async def is_bot(self, value: bool = object):
|
|
136
|
-
|
|
120
|
+
if value is not object:
|
|
121
|
+
value = self.session.is_bot if value else None # dirty
|
|
122
|
+
return bool(await self._accessor("is_bot", value))
|
|
137
123
|
|
|
138
124
|
@staticmethod
|
|
139
125
|
async def version(value: int = object):
|
|
140
|
-
if value
|
|
126
|
+
if value is object:
|
|
141
127
|
ver = await Version.first()
|
|
142
128
|
return ver.number
|
|
143
129
|
else:
|
|
@@ -7,7 +7,9 @@ pyro_client/loader.py
|
|
|
7
7
|
pyro_client/storage.py
|
|
8
8
|
pyro_client/client/base.py
|
|
9
9
|
pyro_client/client/bot.py
|
|
10
|
+
pyro_client/client/dc.json
|
|
10
11
|
pyro_client/client/file.py
|
|
12
|
+
pyro_client/client/single.py
|
|
11
13
|
pyro_client/client/user.py
|
|
12
14
|
pyrogram_client.egg-info/PKG-INFO
|
|
13
15
|
pyrogram_client.egg-info/SOURCES.txt
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from asyncio import sleep
|
|
2
|
-
from io import BytesIO
|
|
3
|
-
from typing import Literal
|
|
4
|
-
|
|
5
|
-
from pyrogram import Client
|
|
6
|
-
from pyrogram.filters import chat
|
|
7
|
-
from pyrogram.handlers import MessageHandler
|
|
8
|
-
from pyrogram.types import Message
|
|
9
|
-
|
|
10
|
-
from pyro_client.storage import PgStorage
|
|
11
|
-
|
|
12
|
-
AuthTopic = Literal["phone", "code", "pass"]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class BaseClient(Client):
|
|
16
|
-
storage: PgStorage
|
|
17
|
-
|
|
18
|
-
def __init__(self, name: str, *args, **kwargs):
|
|
19
|
-
super().__init__(name, *args, storage_engine=PgStorage(name), **kwargs)
|
|
20
|
-
|
|
21
|
-
async def send(self, txt: str, uid: int | str = "me", photo: bytes = None, video: bytes = None) -> Message:
|
|
22
|
-
if photo:
|
|
23
|
-
return await self.send_photo(uid, BytesIO(photo), txt)
|
|
24
|
-
elif video:
|
|
25
|
-
return await self.send_video(uid, BytesIO(video), txt)
|
|
26
|
-
else:
|
|
27
|
-
return await self.send_message(uid, txt)
|
|
28
|
-
|
|
29
|
-
async def wait_from(self, uid: int, topic: str, past: int = 0, timeout: int = 10) -> str:
|
|
30
|
-
handler = MessageHandler(self.got_msg, chat(uid))
|
|
31
|
-
self.add_handler(handler)
|
|
32
|
-
while past < timeout:
|
|
33
|
-
if txt := self.storage.session.state.get(uid, {}).pop(topic, None):
|
|
34
|
-
self.remove_handler(handler)
|
|
35
|
-
return txt
|
|
36
|
-
await sleep(1)
|
|
37
|
-
past += 1
|
|
38
|
-
return await self.wait_from(uid, topic, past, timeout)
|
|
39
|
-
|
|
40
|
-
async def got_msg(self, _, msg: Message):
|
|
41
|
-
if topic := self.storage.session.state.get(msg.from_user.id, {}).pop("waiting_for", None):
|
|
42
|
-
self.storage.session.state[msg.from_user.id][topic] = msg.text
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
from pyrogram import enums
|
|
2
|
-
from pyrogram.errors import BadRequest, SessionPasswordNeeded, AuthKeyUnregistered
|
|
3
|
-
from pyrogram.types import Message, User, SentCode
|
|
4
|
-
|
|
5
|
-
from pyro_client.client.base import BaseClient, AuthTopic
|
|
6
|
-
from pyro_client.client.bot import BotClient
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class UserClient(BaseClient):
|
|
10
|
-
bot: BotClient
|
|
11
|
-
|
|
12
|
-
def __init__(self, name: str, api_id: str, api_hash: str, bot_token: str = None, proxy: dict = None,
|
|
13
|
-
device: str = "iPhone 17 Air", app: str = "XyncNet 1.0", ver: str = "iOS 19.0.1"):
|
|
14
|
-
super().__init__(name, api_id, api_hash, device_model=device, app_version=app, system_version=ver)
|
|
15
|
-
self.bot = bot_token and BotClient(api_id, api_hash, bot_token)
|
|
16
|
-
|
|
17
|
-
async def start(self, use_qr: bool = False, except_ids: list[int] = None):
|
|
18
|
-
await self.bot.start()
|
|
19
|
-
await super().start(use_qr=use_qr, except_ids=except_ids or [])
|
|
20
|
-
|
|
21
|
-
async def ask_for(self, topic: AuthTopic, question: str) -> str:
|
|
22
|
-
await self.bot.send_message(self.storage.me_id, question)
|
|
23
|
-
self.bot.storage.session.state[self.storage.me_id] = {"waiting_for": topic}
|
|
24
|
-
return await self.bot.wait_auth_from(self.storage.me_id, topic)
|
|
25
|
-
|
|
26
|
-
async def receive(self, txt: str, photo: bytes = None, video: bytes = None) -> Message:
|
|
27
|
-
return await self.bot.send(txt, self.me.id, photo, video)
|
|
28
|
-
|
|
29
|
-
async def authorize(self, sent_code: SentCode = None) -> User:
|
|
30
|
-
sent_code_desc = {
|
|
31
|
-
enums.SentCodeType.APP: "Telegram app",
|
|
32
|
-
enums.SentCodeType.SMS: "SMS",
|
|
33
|
-
enums.SentCodeType.CALL: "phone call",
|
|
34
|
-
enums.SentCodeType.FLASH_CALL: "phone flash call",
|
|
35
|
-
enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS",
|
|
36
|
-
enums.SentCodeType.EMAIL_CODE: "email code",
|
|
37
|
-
}
|
|
38
|
-
# Step 1: Phone
|
|
39
|
-
if not self.phone_number:
|
|
40
|
-
try:
|
|
41
|
-
self.phone_number = await self.ask_for("phone", "Your phone:")
|
|
42
|
-
if not self.phone_number:
|
|
43
|
-
await self.authorize()
|
|
44
|
-
sent_code = await self.send_code(self.phone_number)
|
|
45
|
-
except BadRequest as e:
|
|
46
|
-
await self.send(e.MESSAGE)
|
|
47
|
-
self.phone_number = None
|
|
48
|
-
return await self.authorize(sent_code)
|
|
49
|
-
# Step 2: Code
|
|
50
|
-
if not self.phone_code:
|
|
51
|
-
_ = await self.ask_for("code", f"The confirm code sent via {sent_code_desc[sent_code.type]}")
|
|
52
|
-
self.phone_code = _.replace("_", "")
|
|
53
|
-
try:
|
|
54
|
-
signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code)
|
|
55
|
-
except BadRequest as e:
|
|
56
|
-
await self.send(e.MESSAGE)
|
|
57
|
-
self.phone_code = None
|
|
58
|
-
return await self.authorize(sent_code)
|
|
59
|
-
except SessionPasswordNeeded as e:
|
|
60
|
-
# Step 2.1?: Cloud password
|
|
61
|
-
await self.send(e.MESSAGE)
|
|
62
|
-
while True:
|
|
63
|
-
self.password = await self.ask_for("pass", f"Enter pass: (hint: {await self.get_password_hint()})")
|
|
64
|
-
try:
|
|
65
|
-
return await self.check_password(self.password)
|
|
66
|
-
except BadRequest as e:
|
|
67
|
-
await self.send(e.MESSAGE)
|
|
68
|
-
self.password = None
|
|
69
|
-
|
|
70
|
-
if isinstance(signed_in, User):
|
|
71
|
-
await self.send("✅")
|
|
72
|
-
return signed_in
|
|
73
|
-
|
|
74
|
-
if not signed_in:
|
|
75
|
-
await self.send("No registered such phone number")
|
|
76
|
-
|
|
77
|
-
async def stop(self, block: bool = True):
|
|
78
|
-
await super().stop(block)
|
|
79
|
-
await self.bot.stop(block)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
async def main():
|
|
83
|
-
from x_auth import models
|
|
84
|
-
from x_model import init_db
|
|
85
|
-
|
|
86
|
-
from pyro_client.loader import WSToken, PG_DSN, TOKEN, API_ID, API_HASH
|
|
87
|
-
|
|
88
|
-
_ = await init_db(PG_DSN, models, True)
|
|
89
|
-
await models.Proxy.load_list(WSToken)
|
|
90
|
-
session = await models.Session.filter(is_bot=False).order_by("-date").first()
|
|
91
|
-
uc = UserClient("", API_ID, API_HASH, TOKEN)
|
|
92
|
-
# try:
|
|
93
|
-
await uc.start()
|
|
94
|
-
await uc.receive("hi")
|
|
95
|
-
# except Exception as e:
|
|
96
|
-
# print(e.MESSAGE)
|
|
97
|
-
# await uc.send(e.MESSAGE)
|
|
98
|
-
# await uc.storage.session.delete()
|
|
99
|
-
# finally:
|
|
100
|
-
await uc.stop()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if __name__ == "__main__":
|
|
104
|
-
from asyncio import run
|
|
105
|
-
|
|
106
|
-
run(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyrogram_client-0.0.2.dev1 → pyrogram_client-0.0.4}/pyrogram_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|