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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CliRemote
3
- Version: 1.4.4
3
+ Version: 1.4.5
4
4
  Summary: Remote client framework for Telegram automation using Pyrogram
5
5
  Home-page: https://github.com/MohammadAhmadi-R/CliRemote
6
6
  Author: MrAhmadiRad
@@ -1,6 +1,6 @@
1
- cliremote-1.4.4.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
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=WfllTSrXuVaIU2ztQAoiXznuf_7mywqn4i9GCQKskQE,5684
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.4.dist-info/METADATA,sha256=PKeg69Ase2IpQrRBXkxcaPE7XpFEsHziwKdZnyGZ3Qo,1202
35
- cliremote-1.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- cliremote-1.4.4.dist-info/top_level.txt,sha256=yBZidJ6zCix_a2ubGlYaewvlzBFXWbckQt20dudxJ1E,7
37
- cliremote-1.4.4.dist-info/RECORD,,
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
- # antispam_core/account_manager.py
2
- import os, asyncio, json, logging
3
- from typing import Optional, Dict, List
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
- login = {}
16
+ logger.setLevel(logging.DEBUG)
16
17
 
17
- # ==========================
18
- # افزودن اکانت جدید
19
- # ==========================
20
- async def add_account_cmd(message, get_app_info):
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
- parts = message.text.split(' ', 1)
23
- if len(parts) < 2:
24
- await message.reply('مثال: `add +989123456789`')
25
- return
26
-
27
- phone_number = parts[1].strip()
28
- session_file = os.path.join(ACCOUNTS_FOLDER, f'{phone_number}.session')
29
-
30
- if os.path.exists(session_file):
31
- await message.reply('این اکانت وجود دارد!')
32
- return
33
-
34
- global login
35
- api = get_app_info()
36
- if not api or len(api) < 2:
37
- await message.reply('مشکل در API!')
38
- return
39
-
40
- login['id'] = int(api[1])
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
- login['client'] = Client(name=session_file.replace('.session', ''), api_id=login['id'], api_hash=login['hash'])
53
- await login['client'].connect()
54
- login['response'] = await login['client'].send_code(phone_number)
55
- await message.reply(f'✅ کد تأیید به {phone_number} ارسال شد.\n`code 12345`')
56
- except errors.BadRequest as e:
57
- await message.reply(f'Bad request: {str(e)}')
58
- except errors.FloodWait as e:
59
- await message.reply(f'Flood wait: {e.value} sec')
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
- await message.reply(f'Connection error: {str(e)}')
62
- except Exception as e:
63
- await message.reply(f'خطا: {str(e)}')
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
- async def set_code_cmd(message):
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
- await login['client'].sign_in(login['number'], login['response'].phone_code_hash, code)
79
- await login['client'].disconnect()
80
- save_account_data(login['number'], login['api_data'])
81
- await message.reply(f"✅ اکانت اضافه شد!\n├ شماره: {login['number']}")
82
- login = {}
83
- except errors.SessionPasswordNeeded:
84
- await message.reply('🔒 لطفا رمز را با `pass your_password` بدهید')
85
- except errors.BadRequest:
86
- await message.reply('ورود با مشکل مواجه شد')
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
- await message.reply(f'⚠️ خطا در ورود: {e}')
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 set_2fa_cmd(message):
95
- global login
96
- parts = message.text.split(' ', 1)
97
- if len(parts) < 2:
98
- await message.reply('`pass my_password`')
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
- asyncio.create_task(cli.stop())
122
- except:
123
- pass
124
- client_pool.pop(phone_number, None)
125
- client_locks.pop(phone_number, None)
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
- async def delete_account_cmd(message):
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
- phone_number = message.text.split()[1]
131
- main_path = f'{ACCOUNTS_FOLDER}/{phone_number}.session'
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
- await message.reply(f'<b>Error deleting account: {str(e)}</b>')
214
+ logger.error(f"{phone_number}: ⚠️ Error reading JSON - {type(e).__name__}: {e}")
215
+ return None
142
216
 
143
217
 
144
- # ==========================
145
- # حذف تمام اکانت‌ها
146
- # ==========================
147
- async def delete_all_accounts_cmd(message):
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
- accountss = accounts()
150
- count = len(accountss)
151
- await stop_all_clients()
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
- await message.reply(f'<b>Error deleting all accounts: {str(e)}</b>')
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())