Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

remottxrea

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

remottxrea - npm Package Compare versions

Comparing version
1.0.94
to
1.0.95
+1
-1
PKG-INFO
Metadata-Version: 2.4
Name: remottxrea
Version: 1.0.94
Version: 1.0.95
Summary: Remote client framework for Telegram automation using Pyrogram

@@ -5,0 +5,0 @@ Home-page: https://github.com/MohammadAhmadi-R/remottxrea

@@ -7,3 +7,3 @@ [build-system]

name = "remottxrea"
version = "1.0.94"
version = "1.0.95"
description = "Remote client framework for Telegram automation using Pyrogram"

@@ -10,0 +10,0 @@ readme = "README.md"

Metadata-Version: 2.4
Name: remottxrea
Version: 1.0.94
Version: 1.0.95
Summary: Remote client framework for Telegram automation using Pyrogram

@@ -5,0 +5,0 @@ Home-page: https://github.com/MohammadAhmadi-R/remottxrea

@@ -8,3 +8,3 @@ from setuptools import setup, find_packages

name="remottxrea",
version="1.0.94",
version="1.0.95",
author="MrAhmadiRad",

@@ -11,0 +11,0 @@ author_email="mohammadahmadirad69@gmail.com",

# remottxrea/client/create_client.py
import os
import asyncio
from typing import Dict
from pyrogram import Client

@@ -13,10 +16,110 @@ from pyrogram.errors import SessionPasswordNeeded

# ==========================================================
# GLOBAL SAFE CLIENT POOL (NO WATCHER / NO SQLITE LOCK)
# ==========================================================
class _ClientPool:
"""
Production-grade client pool
- race safe
- auto reconnect
- no watcher
"""
def __init__(self):
self.clients: Dict[str, Client] = {}
self._lock = asyncio.Lock()
self._running = False
self._reconnect_task = None
# ---------------- LOCK ----------------
async def acquire(self):
await self._lock.acquire()
def release(self):
if self._lock.locked():
self._lock.release()
def is_locked(self):
return self._lock.locked()
# ---------------- REGISTER ----------------
async def register(self, phone: str, client: Client):
async with self._lock:
if phone in self.clients:
return
if not client.is_connected:
await client.start()
self.clients[phone] = client
# ---------------- AUTO RECONNECT LOOP ----------------
async def _reconnect_loop(self):
while self._running:
await asyncio.sleep(15)
async with self._lock:
for phone, client in list(self.clients.items()):
try:
if not client.is_connected:
await client.start()
except Exception:
# اگر fail شد حذف نمی‌کنیم
# فقط اجازه میدیم سیکل بعدی دوباره تلاش کند
pass
async def start(self):
if self._running:
return
self._running = True
self._reconnect_task = asyncio.create_task(
self._reconnect_loop()
)
async def stop(self):
self._running = False
if self._reconnect_task:
self._reconnect_task.cancel()
async with self._lock:
for client in self.clients.values():
try:
await client.stop()
except Exception:
pass
self.clients.clear()
# Singleton
client_pool = _ClientPool()
# ==========================================================
# SESSION CREATOR
# ==========================================================
class SessionCreator:
def __init__(self):
self.pending = {}
self.pending = {} # phone -> {app, phone_code_hash}
# ---------- BUILD CLIENT ----------
def build_client(self, phone, string_session=None):
# ======================================================
# BUILD CLIENT (SAFE)
# ======================================================
def build_client(self, phone: str, string_session: str | None = None):
saved_data = session_data_manager.load(phone)

@@ -29,2 +132,4 @@

api_id, api_hash = get_apis()
if string_session:

@@ -37,4 +142,2 @@ session_name = string_session

api_id, api_hash = get_apis()
return Client(

@@ -48,8 +151,13 @@ name=session_name,

lang_code=device["lang_code"],
in_memory=in_memory
in_memory=in_memory,
workdir=SESSIONS_DIR,
workers=1 # جلوگیری از race داخلی Pyrogram
)
# ---------- SEND CODE ----------
async def send_code(self, phone):
# ======================================================
# SEND CODE
# ======================================================
async def send_code(self, phone: str):
app = self.build_client(phone)

@@ -66,5 +174,10 @@

# ---------- LOGIN WITH CODE ----------
async def login_with_code(self, phone, code):
return True
# ======================================================
# LOGIN WITH CODE
# ======================================================
async def login_with_code(self, phone: str, code: str):
data = self.pending.get(phone)

@@ -86,16 +199,13 @@ if not data:

await self._save_session_data(
phone,
app,
password=None
)
# ✅ ثبت مستقیم session بعد از لاگین
await self._finalize_login(phone, app, password=None)
await app.disconnect()
del self.pending[phone]
return True
# ---------- LOGIN WITH PASSWORD ----------
async def login_with_password(self, phone, password):
# ======================================================
# LOGIN WITH PASSWORD
# ======================================================
async def login_with_password(self, phone: str, password: str):
data = self.pending.get(phone)

@@ -109,21 +219,13 @@ if not data:

await self._save_session_data(
phone,
app,
password=password
)
# ✅ ثبت مستقیم session بعد از لاگین
await self._finalize_login(phone, app, password=password)
await app.disconnect()
del self.pending[phone]
return True
# ---------- SAVE SESSION DATA ----------
async def _save_session_data(
self,
phone,
app: Client,
password
):
# ======================================================
# FINALIZE LOGIN (CORE LOGIC)
# ======================================================
async def _finalize_login(self, phone: str, app: Client, password):
me = await app.get_me()

@@ -150,2 +252,13 @@ session_string = await app.export_session_string()

session_data_manager.save(phone, data)
# ذخیره JSON
session_data_manager.save(phone, data)
# ثبت مستقیم داخل pool
await client_pool.register(phone, app)
# پاک کردن pending
if phone in self.pending:
del self.pending[phone]
# استارت reconnect loop اگر قبلاً شروع نشده
await client_pool.start()
"""
Enterprise Add Account Handler
Compatible with MultiSessionRunner
Race-safe + Dynamic Cooldown

@@ -12,3 +13,3 @@ """

from ...core.logger import get_action_logger
from ...runner.multi_session_runner import runner
from ...runner.multi_session_runner import runner # ← مهم

@@ -106,3 +107,2 @@

await runner.acquire_pool_lock()
try:

@@ -112,7 +112,5 @@ await self.creator.send_code(phone)

logger.exception(f"Send code failed → {e}")
runner.release_pool_lock()
return await message.reply_text("Failed to send code")
finally:
if runner.is_pool_locked():
runner.release_pool_lock()
runner.release_pool_lock()

@@ -155,3 +153,2 @@ state.update({

await runner.acquire_pool_lock()
try:

@@ -164,11 +161,12 @@ result = await self.creator.login_with_code(

logger.exception(f"Login error → {e}")
runner.release_pool_lock()
return await message.reply_text("Login failed")
finally:
if runner.is_pool_locked():
runner.release_pool_lock()
runner.release_pool_lock()
if result is True:
logger.info("Login success")
# بعد از لاگین، watcher خودش sync می‌کند
self.states.pop(user_id, None)
return await message.reply_text(

@@ -216,3 +214,2 @@ "Account added successfully ✅"

await runner.acquire_pool_lock()
try:

@@ -225,3 +222,2 @@ success = await self.creator.login_with_password(

logger.exception(f"2FA error → {e}")
runner.release_pool_lock()
return await message.reply_text(

@@ -231,8 +227,9 @@ "Password verification failed"

finally:
if runner.is_pool_locked():
runner.release_pool_lock()
runner.release_pool_lock()
if success:
logger.info("Login success with 2FA")
self.states.pop(user_id, None)
return await message.reply_text(

@@ -239,0 +236,0 @@ "Account added successfully ✅"

@@ -9,3 +9,2 @@ # remottxrea/runner/multi_session_runner.py

from pyrogram.errors import FloodWait, RPCError
from watchfiles import awatch

@@ -18,9 +17,7 @@ from ..config.main_config import SESSIONS_DIR

# ==========================================================
# MANAGED CLIENT
# ==========================================================
class ManagedClient:
"""
Wrapper around Pyrogram Client
Handles:
- auto reconnect
- per-client lock
"""

@@ -51,6 +48,7 @@ def __init__(self, phone: str, client: Client):

# ==========================================================
# MULTI SESSION RUNNER (NO WATCHER VERSION)
# ==========================================================
class MultiSessionRunner:
"""
Production-grade client pool
"""

@@ -60,57 +58,72 @@ def __init__(self):

self._registry_lock = asyncio.Lock()
self._watch_task = None
self._running = False
# ==================================================
# INITIAL LOAD
# INITIAL LOAD (ONLY ON STARTUP)
# ==================================================
async def load_all(self):
async with self._registry_lock:
await self._sync_with_disk()
# start all once
for file in os.listdir(SESSIONS_DIR):
if not file.endswith("_data.json"):
continue
phone = file.replace("_data.json", "")
if phone in self.clients:
continue
client = self._create_client(phone)
self.clients[phone] = ManagedClient(phone, client)
await self._start_all()
# ==================================================
# START ALL CLIENTS
# REGISTER NEW SESSION (🔥 مهم)
# ==================================================
async def _start_all(self):
for phone, managed in self.clients.items():
try:
await managed.start()
print(f"[POOL] Started → {phone}")
except Exception as e:
print(f"[POOL] Failed to start {phone}: {e}")
async def register_session(self, phone: str):
async with self._registry_lock:
if phone in self.clients:
return
client = self._create_client(phone)
managed = ManagedClient(phone, client)
self.clients[phone] = managed
await managed.start()
print(f"[POOL] Registered → {phone}")
# ==================================================
# SYNC WITH DISK (SAFE)
# REMOVE SESSION
# ==================================================
async def _sync_with_disk(self):
async def remove_session(self, phone: str):
disk_phones = set()
async with self._registry_lock:
for file in os.listdir(SESSIONS_DIR):
if file.endswith("_data.json"):
phone = file.replace("_data.json", "")
disk_phones.add(phone)
managed = self.clients.pop(phone, None)
current_phones = set(self.clients.keys())
if managed:
await managed.stop()
print(f"[POOL] Removed → {phone}")
# ---- ADD NEW ----
for phone in disk_phones - current_phones:
client = self._create_client(phone)
self.clients[phone] = ManagedClient(phone, client)
print(f"[WATCHER] Added → {phone}")
# ==================================================
# START ALL
# ==================================================
# ---- REMOVE ----
for phone in current_phones - disk_phones:
managed = self.clients.pop(phone)
async def _start_all(self):
for phone, managed in self.clients.items():
try:
await managed.stop()
except:
pass
print(f"[WATCHER] Removed → {phone}")
await managed.start()
print(f"[POOL] Started → {phone}")
except Exception as e:
print(f"[POOL] Failed → {phone}: {e}")

@@ -134,23 +147,7 @@ # ==================================================

lang_code=device["lang_code"],
workdir=SESSIONS_DIR
workdir=SESSIONS_DIR,
workers=1 # جلوگیری از race داخلی
)
# ==================================================
# WATCHER (RACE SAFE)
# ==================================================
async def start_watcher(self):
if self._watch_task:
return
async def watcher():
async for _ in awatch(SESSIONS_DIR):
async with self._registry_lock:
await self._sync_with_disk()
self._watch_task = asyncio.create_task(watcher())
print("🔥 Session watcher started")
# ==================================================
# RUN ACTION SAFELY

@@ -215,3 +212,3 @@ # ==================================================

# ==================================================
# SAFE LOGIN LOCK (NO SQLITE LOCK)
# SAFE LOGIN LOCK
# ==================================================

@@ -223,2 +220,10 @@

def release_pool_lock(self):
self._registry_lock.release()
if self._registry_lock.locked():
self._registry_lock.release()
# ==========================================================
# 🔥 GLOBAL RUNNER INSTANCE (همین لازم داشتی)
# ==========================================================
runner = MultiSessionRunner()