CliRemote 1.4.4__py3-none-any.whl → 1.4.5__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.
- {cliremote-1.4.4.dist-info → cliremote-1.4.5.dist-info}/METADATA +1 -1
- {cliremote-1.4.4.dist-info → cliremote-1.4.5.dist-info}/RECORD +6 -6
- remote/account_manager.py +219 -133
- {cliremote-1.4.4.dist-info → cliremote-1.4.5.dist-info}/WHEEL +0 -0
- {cliremote-1.4.4.dist-info → cliremote-1.4.5.dist-info}/licenses/LICENSE +0 -0
- {cliremote-1.4.4.dist-info → cliremote-1.4.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
cliremote-1.4.
|
1
|
+
cliremote-1.4.5.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
|
2
2
|
remote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
remote/account_manager.py,sha256=
|
3
|
+
remote/account_manager.py,sha256=u8yqcyq7Vl8LZ1rNlrEfwlJEbJiBzoSAlL3z2hhTzyc,9829
|
4
4
|
remote/account_viewer.py,sha256=MQoH5lOz24651EjhlzVwi6O5hVUAT7Q5fFU6ZHjBlsM,4243
|
5
5
|
remote/admin_manager.py,sha256=WiUUVmSs5JTUdXeSry8PkK_3TRemAdSZjm0G1ilAA-A,3532
|
6
6
|
remote/analytics_manager.py,sha256=cQ_uATzOK345U1J88Fki3gOfNedS4Ur5NpLjazsxQkg,3625
|
@@ -31,7 +31,7 @@ remote/speed_manager.py,sha256=fIWSQAP9qW8AHZtMZq0MrC4_nvxcTFU1SBU75kpRzB8,1115
|
|
31
31
|
remote/stop_manager.py,sha256=UXzKJTblEyQqCjp7fenvQ51Q96Unx05WeOiuFMdj25M,1151
|
32
32
|
remote/text_manager.py,sha256=C2wNSXPSCDu8NSD3RsfbKmUQMWOYd1B5N4tzy-Jsriw,2195
|
33
33
|
remote/username_manager.py,sha256=nMNdke-2FIv86xR1Y6rR-43oUoQu_3Khw8wEo54noXI,3388
|
34
|
-
cliremote-1.4.
|
35
|
-
cliremote-1.4.
|
36
|
-
cliremote-1.4.
|
37
|
-
cliremote-1.4.
|
34
|
+
cliremote-1.4.5.dist-info/METADATA,sha256=KBdd5hKfr0fh7rPnySbjPklm4isIJGuHztut6Rnds_U,1202
|
35
|
+
cliremote-1.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
36
|
+
cliremote-1.4.5.dist-info/top_level.txt,sha256=yBZidJ6zCix_a2ubGlYaewvlzBFXWbckQt20dudxJ1E,7
|
37
|
+
cliremote-1.4.5.dist-info/RECORD,,
|
remote/account_manager.py
CHANGED
@@ -1,160 +1,246 @@
|
|
1
|
-
|
2
|
-
import
|
3
|
-
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import asyncio
|
4
|
+
import logging
|
5
|
+
import random
|
6
|
+
import traceback
|
7
|
+
from typing import Optional, Dict, List, Set, Tuple
|
4
8
|
from pyrogram import Client, errors
|
5
|
-
from .client_manager import (
|
6
|
-
ACCOUNTS_FOLDER,
|
7
|
-
save_account_data,
|
8
|
-
stop_all_clients,
|
9
|
-
accounts,
|
10
|
-
client_pool,
|
11
|
-
client_locks
|
12
|
-
)
|
13
9
|
|
10
|
+
# ============================================================
|
11
|
+
# ⚙️ تنظیم لاگ دقیق برای دیباگ Pyrogram و SQLite
|
12
|
+
# ============================================================
|
13
|
+
os.makedirs("logs", exist_ok=True)
|
14
|
+
log_file = "logs/client_debug_log.txt"
|
14
15
|
logger = logging.getLogger(__name__)
|
15
|
-
|
16
|
+
logger.setLevel(logging.DEBUG)
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
if not any(isinstance(h, logging.FileHandler) and h.baseFilename.endswith(log_file) for h in logger.handlers):
|
19
|
+
fh = logging.FileHandler(log_file, encoding="utf-8")
|
20
|
+
fmt = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S")
|
21
|
+
fh.setFormatter(fmt)
|
22
|
+
logger.addHandler(fh)
|
23
|
+
|
24
|
+
logger.info("🧩 Client Manager started in DEBUG MODE.")
|
25
|
+
|
26
|
+
# ============================================================
|
27
|
+
# 🧱 ساختار دادهها
|
28
|
+
# ============================================================
|
29
|
+
client_pool: Dict[str, Client] = {}
|
30
|
+
client_locks: Dict[str, asyncio.Lock] = {}
|
31
|
+
|
32
|
+
ACCOUNTS_FOLDER = "acc"
|
33
|
+
ACCOUNTS_DATA_FOLDER = "acc_data"
|
34
|
+
os.makedirs(ACCOUNTS_FOLDER, exist_ok=True)
|
35
|
+
os.makedirs(ACCOUNTS_DATA_FOLDER, exist_ok=True)
|
36
|
+
|
37
|
+
|
38
|
+
# ============================================================
|
39
|
+
# 🧠 ساخت یا دریافت کلاینت
|
40
|
+
# ============================================================
|
41
|
+
async def get_or_start_client(phone_number: str) -> Optional[Client]:
|
42
|
+
cli = client_pool.get(phone_number)
|
21
43
|
try:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
login['hash'] = api[0]
|
42
|
-
login['number'] = phone_number
|
43
|
-
login['api_data'] = {
|
44
|
-
'api_id': api[1],
|
45
|
-
'api_hash': api[0],
|
46
|
-
'phone_number': phone_number,
|
47
|
-
'session': phone_number,
|
48
|
-
'2fa_password': None
|
49
|
-
}
|
44
|
+
if cli is not None and getattr(cli, "is_connected", False):
|
45
|
+
logger.debug(f"{phone_number}: Already connected → {cli.session_name}")
|
46
|
+
return cli
|
47
|
+
|
48
|
+
cli = _make_client_from_json(phone_number)
|
49
|
+
if cli is None:
|
50
|
+
logger.error(f"{phone_number}: ❌ Could not build client (no JSON or invalid data)")
|
51
|
+
return None
|
52
|
+
|
53
|
+
session_db_path = f"{cli.session_name}.session"
|
54
|
+
logger.debug(f"{phone_number}: Session DB path → {session_db_path}")
|
55
|
+
|
56
|
+
if not os.path.exists(session_db_path):
|
57
|
+
logger.warning(f"{phone_number}: Session file not found → {session_db_path}")
|
58
|
+
else:
|
59
|
+
size = os.path.getsize(session_db_path)
|
60
|
+
logger.debug(f"{phone_number}: Session file exists ({size} bytes)")
|
61
|
+
if not os.access(session_db_path, os.R_OK | os.W_OK):
|
62
|
+
logger.warning(f"{phone_number}: ⚠️ No read/write permission for {session_db_path}")
|
50
63
|
|
51
64
|
try:
|
52
|
-
|
53
|
-
await
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
65
|
+
await cli.start()
|
66
|
+
await asyncio.sleep(0.4)
|
67
|
+
logger.info(f"{phone_number}: ✅ Client started successfully.")
|
68
|
+
except errors.SessionPasswordNeeded:
|
69
|
+
twofa = getattr(cli, "_twofa_password", None)
|
70
|
+
if twofa:
|
71
|
+
await cli.check_password(twofa)
|
72
|
+
logger.info(f"{phone_number}: ✅ 2FA password applied.")
|
73
|
+
else:
|
74
|
+
logger.error(f"{phone_number}: ⚠️ 2FA required but missing.")
|
75
|
+
return None
|
76
|
+
except errors.AuthKeyDuplicated:
|
77
|
+
logger.error(f"{phone_number}: ❌ AuthKeyDuplicated (session invalid).")
|
78
|
+
return None
|
60
79
|
except Exception as e:
|
61
|
-
|
62
|
-
|
63
|
-
|
80
|
+
tb = traceback.format_exc(limit=3)
|
81
|
+
logger.error(f"{phone_number}: ❌ Start failed - {type(e).__name__}: {e}\n{tb}")
|
82
|
+
return None
|
64
83
|
|
84
|
+
client_pool[phone_number] = cli
|
85
|
+
client_locks.setdefault(phone_number, asyncio.Lock())
|
86
|
+
return cli
|
65
87
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
global login
|
71
|
-
parts = message.text.split(' ', 1)
|
72
|
-
if len(parts) < 2:
|
73
|
-
await message.reply('`code 12345`')
|
74
|
-
return
|
75
|
-
code = parts[1].strip()
|
88
|
+
except Exception as e:
|
89
|
+
tb = traceback.format_exc(limit=3)
|
90
|
+
logger.critical(f"{phone_number}: 💥 Fatal error in get_or_start_client - {type(e).__name__}: {e}\n{tb}")
|
91
|
+
return None
|
76
92
|
|
93
|
+
|
94
|
+
# ============================================================
|
95
|
+
# 🧩 ساخت کلاینت از JSON
|
96
|
+
# ============================================================
|
97
|
+
def _make_client_from_json(phone_number: str) -> Optional[Client]:
|
77
98
|
try:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
99
|
+
data_path = os.path.join(ACCOUNTS_DATA_FOLDER, f"{phone_number}.json")
|
100
|
+
if not os.path.exists(data_path):
|
101
|
+
logger.error(f"{phone_number}: ⚠️ Account JSON not found → {data_path}")
|
102
|
+
return None
|
103
|
+
|
104
|
+
with open(data_path, "r", encoding="utf-8") as f:
|
105
|
+
account_data = json.load(f)
|
106
|
+
|
107
|
+
session_base = account_data.get("session")
|
108
|
+
if not session_base:
|
109
|
+
logger.error(f"{phone_number}: Missing 'session' key in JSON → {data_path}")
|
110
|
+
return None
|
111
|
+
|
112
|
+
session_path = os.path.join(ACCOUNTS_FOLDER, session_base)
|
113
|
+
if not session_path.endswith(".session"):
|
114
|
+
session_path += ".session"
|
115
|
+
|
116
|
+
os.makedirs(os.path.dirname(session_path), exist_ok=True)
|
117
|
+
|
118
|
+
logger.debug(f"{phone_number}: Final session path → {session_path}")
|
119
|
+
|
120
|
+
api_id = account_data.get("api_id")
|
121
|
+
api_hash = account_data.get("api_hash")
|
122
|
+
if not api_id or not api_hash:
|
123
|
+
logger.error(f"{phone_number}: Missing API credentials in JSON → {data_path}")
|
124
|
+
return None
|
125
|
+
|
126
|
+
cli = Client(
|
127
|
+
name=session_path,
|
128
|
+
api_id=int(api_id),
|
129
|
+
api_hash=str(api_hash),
|
130
|
+
sleep_threshold=30,
|
131
|
+
workdir=os.path.join("acc_temp", phone_number),
|
132
|
+
no_updates=True,
|
133
|
+
)
|
134
|
+
|
135
|
+
if account_data.get("2fa_password"):
|
136
|
+
setattr(cli, "_twofa_password", account_data["2fa_password"])
|
137
|
+
|
138
|
+
return cli
|
139
|
+
|
87
140
|
except Exception as e:
|
88
|
-
|
141
|
+
tb = traceback.format_exc(limit=3)
|
142
|
+
logger.critical(f"{phone_number}: 💥 Error creating client - {type(e).__name__}: {e}\n{tb}")
|
143
|
+
return None
|
89
144
|
|
90
145
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
async def
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
146
|
+
# ============================================================
|
147
|
+
# 🚀 Preload با لاگ کامل
|
148
|
+
# ============================================================
|
149
|
+
async def preload_clients(limit: Optional[int] = None) -> None:
|
150
|
+
phones = list(get_active_accounts())
|
151
|
+
if limit is not None:
|
152
|
+
phones = phones[:max(0, int(limit))]
|
153
|
+
|
154
|
+
if not phones:
|
155
|
+
logger.info("⚙️ No accounts found for preload.")
|
99
156
|
return
|
100
|
-
password = parts[1].strip()
|
101
|
-
try:
|
102
|
-
await login['client'].check_password(password)
|
103
|
-
login['api_data']['2fa_password'] = password
|
104
|
-
save_account_data(login['number'], login['api_data'])
|
105
|
-
await message.reply(f"✅ اکانت با موفقیت اضافه شد!\n├ شماره: {login['number']}")
|
106
|
-
await login['client'].disconnect()
|
107
|
-
login = {}
|
108
|
-
except errors.BadRequest:
|
109
|
-
await message.reply('رمز اشتباه است!')
|
110
|
-
except Exception as e:
|
111
|
-
await message.reply(f'⚠️ خطا در ثبت پسورد: {e}')
|
112
157
|
|
158
|
+
logger.info(f"🚀 Preloading {len(phones)} clients...")
|
159
|
+
ok, bad = 0, 0
|
113
160
|
|
114
|
-
|
115
|
-
|
116
|
-
# ==========================
|
117
|
-
def remove_client_from_pool(phone_number: str):
|
118
|
-
cli = client_pool.get(phone_number)
|
119
|
-
if cli:
|
161
|
+
for idx, phone in enumerate(phones, 1):
|
162
|
+
logger.info(f"🔹 [{idx}/{len(phones)}] Loading client {phone}")
|
120
163
|
try:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
164
|
+
cli = await get_or_start_client(phone)
|
165
|
+
if cli and getattr(cli, "is_connected", False):
|
166
|
+
ok += 1
|
167
|
+
logger.info(f"{phone}: ✅ Connected.")
|
168
|
+
else:
|
169
|
+
bad += 1
|
170
|
+
logger.warning(f"{phone}: ❌ Not connected after start().")
|
171
|
+
except Exception as e:
|
172
|
+
bad += 1
|
173
|
+
tb = traceback.format_exc(limit=3)
|
174
|
+
logger.error(f"{phone}: ❌ Exception during preload - {type(e).__name__}: {e}\n{tb}")
|
126
175
|
|
176
|
+
await asyncio.sleep(1.0)
|
127
177
|
|
128
|
-
|
178
|
+
logger.info(f"🎯 Preload completed: OK={ok} | FAIL={bad}")
|
179
|
+
|
180
|
+
|
181
|
+
# ============================================================
|
182
|
+
# 🧹 توقف تمام کلاینتها
|
183
|
+
# ============================================================
|
184
|
+
async def stop_all_clients() -> None:
|
185
|
+
logger.info("🧹 Stopping all clients...")
|
186
|
+
for phone, cli in list(client_pool.items()):
|
187
|
+
try:
|
188
|
+
await cli.stop()
|
189
|
+
logger.info(f"{phone}: 📴 Stopped successfully.")
|
190
|
+
except Exception as e:
|
191
|
+
tb = traceback.format_exc(limit=2)
|
192
|
+
logger.warning(f"{phone}: ⚠️ Error stopping client - {type(e).__name__}: {e}\n{tb}")
|
193
|
+
finally:
|
194
|
+
client_pool.pop(phone, None)
|
195
|
+
await asyncio.sleep(0.3)
|
196
|
+
logger.info("✅ All clients stopped cleanly.")
|
197
|
+
|
198
|
+
|
199
|
+
# ============================================================
|
200
|
+
# 📦 مدیریت JSON دادههای اکانت
|
201
|
+
# ============================================================
|
202
|
+
def get_account_data(phone_number: str) -> Optional[Dict]:
|
203
|
+
"""
|
204
|
+
خواندن دادههای JSON اکانت از acc_data/{phone}.json
|
205
|
+
"""
|
206
|
+
file_path = os.path.join(ACCOUNTS_DATA_FOLDER, f"{phone_number}.json")
|
207
|
+
if not os.path.exists(file_path):
|
208
|
+
logger.warning(f"{phone_number}: ⚠️ Account JSON not found at {file_path}")
|
209
|
+
return None
|
129
210
|
try:
|
130
|
-
|
131
|
-
|
132
|
-
remove_client_from_pool(phone_number)
|
133
|
-
if os.path.isfile(main_path):
|
134
|
-
os.unlink(main_path)
|
135
|
-
await message.reply('<b>Account deleted successfully.</b>')
|
136
|
-
else:
|
137
|
-
await message.reply('<b>Account not found in database.</b>')
|
138
|
-
except IndexError:
|
139
|
-
await message.reply('Please enter phone number')
|
211
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
212
|
+
return json.load(f)
|
140
213
|
except Exception as e:
|
141
|
-
|
214
|
+
logger.error(f"{phone_number}: ⚠️ Error reading JSON - {type(e).__name__}: {e}")
|
215
|
+
return None
|
142
216
|
|
143
217
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
218
|
+
def save_account_data(phone_number: str, data: Dict) -> None:
|
219
|
+
"""
|
220
|
+
ذخیره اطلاعات JSON اکانت در acc_data/{phone}.json
|
221
|
+
"""
|
222
|
+
os.makedirs(ACCOUNTS_DATA_FOLDER, exist_ok=True)
|
223
|
+
file_path = os.path.join(ACCOUNTS_DATA_FOLDER, f"{phone_number}.json")
|
148
224
|
try:
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
for session in accountss:
|
153
|
-
main_path = f'{ACCOUNTS_FOLDER}/{session}.session'
|
154
|
-
try:
|
155
|
-
os.unlink(main_path)
|
156
|
-
except Exception:
|
157
|
-
pass
|
158
|
-
await message.reply(f'<b>{count} accounts deleted.</b>')
|
225
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
226
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
227
|
+
logger.info(f"{phone_number}: 💾 Account data saved successfully → {file_path}")
|
159
228
|
except Exception as e:
|
160
|
-
|
229
|
+
logger.error(f"{phone_number}: ⚠️ Error saving JSON - {type(e).__name__}: {e}")
|
230
|
+
|
231
|
+
|
232
|
+
# ============================================================
|
233
|
+
# 📋 لیست اکانتهای فعال
|
234
|
+
# ============================================================
|
235
|
+
def accounts() -> List[str]:
|
236
|
+
accs: Set[str] = set()
|
237
|
+
if not os.path.isdir(ACCOUNTS_FOLDER):
|
238
|
+
return []
|
239
|
+
for acc in os.listdir(ACCOUNTS_FOLDER):
|
240
|
+
if acc.endswith(".session"):
|
241
|
+
accs.add(acc.split(".")[0])
|
242
|
+
return list(accs)
|
243
|
+
|
244
|
+
|
245
|
+
def get_active_accounts() -> Set[str]:
|
246
|
+
return set(accounts())
|
File without changes
|
File without changes
|
File without changes
|