CliRemote 1.4.4__tar.gz → 1.4.6__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.
Files changed (45) hide show
  1. {cliremote-1.4.4 → cliremote-1.4.6}/CliRemote.egg-info/PKG-INFO +1 -1
  2. {cliremote-1.4.4 → cliremote-1.4.6}/PKG-INFO +1 -1
  3. {cliremote-1.4.4 → cliremote-1.4.6}/pyproject.toml +1 -1
  4. cliremote-1.4.6/remote/account_manager.py +246 -0
  5. {cliremote-1.4.4 → cliremote-1.4.6}/remote/device_manager.py +3 -2
  6. {cliremote-1.4.4 → cliremote-1.4.6}/setup.py +1 -1
  7. cliremote-1.4.4/remote/account_manager.py +0 -160
  8. {cliremote-1.4.4 → cliremote-1.4.6}/CliRemote.egg-info/SOURCES.txt +0 -0
  9. {cliremote-1.4.4 → cliremote-1.4.6}/CliRemote.egg-info/dependency_links.txt +0 -0
  10. {cliremote-1.4.4 → cliremote-1.4.6}/CliRemote.egg-info/requires.txt +0 -0
  11. {cliremote-1.4.4 → cliremote-1.4.6}/CliRemote.egg-info/top_level.txt +0 -0
  12. {cliremote-1.4.4 → cliremote-1.4.6}/LICENSE +0 -0
  13. {cliremote-1.4.4 → cliremote-1.4.6}/MANIFEST.in +0 -0
  14. {cliremote-1.4.4 → cliremote-1.4.6}/README.md +0 -0
  15. {cliremote-1.4.4 → cliremote-1.4.6}/remote/__init__.py +0 -0
  16. {cliremote-1.4.4 → cliremote-1.4.6}/remote/account_viewer.py +0 -0
  17. {cliremote-1.4.4 → cliremote-1.4.6}/remote/admin_manager.py +0 -0
  18. {cliremote-1.4.4 → cliremote-1.4.6}/remote/analytics_manager.py +0 -0
  19. {cliremote-1.4.4 → cliremote-1.4.6}/remote/batch_manager.py +0 -0
  20. {cliremote-1.4.4 → cliremote-1.4.6}/remote/block_manager.py +0 -0
  21. {cliremote-1.4.4 → cliremote-1.4.6}/remote/caption_manager.py +0 -0
  22. {cliremote-1.4.4 → cliremote-1.4.6}/remote/cleaner.py +0 -0
  23. {cliremote-1.4.4 → cliremote-1.4.6}/remote/client_manager.py +0 -0
  24. {cliremote-1.4.4 → cliremote-1.4.6}/remote/client_picker.py +0 -0
  25. {cliremote-1.4.4 → cliremote-1.4.6}/remote/config.py +0 -0
  26. {cliremote-1.4.4 → cliremote-1.4.6}/remote/file_sender.py +0 -0
  27. {cliremote-1.4.4 → cliremote-1.4.6}/remote/getcode_controller.py +0 -0
  28. {cliremote-1.4.4 → cliremote-1.4.6}/remote/health.py +0 -0
  29. {cliremote-1.4.4 → cliremote-1.4.6}/remote/help_menu.py +0 -0
  30. {cliremote-1.4.4 → cliremote-1.4.6}/remote/init.py +0 -0
  31. {cliremote-1.4.4 → cliremote-1.4.6}/remote/join_controller.py +0 -0
  32. {cliremote-1.4.4 → cliremote-1.4.6}/remote/joiner.py +0 -0
  33. {cliremote-1.4.4 → cliremote-1.4.6}/remote/leave_controller.py +0 -0
  34. {cliremote-1.4.4 → cliremote-1.4.6}/remote/lefter.py +0 -0
  35. {cliremote-1.4.4 → cliremote-1.4.6}/remote/mention_manager.py +0 -0
  36. {cliremote-1.4.4 → cliremote-1.4.6}/remote/precise_engine.py +0 -0
  37. {cliremote-1.4.4 → cliremote-1.4.6}/remote/profile_info.py +0 -0
  38. {cliremote-1.4.4 → cliremote-1.4.6}/remote/profile_media.py +0 -0
  39. {cliremote-1.4.4 → cliremote-1.4.6}/remote/profile_privacy.py +0 -0
  40. {cliremote-1.4.4 → cliremote-1.4.6}/remote/spammer.py +0 -0
  41. {cliremote-1.4.4 → cliremote-1.4.6}/remote/speed_manager.py +0 -0
  42. {cliremote-1.4.4 → cliremote-1.4.6}/remote/stop_manager.py +0 -0
  43. {cliremote-1.4.4 → cliremote-1.4.6}/remote/text_manager.py +0 -0
  44. {cliremote-1.4.4 → cliremote-1.4.6}/remote/username_manager.py +0 -0
  45. {cliremote-1.4.4 → cliremote-1.4.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CliRemote
3
- Version: 1.4.4
3
+ Version: 1.4.6
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
1
  Metadata-Version: 2.4
2
2
  Name: CliRemote
3
- Version: 1.4.4
3
+ Version: 1.4.6
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "CliRemote"
7
- version = "1.4.4"
7
+ version = "1.4.6"
8
8
  description = "Remote client framework for Telegram automation using Pyrogram"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,246 @@
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
8
+ from pyrogram import Client, errors
9
+
10
+ # ============================================================
11
+ # ⚙️ تنظیم لاگ دقیق برای دیباگ Pyrogram و SQLite
12
+ # ============================================================
13
+ os.makedirs("logs", exist_ok=True)
14
+ log_file = "logs/client_debug_log.txt"
15
+ logger = logging.getLogger(__name__)
16
+ logger.setLevel(logging.DEBUG)
17
+
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)
43
+ try:
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}")
63
+
64
+ try:
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
79
+ except Exception as 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
83
+
84
+ client_pool[phone_number] = cli
85
+ client_locks.setdefault(phone_number, asyncio.Lock())
86
+ return cli
87
+
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
92
+
93
+
94
+ # ============================================================
95
+ # 🧩 ساخت کلاینت از JSON
96
+ # ============================================================
97
+ def _make_client_from_json(phone_number: str) -> Optional[Client]:
98
+ try:
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
+
140
+ except Exception as 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
144
+
145
+
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.")
156
+ return
157
+
158
+ logger.info(f"🚀 Preloading {len(phones)} clients...")
159
+ ok, bad = 0, 0
160
+
161
+ for idx, phone in enumerate(phones, 1):
162
+ logger.info(f"🔹 [{idx}/{len(phones)}] Loading client {phone}")
163
+ try:
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}")
175
+
176
+ await asyncio.sleep(1.0)
177
+
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
210
+ try:
211
+ with open(file_path, "r", encoding="utf-8") as f:
212
+ return json.load(f)
213
+ except Exception as e:
214
+ logger.error(f"{phone_number}: ⚠️ Error reading JSON - {type(e).__name__}: {e}")
215
+ return None
216
+
217
+
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")
224
+ try:
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}")
228
+ except Exception as e:
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())
@@ -1,8 +1,9 @@
1
1
  # antispam_core/device_manager.py (updated safe version)
2
2
  import asyncio, logging
3
3
  from pyrogram import Client, errors, raw
4
- from .client_manager import get_or_start_client, get_account_data, accounts
5
- from . import admin_manager
4
+ from .client_manager import get_or_start_client, accounts
5
+ from .account_manager import get_account_data
6
+
6
7
 
7
8
  logger = logging.getLogger(__name__)
8
9
 
@@ -5,7 +5,7 @@ with open("README.md", encoding="utf-8") as f:
5
5
 
6
6
  setup(
7
7
  name="CliRemote",
8
- version="1.4.4",
8
+ version="1.4.6",
9
9
  author="MrAhmadiRad",
10
10
  author_email="mohammadahmadirad69@gmail.com",
11
11
  description="A precise, async-safe, Telegram automation core (Python 3.8+)",
@@ -1,160 +0,0 @@
1
- # antispam_core/account_manager.py
2
- import os, asyncio, json, logging
3
- from typing import Optional, Dict, List
4
- 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
-
14
- logger = logging.getLogger(__name__)
15
- login = {}
16
-
17
- # ==========================
18
- # افزودن اکانت جدید
19
- # ==========================
20
- async def add_account_cmd(message, get_app_info):
21
- 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
- }
50
-
51
- 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')
60
- 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)}')
64
-
65
-
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()
76
-
77
- 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('ورود با مشکل مواجه شد')
87
- except Exception as e:
88
- await message.reply(f'⚠️ خطا در ورود: {e}')
89
-
90
-
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`')
99
- 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
-
113
-
114
- # ==========================
115
- # حذف یک اکانت خاص
116
- # ==========================
117
- def remove_client_from_pool(phone_number: str):
118
- cli = client_pool.get(phone_number)
119
- if cli:
120
- 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)
126
-
127
-
128
- async def delete_account_cmd(message):
129
- 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')
140
- except Exception as e:
141
- await message.reply(f'<b>Error deleting account: {str(e)}</b>')
142
-
143
-
144
- # ==========================
145
- # حذف تمام اکانت‌ها
146
- # ==========================
147
- async def delete_all_accounts_cmd(message):
148
- 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>')
159
- except Exception as e:
160
- await message.reply(f'<b>Error deleting all accounts: {str(e)}</b>')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes