reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
638
research-what-we-did/deskgram_reverse.py
Normal file
638
research-what-we-did/deskgram_reverse.py
Normal file
@@ -0,0 +1,638 @@
|
||||
"""
|
||||
Deskgram 2 — реконструкция функций по параметрам из веб-превью.
|
||||
Telethon-based. Все параметры взяты из интерфейса модулей.
|
||||
"""
|
||||
import asyncio, random, json, os, re, time, csv
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from telethon import TelegramClient, functions, types, errors
|
||||
from telethon.tl.types import InputPeerUser, InputPeerChat, InputPeerChannel
|
||||
from telethon.tl.functions.messages import GetDialogsRequest
|
||||
from telethon.tl.functions.channels import InviteToChannelRequest, JoinChannelRequest
|
||||
from telethon.tl.functions.account import UpdateProfileRequest, UpdateUsernameRequest
|
||||
from telethon.tl.functions.photos import UploadProfilePhotoRequest
|
||||
from telethon.sessions import StringSession
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Панель аккаунтов
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def account_check(client: TelegramClient):
|
||||
"""Проверка аккаунта: статус, спамблок, премиум"""
|
||||
me = await client.get_me()
|
||||
try:
|
||||
await client.send_message('SpamBot', '/start')
|
||||
await asyncio.sleep(2)
|
||||
msgs = await client.get_messages('SpamBot', limit=1)
|
||||
spam_blocked = 'ограничен' in (msgs[0].message or '').lower()
|
||||
except:
|
||||
spam_blocked = None
|
||||
return {
|
||||
'id': me.id, 'username': me.username, 'phone': me.phone,
|
||||
'premium': getattr(me, 'premium', False),
|
||||
'spam_blocked': spam_blocked,
|
||||
'last_seen': me.status.was_online if hasattr(me.status, 'was_online') else None
|
||||
}
|
||||
|
||||
async def account_edit_profile(client: TelegramClient, *,
|
||||
first_name=None, last_name=None, bio=None, username=None, photo_path=None):
|
||||
"""Редактирование профиля аккаунта"""
|
||||
if first_name or last_name:
|
||||
await client(UpdateProfileRequest(
|
||||
first_name=first_name, last_name=last_name, about=bio))
|
||||
if username:
|
||||
await client(UpdateUsernameRequest(username))
|
||||
if photo_path:
|
||||
await client(UploadProfilePhotoRequest(
|
||||
await client.upload_file(photo_path)))
|
||||
|
||||
async def account_set_2fa(client: TelegramClient, password: str, hint: str = ''):
|
||||
"""Установка 2FA"""
|
||||
await client.edit_2fa(new_password=password, hint=hint)
|
||||
|
||||
async def account_get_sessions(client: TelegramClient):
|
||||
"""Получить активные сессии"""
|
||||
return await client(functions.account.GetAuthorizationsRequest())
|
||||
|
||||
async def account_terminate_sessions(client: TelegramClient):
|
||||
"""Сбросить все сессии кроме текущей"""
|
||||
await client(functions.auth.ResetAuthorizationsRequest())
|
||||
|
||||
async def account_privacy(client: TelegramClient, *,
|
||||
hide_phone=True, hide_lastseen=True, hide_forwarded=True):
|
||||
"""Настройки приватности"""
|
||||
rules = []
|
||||
if hide_phone:
|
||||
rules.append(types.InputPrivacyValueDisallowAll())
|
||||
if hide_lastseen:
|
||||
pass # аналогично через account.setPrivacy
|
||||
await client(functions.account.SetPrivacyRequest(
|
||||
key=types.InputPrivacyKeyPhoneNumber(), rules=rules))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Рассылка в ЛС
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@dataclass
|
||||
class SpamPMConfig:
|
||||
threads: int = 3
|
||||
msg_per_account: int = 50
|
||||
delay_min: float = 30.0
|
||||
delay_max: float = 90.0
|
||||
silent: bool = False
|
||||
ttl_seconds: Optional[int] = None
|
||||
pin_message: bool = False
|
||||
delete_sender: bool = False
|
||||
block_after: bool = False
|
||||
add_to_blacklist: bool = False
|
||||
remove_links_preview: bool = False
|
||||
rotate_on_flood: bool = True
|
||||
check_spambot: bool = True
|
||||
use_autoresponder: bool = False
|
||||
collect_incoming: bool = False
|
||||
thread_start_delay: tuple = (5, 30)
|
||||
|
||||
async def spam_pm(client: TelegramClient, users: list, message: str, cfg: SpamPMConfig):
|
||||
"""Рассылка в ЛС с настройками из интерфейса"""
|
||||
sent = 0
|
||||
|
||||
for user in users:
|
||||
if sent >= cfg.msg_per_account:
|
||||
break
|
||||
try:
|
||||
entity = await client.get_input_entity(user)
|
||||
await client.send_message(
|
||||
entity, message,
|
||||
silent=cfg.silent,
|
||||
background=None
|
||||
)
|
||||
sent += 1
|
||||
|
||||
if cfg.block_after:
|
||||
await client(functions.contacts.BlockRequest(entity))
|
||||
|
||||
delay = random.uniform(cfg.delay_min, cfg.delay_max)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
except errors.FloodWaitError as e:
|
||||
if cfg.rotate_on_flood:
|
||||
return False # сигнал сменить аккаунт
|
||||
await asyncio.sleep(e.seconds)
|
||||
except errors.PeerFloodError:
|
||||
if cfg.check_spambot:
|
||||
await client.send_message('SpamBot', '/start')
|
||||
return False
|
||||
except Exception:
|
||||
continue
|
||||
return True
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Нейрочаттинг
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@dataclass
|
||||
class NeuroChatConfig:
|
||||
mode: str = 'keywords' # keywords | trigger | smart | promoter
|
||||
chat_source: str = 'subscriptions' # subscriptions | list
|
||||
chat_list: list = field(default_factory=list)
|
||||
replies_per_account: int = 30
|
||||
context_messages: int = 5
|
||||
delay_before_reply: tuple = (5, 30)
|
||||
pause_between_replies: tuple = (60, 300)
|
||||
max_replies_per_chat: int = 10
|
||||
chat_cooldown_min: int = 30
|
||||
trigger_words: list = field(default_factory=list)
|
||||
whole_word: bool = True
|
||||
case_sensitive: bool = False
|
||||
ignore_bots: bool = True
|
||||
ignore_system: bool = True
|
||||
min_message_length: int = 1
|
||||
leave_if_cant_write: bool = False
|
||||
auto_leave_spam: bool = False
|
||||
respond_to_our_replies: bool = False
|
||||
check_spambot_on_flood: bool = True
|
||||
|
||||
async def neurochatting(client: TelegramClient, ai_generate, cfg: NeuroChatConfig):
|
||||
"""Нейрочаттинг: AI отвечает на сообщения в чатах"""
|
||||
|
||||
if cfg.chat_source == 'subscriptions':
|
||||
dialogs = await client.get_dialogs()
|
||||
chats = [d for d in dialogs if d.is_group]
|
||||
else:
|
||||
chats = cfg.chat_list
|
||||
|
||||
chat_stats = {}
|
||||
for chat in chats:
|
||||
if chat_stats.get(chat.id, 0) >= cfg.max_replies_per_chat:
|
||||
continue
|
||||
|
||||
try:
|
||||
messages = await client.get_messages(chat, limit=50)
|
||||
except errors.ChatWriteForbiddenError:
|
||||
if cfg.leave_if_cant_write:
|
||||
await client.delete_dialog(chat)
|
||||
continue
|
||||
|
||||
for msg in messages:
|
||||
if not msg.message:
|
||||
continue
|
||||
if cfg.ignore_bots and msg.sender and getattr(msg.sender, 'bot', False):
|
||||
continue
|
||||
if cfg.ignore_system and getattr(msg, 'action', None):
|
||||
continue
|
||||
if len(msg.message or '') < cfg.min_message_length:
|
||||
continue
|
||||
|
||||
# Проверка триггеров
|
||||
should_reply = False
|
||||
text = msg.message or ''
|
||||
|
||||
if cfg.mode == 'keywords':
|
||||
for word in cfg.trigger_words:
|
||||
if cfg.whole_word:
|
||||
pattern = rf'\b{re.escape(word)}\b'
|
||||
else:
|
||||
pattern = re.escape(word)
|
||||
flags = 0 if cfg.case_sensitive else re.IGNORECASE
|
||||
if re.search(pattern, text, flags):
|
||||
should_reply = True
|
||||
break
|
||||
|
||||
elif cfg.mode == 'trigger':
|
||||
should_reply = True # AI решает
|
||||
|
||||
elif cfg.mode == 'smart':
|
||||
should_reply = True
|
||||
|
||||
elif cfg.mode == 'promoter':
|
||||
should_reply = True
|
||||
|
||||
if should_reply:
|
||||
# Контекст
|
||||
context = await client.get_messages(
|
||||
chat, limit=cfg.context_messages, offset_id=msg.id)
|
||||
context_text = '\n'.join(
|
||||
f"[{m.sender_id}]: {m.message}" for m in reversed(context) if m.message)
|
||||
|
||||
# AI генерация
|
||||
reply = await ai_generate(context_text, msg.message)
|
||||
|
||||
delay = random.uniform(*cfg.delay_before_reply)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
try:
|
||||
await client.send_message(chat, reply, reply_to=msg.id)
|
||||
chat_stats[chat.id] = chat_stats.get(chat.id, 0) + 1
|
||||
except errors.PeerFloodError:
|
||||
if cfg.check_spambot_on_flood:
|
||||
await client.send_message('SpamBot', '/start')
|
||||
return chat_stats
|
||||
|
||||
pause = random.uniform(*cfg.pause_between_replies)
|
||||
await asyncio.sleep(pause)
|
||||
|
||||
return chat_stats
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Инвайтинг
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def invite_users(client: TelegramClient, chat, users: list,
|
||||
per_account: int = 50, delay: tuple = (60, 180), rotate_on_flood: bool = True):
|
||||
"""Массовый инвайт пользователей в канал/группу"""
|
||||
invited = 0
|
||||
for user in users:
|
||||
if invited >= per_account:
|
||||
break
|
||||
try:
|
||||
entity = await client.get_input_entity(user)
|
||||
await client(InviteToChannelRequest(chat, [entity]))
|
||||
invited += 1
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
except errors.FloodWaitError as e:
|
||||
if rotate_on_flood:
|
||||
return invited
|
||||
await asyncio.sleep(e.seconds)
|
||||
except errors.UserPrivacyRestrictedError:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
return invited
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Парсер аудитории
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def parse_chat_members(client: TelegramClient, chat,
|
||||
limit: int = 50000, active_only: bool = False) -> list[dict]:
|
||||
"""Сбор участников чата/канала"""
|
||||
users = []
|
||||
try:
|
||||
async for member in client.iter_participants(chat, limit=limit):
|
||||
if active_only and member.status:
|
||||
if hasattr(member.status, 'was_online'):
|
||||
if (datetime.now() - member.status.was_online).days > 30:
|
||||
continue
|
||||
users.append({
|
||||
'id': member.id,
|
||||
'username': member.username,
|
||||
'first_name': member.first_name,
|
||||
'last_name': member.last_name,
|
||||
'phone': getattr(member, 'phone', None),
|
||||
'premium': getattr(member, 'premium', False),
|
||||
'bot': member.bot,
|
||||
'was_online': str(getattr(member.status, 'was_online', None))
|
||||
})
|
||||
except errors.ChatAdminRequiredError:
|
||||
pass
|
||||
return users
|
||||
|
||||
async def parse_comments(client: TelegramClient, channel, post_limit: int = 100):
|
||||
"""Парсер комментаторов постов"""
|
||||
commenters = {}
|
||||
messages = await client.get_messages(channel, limit=post_limit)
|
||||
for msg in messages:
|
||||
if msg.replies and msg.replies.replies > 0:
|
||||
try:
|
||||
async for comment in client.iter_messages(
|
||||
channel, reply_to=msg.id, limit=100):
|
||||
if comment.sender_id and comment.sender_id not in commenters:
|
||||
commenters[comment.sender_id] = {
|
||||
'user_id': comment.sender_id,
|
||||
'post_id': msg.id,
|
||||
'count': 1
|
||||
}
|
||||
elif comment.sender_id:
|
||||
commenters[comment.sender_id]['count'] += 1
|
||||
except:
|
||||
continue
|
||||
return sorted(commenters.values(), key=lambda x: x['count'], reverse=True)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Нейрокомментинг
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def neuro_commenting(client: TelegramClient, channels: list,
|
||||
ai_generate, posts_per_channel: int = 10,
|
||||
delay: tuple = (30, 120), mode: str = 'firstnah'):
|
||||
"""Автокомментирование новых постов с AI"""
|
||||
results = []
|
||||
for channel in channels:
|
||||
try:
|
||||
# Первонах (firstnah) — комментарий первым
|
||||
messages = await client.get_messages(channel, limit=posts_per_channel)
|
||||
for msg in messages:
|
||||
if not msg.message:
|
||||
continue
|
||||
try:
|
||||
existing = await client.get_messages(
|
||||
channel, reply_to=msg.id, limit=5)
|
||||
our_comments = [c for c in existing
|
||||
if c.sender_id == (await client.get_me()).id]
|
||||
if mode == 'firstnah' and our_comments:
|
||||
continue # уже комментили
|
||||
except:
|
||||
pass
|
||||
|
||||
comment = await ai_generate(
|
||||
f"POST: {msg.message[:500]}",
|
||||
style='positive' # из настроек модуля
|
||||
)
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
await client.send_message(channel, comment, reply_to=msg.id)
|
||||
results.append({'channel': channel, 'post_id': msg.id, 'comment': comment[:100]})
|
||||
except Exception as e:
|
||||
results.append({'channel': channel, 'error': str(e)})
|
||||
return results
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Рассылка в группы
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def spam_groups(client: TelegramClient, chats: list, message: str,
|
||||
ai_rewrite=False, ai_generate=None,
|
||||
per_account: int = 20, delay: tuple = (60, 180)):
|
||||
"""Рассылка в группы с AI-рерайтом"""
|
||||
sent = 0
|
||||
for chat in chats:
|
||||
if sent >= per_account:
|
||||
break
|
||||
try:
|
||||
text = message
|
||||
if ai_rewrite and ai_generate:
|
||||
text = await ai_generate(f"Rewrite this naturally: {message}")
|
||||
|
||||
await client.send_message(chat, text)
|
||||
sent += 1
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
except errors.FloodWaitError as e:
|
||||
await asyncio.sleep(e.seconds)
|
||||
except Exception:
|
||||
continue
|
||||
return sent
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Автоответчик с ветвлением
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@dataclass
|
||||
class AutoReplyRule:
|
||||
trigger_words: list
|
||||
response: str
|
||||
whole_word: bool = True
|
||||
case_sensitive: bool = False
|
||||
collect_usernames: bool = False
|
||||
|
||||
async def auto_responder(client: TelegramClient, rules: list[AutoReplyRule],
|
||||
collect_usernames: bool = False, blacklist: set = set()):
|
||||
"""Автоответчик на входящие сообщения"""
|
||||
collected = []
|
||||
while True:
|
||||
try:
|
||||
dialogs = await client.get_dialogs()
|
||||
for dialog in dialogs:
|
||||
if dialog.unread_count > 0 and dialog.id not in blacklist:
|
||||
msgs = await client.get_messages(dialog.id, limit=dialog.unread_count)
|
||||
for msg in msgs:
|
||||
if msg.out:
|
||||
continue
|
||||
if collect_usernames and msg.sender:
|
||||
collected.append({
|
||||
'user_id': msg.sender_id,
|
||||
'username': getattr(msg.sender, 'username', None),
|
||||
'message': msg.message[:200]
|
||||
})
|
||||
|
||||
text = msg.message or ''
|
||||
for rule in rules:
|
||||
for word in rule.trigger_words:
|
||||
if rule.whole_word:
|
||||
pattern = rf'\b{re.escape(word)}\b'
|
||||
else:
|
||||
pattern = re.escape(word)
|
||||
flags = 0 if rule.case_sensitive else re.IGNORECASE
|
||||
if re.search(pattern, text, flags):
|
||||
await client.send_message(dialog.id, rule.response)
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(5)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Массовые подписки + лайки + просмотры
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def mass_join(client: TelegramClient, channels: list,
|
||||
per_account: int = 50, delay: tuple = (30, 90)):
|
||||
"""Массовое вступление в каналы/группы"""
|
||||
joined = 0
|
||||
for ch in channels:
|
||||
if joined >= per_account:
|
||||
break
|
||||
try:
|
||||
await client(JoinChannelRequest(ch))
|
||||
joined += 1
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
except errors.FloodWaitError as e:
|
||||
await asyncio.sleep(e.seconds)
|
||||
except Exception:
|
||||
continue
|
||||
return joined
|
||||
|
||||
async def mass_react(client: TelegramClient, channel, post_ids: list,
|
||||
reaction='👍', per_account: int = 100, delay: tuple = (5, 15)):
|
||||
"""Массовые реакции на посты"""
|
||||
for pid in post_ids[:per_account]:
|
||||
try:
|
||||
await client.send_reaction(channel, pid, reaction)
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
except errors.FloodWaitError as e:
|
||||
await asyncio.sleep(e.seconds)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Чекер юзернеймов
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def check_usernames(client: TelegramClient, usernames: list) -> dict:
|
||||
"""Проверка доступности юзернеймов"""
|
||||
results = {'free': [], 'taken': [], 'errors': []}
|
||||
for username in usernames:
|
||||
try:
|
||||
entity = await client.get_entity(username)
|
||||
results['taken'].append(username)
|
||||
except errors.UsernameNotOccupiedError:
|
||||
results['free'].append(username)
|
||||
except Exception:
|
||||
results['errors'].append(username)
|
||||
await asyncio.sleep(0.5)
|
||||
return results
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Масслукинг (просмотр Stories)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def mass_story_view(client: TelegramClient, targets: list,
|
||||
per_account: int = 200, delay: tuple = (3, 10)):
|
||||
"""Массовый просмотр Telegram Stories"""
|
||||
viewed = 0
|
||||
for target in targets:
|
||||
if viewed >= per_account:
|
||||
break
|
||||
try:
|
||||
entity = await client.get_input_entity(target)
|
||||
stories = await client(functions.stories.GetPeerStoriesRequest(
|
||||
peer=entity))
|
||||
if stories.stories:
|
||||
await client(functions.stories.ReadStoriesRequest(
|
||||
peer=entity,
|
||||
max_id=max(s.id for s in stories.stories.stories)
|
||||
))
|
||||
viewed += 1
|
||||
await asyncio.sleep(random.uniform(*delay))
|
||||
except Exception:
|
||||
continue
|
||||
return viewed
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Прогрев аккаунтов
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def warmup_account(client: TelegramClient, days: int = 7,
|
||||
daily_joins: int = 5, daily_messages: int = 3, daily_reactions: int = 10):
|
||||
"""Прогрев аккаунта перед работой"""
|
||||
for day in range(days):
|
||||
for _ in range(random.randint(1, daily_joins)):
|
||||
try:
|
||||
dialogs = await client.get_dialogs()
|
||||
groups = [d for d in dialogs if d.is_group]
|
||||
if groups:
|
||||
await client(JoinChannelRequest(random.choice(groups)))
|
||||
except:
|
||||
pass
|
||||
await asyncio.sleep(random.uniform(300, 900))
|
||||
|
||||
for _ in range(random.randint(1, daily_messages)):
|
||||
try:
|
||||
await client.send_message('me', f'Warmup message {time.time()}')
|
||||
except:
|
||||
pass
|
||||
await asyncio.sleep(random.uniform(600, 1800))
|
||||
|
||||
return True
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Постинг по каналам
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def channel_posting(client: TelegramClient, channels: list,
|
||||
posts: list[dict], schedule: bool = False):
|
||||
"""Публикация постов в каналы через бота-админа"""
|
||||
for channel in channels:
|
||||
for post in posts:
|
||||
try:
|
||||
entity = await client.get_entity(channel)
|
||||
if post.get('photo'):
|
||||
await client.send_file(entity, post['photo'],
|
||||
caption=post.get('caption', ''))
|
||||
else:
|
||||
await client.send_message(entity, post.get('text', ''))
|
||||
except Exception:
|
||||
continue
|
||||
await asyncio.sleep(random.uniform(10, 30))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Снятие спамблока
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def remove_spamblock(client: TelegramClient) -> bool:
|
||||
"""Снятие ограничений через SpamBot"""
|
||||
try:
|
||||
await client.send_message('SpamBot', '/start')
|
||||
await asyncio.sleep(2)
|
||||
msgs = await client.get_messages('SpamBot', limit=5)
|
||||
for msg in msgs:
|
||||
if msg.reply_markup and hasattr(msg.reply_markup, 'rows'):
|
||||
for row in msg.reply_markup.rows:
|
||||
for btn in row.buttons:
|
||||
if 'ограничен' in (btn.text or '').lower() or \
|
||||
'это я' in (btn.text or '').lower() or \
|
||||
'продолжить' in (btn.text or '').lower():
|
||||
await btn.click()
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Диспетчер задач (оркестратор)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class TaskDispatcher:
|
||||
"""Центральный диспетчер задач"""
|
||||
def __init__(self):
|
||||
self.tasks = {}
|
||||
self.running = set()
|
||||
self.results = {}
|
||||
|
||||
async def run_task(self, task_id: str, coro, accounts: list,
|
||||
proxy_rotate=None):
|
||||
"""Запуск задачи на пуле аккаунтов с ротацией"""
|
||||
self.running.add(task_id)
|
||||
results = []
|
||||
for acc in accounts:
|
||||
client = await self._get_client(acc, proxy_rotate)
|
||||
try:
|
||||
result = await coro(client)
|
||||
results.append({'account': acc, 'result': result})
|
||||
except Exception as e:
|
||||
results.append({'account': acc, 'error': str(e)})
|
||||
finally:
|
||||
await client.disconnect()
|
||||
self.running.discard(task_id)
|
||||
self.results[task_id] = results
|
||||
return results
|
||||
|
||||
async def _get_client(self, account, proxy):
|
||||
"""Создать клиент с прокси"""
|
||||
session = account.get('session')
|
||||
api_id = account.get('api_id', 2040)
|
||||
api_hash = account.get('api_hash', 'b18441a1ff607e10a989891a5462e627')
|
||||
client = TelegramClient(
|
||||
StringSession(session) if session else StringSession(),
|
||||
api_id, api_hash,
|
||||
proxy=proxy
|
||||
)
|
||||
await client.connect()
|
||||
if not await client.is_user_authorized():
|
||||
await client.start(phone=account['phone'])
|
||||
return client
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# МОДУЛЬ: Прокси-менеджер
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async def proxy_check(proxy: dict) -> dict:
|
||||
"""Проверка прокси перед назначением на аккаунт"""
|
||||
import socket
|
||||
try:
|
||||
proto, host, port = proxy['type'], proxy['host'], proxy['port']
|
||||
sock = socket.create_connection((host, port), timeout=5)
|
||||
sock.close()
|
||||
return {'proxy': proxy, 'alive': True, 'latency': None}
|
||||
except Exception as e:
|
||||
return {'proxy': proxy, 'alive': False, 'error': str(e)}
|
||||
|
||||
async def proxy_rotate(proxies: list, accounts: list) -> dict:
|
||||
"""Назначение прокси на аккаунты (round-robin)"""
|
||||
alive = [p for p in proxies if (await proxy_check(p))['alive']]
|
||||
mapping = {}
|
||||
for i, acc in enumerate(accounts):
|
||||
mapping[acc['id']] = alive[i % len(alive)] if alive else None
|
||||
return mapping
|
||||
Reference in New Issue
Block a user