CliRemote 1.6.0__tar.gz → 1.6.1__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.
- {cliremote-1.6.0 → cliremote-1.6.1}/CliRemote.egg-info/PKG-INFO +1 -1
- {cliremote-1.6.0 → cliremote-1.6.1}/PKG-INFO +1 -1
- {cliremote-1.6.0 → cliremote-1.6.1}/pyproject.toml +1 -1
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/client_manager.py +16 -0
- cliremote-1.6.1/remote/client_picker.py +40 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/setup.py +1 -1
- cliremote-1.6.0/remote/client_picker.py +0 -109
- {cliremote-1.6.0 → cliremote-1.6.1}/CliRemote.egg-info/SOURCES.txt +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/CliRemote.egg-info/dependency_links.txt +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/CliRemote.egg-info/requires.txt +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/CliRemote.egg-info/top_level.txt +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/LICENSE +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/MANIFEST.in +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/README.md +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/__init__.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/account_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/account_viewer.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/admin_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/analytics_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/batch_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/block_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/caption_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/cleaner.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/config.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/device_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/file_sender.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/getcode_controller.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/health.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/help_menu.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/init.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/join_controller.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/joiner.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/leave_controller.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/lefter.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/mention_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/precise_engine.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/profile_info.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/profile_media.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/profile_privacy.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/spammer.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/speed_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/stop_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/text_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/username_manager.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/remote/utils/sqlite_utils.py +0 -0
- {cliremote-1.6.0 → cliremote-1.6.1}/setup.cfg +0 -0
@@ -229,3 +229,19 @@ def accounts() -> List[str]:
|
|
229
229
|
|
230
230
|
def get_active_accounts() -> Set[str]:
|
231
231
|
return set(accounts())
|
232
|
+
|
233
|
+
|
234
|
+
def remove_client_from_pool(phone_number: str) -> None:
|
235
|
+
"""
|
236
|
+
کلاینت را از pool خارج و stop میکند (بهصورت غیرمسدودکننده).
|
237
|
+
"""
|
238
|
+
cli = client_pool.get(phone_number)
|
239
|
+
if cli is not None:
|
240
|
+
try:
|
241
|
+
asyncio.create_task(cli.stop())
|
242
|
+
logger.info("%s: scheduled stop()", phone_number)
|
243
|
+
except Exception as e:
|
244
|
+
logger.warning("%s: stop() scheduling error: %s: %s", phone_number, type(e).__name__, e)
|
245
|
+
client_pool.pop(phone_number, None)
|
246
|
+
client_locks.pop(phone_number, None)
|
247
|
+
logger.info("%s: removed from client_pool and client_locks", phone_number)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import random
|
2
|
+
import logging
|
3
|
+
from typing import Optional
|
4
|
+
from .client_manager import client_pool, get_active_accounts, get_or_start_client
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
async def get_any_client(message=None) -> Optional[object]:
|
9
|
+
"""
|
10
|
+
یک کلاینت آماده برمیگرداند:
|
11
|
+
1) اگر کلاینت متصل در pool هست، همان را برمیگرداند.
|
12
|
+
2) وگرنه از بین active_accounts یکی را استارت میکند.
|
13
|
+
"""
|
14
|
+
# Use any connected client
|
15
|
+
for phone, cli in list(client_pool.items()):
|
16
|
+
try:
|
17
|
+
if getattr(cli, "is_connected", False):
|
18
|
+
logger.debug("get_any_client: using connected client %s", phone)
|
19
|
+
return cli
|
20
|
+
except Exception:
|
21
|
+
pass
|
22
|
+
|
23
|
+
# Start one if needed
|
24
|
+
accs = list(get_active_accounts())
|
25
|
+
if not accs:
|
26
|
+
logger.warning("get_any_client: no active accounts")
|
27
|
+
return None
|
28
|
+
|
29
|
+
random.shuffle(accs)
|
30
|
+
for phone in accs:
|
31
|
+
try:
|
32
|
+
cli = await get_or_start_client(phone)
|
33
|
+
if cli and getattr(cli, "is_connected", False):
|
34
|
+
logger.info("get_any_client: started %s", phone)
|
35
|
+
return cli
|
36
|
+
except Exception as e:
|
37
|
+
logger.warning("get_any_client: failed start %s: %s: %s", phone, type(e).__name__, e)
|
38
|
+
|
39
|
+
logger.error("get_any_client: could not get any client")
|
40
|
+
return None
|
@@ -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.6.
|
8
|
+
version="1.6.1",
|
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,109 +0,0 @@
|
|
1
|
-
# remote/client_picker.py
|
2
|
-
import random
|
3
|
-
import asyncio
|
4
|
-
import logging
|
5
|
-
from typing import Optional, Callable, Iterable
|
6
|
-
|
7
|
-
logger = logging.getLogger(__name__)
|
8
|
-
|
9
|
-
# ---- وابستگیهای اصلی
|
10
|
-
# تلاش میکنیم client_manager را حتماً داشته باشیم
|
11
|
-
from . import client_manager
|
12
|
-
|
13
|
-
# get_active() را بهصورت ایمن تعیین میکنیم:
|
14
|
-
def _resolve_get_active() -> Callable[[], Iterable[str]]:
|
15
|
-
"""
|
16
|
-
سعی میکند منبع معتبر لیست اکانتهای فعال را پیدا کند:
|
17
|
-
1) client_manager.get_active_accounts()
|
18
|
-
2) account_manager.get_active_accounts()
|
19
|
-
3) account_manager.accounts() (fallback)
|
20
|
-
4) client_manager.accounts() (fallback)
|
21
|
-
و در نهایت اگر هیچکدام نبود، یک فانکشنِ خالی برمیگرداند.
|
22
|
-
"""
|
23
|
-
# 1) client_manager.get_active_accounts
|
24
|
-
if hasattr(client_manager, "get_active_accounts"):
|
25
|
-
return client_manager.get_active_accounts
|
26
|
-
|
27
|
-
# 2) account_manager...
|
28
|
-
try:
|
29
|
-
from . import account_manager # ممکن است وجود نداشته باشد
|
30
|
-
if hasattr(account_manager, "get_active_accounts"):
|
31
|
-
return account_manager.get_active_accounts
|
32
|
-
if hasattr(account_manager, "accounts"):
|
33
|
-
return lambda: set(account_manager.accounts())
|
34
|
-
except Exception:
|
35
|
-
pass
|
36
|
-
|
37
|
-
# 3) fallback به client_manager.accounts
|
38
|
-
if hasattr(client_manager, "accounts"):
|
39
|
-
return lambda: set(client_manager.accounts())
|
40
|
-
|
41
|
-
# 4) آخرین fallback: لیست خالی
|
42
|
-
return lambda: set()
|
43
|
-
|
44
|
-
_get_active_accounts = _resolve_get_active()
|
45
|
-
|
46
|
-
|
47
|
-
async def get_any_client(message=None, max_attempts: int = 3) -> Optional[object]:
|
48
|
-
"""
|
49
|
-
تلاش برای گرفتن یک کلاینت فعال از بین اکانتها.
|
50
|
-
- تا `max_attempts` بار با اکانتهای تصادفی امتحان میکند.
|
51
|
-
- اگر موفق نشد، پیام خطا (در صورت وجود message) ارسال میکند،
|
52
|
-
سپس stop_all_clients() فراخوانی میشود و در نهایت None برمیگرداند.
|
53
|
-
"""
|
54
|
-
try:
|
55
|
-
acc_iter = _get_active_accounts()
|
56
|
-
acc_list = list(acc_iter) if not isinstance(acc_iter, (list, set, tuple)) else list(acc_iter)
|
57
|
-
except Exception as e:
|
58
|
-
logger.error(f"❌ نتوانستم لیست اکانتها را بگیرم: {type(e).__name__} - {e}")
|
59
|
-
acc_list = []
|
60
|
-
|
61
|
-
if not acc_list:
|
62
|
-
if message:
|
63
|
-
try:
|
64
|
-
await message.reply("⚠️ هیچ اکانت فعالی برای اتصال وجود ندارد.")
|
65
|
-
except Exception:
|
66
|
-
pass
|
67
|
-
logger.warning("⚠️ هیچ اکانت فعالی در دسترس نیست.")
|
68
|
-
return None
|
69
|
-
|
70
|
-
tried = set()
|
71
|
-
|
72
|
-
for attempt in range(1, max_attempts + 1):
|
73
|
-
if len(tried) == len(acc_list):
|
74
|
-
break
|
75
|
-
|
76
|
-
phone = random.choice([p for p in acc_list if p not in tried])
|
77
|
-
tried.add(phone)
|
78
|
-
logger.info(f"🔁 تلاش {attempt}/{max_attempts} برای اتصال با اکانت {phone}")
|
79
|
-
|
80
|
-
try:
|
81
|
-
cli = await client_manager.get_or_start_client(phone)
|
82
|
-
if cli and getattr(cli, "is_connected", True):
|
83
|
-
logger.info(f"✅ اتصال موفق با اکانت {phone}")
|
84
|
-
return cli
|
85
|
-
else:
|
86
|
-
logger.warning(f"⚠️ اکانت {phone} وصل نیست یا کلاینت معتبر برنگشته.")
|
87
|
-
except Exception as e:
|
88
|
-
logger.error(f"❌ خطا در اتصال {phone}: {type(e).__name__} - {e}")
|
89
|
-
try:
|
90
|
-
await asyncio.sleep(1)
|
91
|
-
except Exception:
|
92
|
-
pass
|
93
|
-
|
94
|
-
# شکست پس از تلاشها
|
95
|
-
error_msg = f"❌ هیچ کلاینت فعالی پس از {max_attempts} تلاش یافت نشد. در حال ریست کامل کلاینتها..."
|
96
|
-
if message:
|
97
|
-
try:
|
98
|
-
await message.reply(error_msg)
|
99
|
-
except Exception:
|
100
|
-
pass
|
101
|
-
logger.error(error_msg)
|
102
|
-
|
103
|
-
try:
|
104
|
-
await client_manager.stop_all_clients()
|
105
|
-
logger.warning("🔄 تمام کلاینتها ریست شدند (stop_all_clients فراخوانی شد).")
|
106
|
-
except Exception as e:
|
107
|
-
logger.error(f"⚠️ خطا در ریست کلاینتها: {type(e).__name__} - {e}")
|
108
|
-
|
109
|
-
return None
|
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
|
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
|
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
|