feat(cli): add Worker and main for TDATA check
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from modules.thread import Worker
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with Worker() as worker:
|
||||||
|
await worker.check_all()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s | %(levelname)-7s | %(message)s",
|
||||||
|
datefmt="%H:%M:%S",
|
||||||
|
handlers=[logging.StreamHandler()],
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.getLogger("telethon").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("opentele2").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("Прервано пользователем")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Критическая ошибка: {e}")
|
||||||
|
raise
|
||||||
+51
-69
@@ -14,15 +14,17 @@ from modules.tdata import TDATA
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SessionInvalid(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Unified Result class
|
# Unified Result class
|
||||||
@dataclass
|
@dataclass
|
||||||
class Result:
|
class Result:
|
||||||
valid: bool
|
|
||||||
error: Optional[str] = None
|
|
||||||
|
|
||||||
me: Optional[User] = None
|
me: Optional[User] = None
|
||||||
stars: Optional[int] = None
|
stars: Optional[int] = None
|
||||||
country: Optional[str] = None
|
country: Optional[str] = None
|
||||||
|
|
||||||
contacts: Optional[List[User]] = None
|
contacts: Optional[List[User]] = None
|
||||||
chats: Optional[List[Chat]] = None
|
chats: Optional[List[Chat]] = None
|
||||||
channels: Optional[List[Channel]] = None
|
channels: Optional[List[Channel]] = None
|
||||||
@@ -41,6 +43,7 @@ class Checker:
|
|||||||
self.desktop = None
|
self.desktop = None
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
|
self.__ensure_client()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
@@ -48,110 +51,94 @@ class Checker:
|
|||||||
logger.debug("Отключение клиента Telethon")
|
logger.debug("Отключение клиента Telethon")
|
||||||
try:
|
try:
|
||||||
await self.client.disconnect()
|
await self.client.disconnect()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _ensure_client(self):
|
except Exception as e:
|
||||||
"""Обеспечение инициализации клиента перед операцией"""
|
logger.error(f"Ошибка отключения клиента Telethon: {e}")
|
||||||
|
|
||||||
|
async def __ensure_client(self):
|
||||||
if self.client is None:
|
if self.client is None:
|
||||||
if self.desktop is None:
|
if self.desktop is None:
|
||||||
raise Exception("TDATA не инициализирована")
|
raise Exception("TDATA не инициализирована")
|
||||||
|
|
||||||
logger.debug("Инициализация клиента Telethon из TDATA")
|
logger.debug("Инициализация клиента Telethon из TDATA")
|
||||||
self.client = await self.desktop.convert()
|
self.client = await self.desktop.convert()
|
||||||
|
|
||||||
async def validate(self) -> Result:
|
async def check_stars(self, me: User) -> int:
|
||||||
"""Валидация TDATA"""
|
|
||||||
try:
|
|
||||||
if self.desktop is None:
|
|
||||||
return Result(valid=False, error="TDATA не инициализирована")
|
|
||||||
|
|
||||||
if not await self.desktop.validate():
|
|
||||||
return Result(valid=False, error="TDATA невалидна или пуста")
|
|
||||||
|
|
||||||
await self._ensure_client()
|
|
||||||
me = await self.client.get_me()
|
|
||||||
|
|
||||||
if not me:
|
|
||||||
return Result(
|
|
||||||
valid=False, error="Не удалось получить информацию о пользователе"
|
|
||||||
)
|
|
||||||
|
|
||||||
return Result(valid=True, me=me)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка валидации: {e}")
|
|
||||||
return Result(valid=False, error=str(e))
|
|
||||||
|
|
||||||
async def check_all(self) -> Result:
|
|
||||||
"""Полная проверка аккаунта"""
|
|
||||||
try:
|
|
||||||
# Сначала валидация
|
|
||||||
validation = await self.validate()
|
|
||||||
if not validation.valid:
|
|
||||||
return validation
|
|
||||||
|
|
||||||
me = validation.me
|
|
||||||
logger.debug(f"Пользователь: {me.first_name} (+{me.phone})")
|
|
||||||
|
|
||||||
# Собираем данные с обработкой ошибок
|
|
||||||
stars = None
|
|
||||||
country = None
|
|
||||||
contacts = None
|
|
||||||
chats = None
|
|
||||||
channels = None
|
|
||||||
admin_channels = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stars_status = await self.client(GetStarsStatusRequest(peer=me))
|
stars_status = await self.client(GetStarsStatusRequest(peer=me))
|
||||||
stars = stars_status.balance.amount
|
return stars_status.balance.amount
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось получить баланс звезд: {e}")
|
logger.warning(f"Не удалось получить баланс звезд: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def check_country(self, me: User) -> str:
|
||||||
try:
|
try:
|
||||||
phone_parse = parse(str(f"+{me.phone}"))
|
phone_parse = parse(str(f"+{me.phone}"))
|
||||||
country = geocoder.description_for_number(phone_parse, "ru")
|
return geocoder.description_for_number(phone_parse, "en")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось определить страну: {e}")
|
logger.warning(f"Не удалось определить страну: {e}")
|
||||||
|
return "N/A"
|
||||||
|
|
||||||
|
async def check_contacts(self) -> List[User]:
|
||||||
try:
|
try:
|
||||||
logger.debug("Получение контактов")
|
logger.debug("Получение контактов")
|
||||||
result = await self.client(GetContactsRequest(hash=0))
|
result = await self.client(GetContactsRequest(hash=0))
|
||||||
contacts = result.users
|
return result.users
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось получить контакты: {e}")
|
logger.warning(f"Не удалось получить контакты: {e}")
|
||||||
|
|
||||||
|
async def check_chats(self) -> List[Chat]:
|
||||||
try:
|
try:
|
||||||
logger.debug("Получение всех диалогов")
|
logger.debug("Получение всех диалогов")
|
||||||
chats = []
|
chats = []
|
||||||
async for dialog in self.client.iter_dialogs():
|
async for dialog in self.client.iter_dialogs():
|
||||||
chats.append(dialog.entity)
|
chats.append(dialog.entity)
|
||||||
|
|
||||||
|
return chats
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось получить чаты: {e}")
|
logger.warning(f"Не удалось получить чаты: {e}")
|
||||||
|
|
||||||
if chats:
|
async def check_channels(self, chats: List[Chat]) -> List[Channel]:
|
||||||
try:
|
try:
|
||||||
channels = [chat for chat in chats if isinstance(chat, Channel)]
|
channels = [chat for chat in chats if isinstance(chat, Channel)]
|
||||||
|
return channels
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось отфильтровать каналы: {e}")
|
logger.warning(f"Не удалось отфильтровать каналы: {e}")
|
||||||
|
|
||||||
|
async def check_admin_channels(self, channels: List[Channel]) -> List[Channel]:
|
||||||
try:
|
try:
|
||||||
logger.debug("Проверка прав администратора")
|
logger.debug("Проверка прав администратора")
|
||||||
admin_channels = []
|
admin_channels = []
|
||||||
for chat in chats:
|
|
||||||
if isinstance(chat, Channel):
|
for channel in channels:
|
||||||
try:
|
permissions = await self.client.get_permissions(channel, "me")
|
||||||
permissions = await self.client.get_permissions(
|
|
||||||
chat, "me"
|
|
||||||
)
|
|
||||||
if permissions.is_admin or permissions.is_creator:
|
if permissions.is_admin or permissions.is_creator:
|
||||||
admin_channels.append(chat)
|
admin_channels.append(channel)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось проверить админские права: {e}")
|
logger.warning(f"Не удалось проверить админские права: {e}")
|
||||||
|
|
||||||
|
async def check_all(self) -> Result:
|
||||||
|
try:
|
||||||
|
me = await self.client.get_me()
|
||||||
|
|
||||||
|
# Собираем данные с обработкой ошибок
|
||||||
|
stars = self.check_stars(me=me)
|
||||||
|
country = self.check_country(me=me)
|
||||||
|
|
||||||
|
contacts = self.check_contacts()
|
||||||
|
chats = self.check_chats()
|
||||||
|
channels = [] if chats == [] else self.check_channels(chats=chats)
|
||||||
|
admin_channels = (
|
||||||
|
[] if channels == [] else self.check_admin_channels(channels=channels)
|
||||||
|
)
|
||||||
|
|
||||||
return Result(
|
return Result(
|
||||||
valid=True,
|
|
||||||
me=me,
|
me=me,
|
||||||
stars=stars,
|
stars=stars,
|
||||||
country=country,
|
country=country,
|
||||||
@@ -163,9 +150,4 @@ class Checker:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Критическая ошибка при проверке: {e}")
|
logger.error(f"Критическая ошибка при проверке: {e}")
|
||||||
return Result(valid=False, error=str(e))
|
raise SessionInvalid()
|
||||||
|
|
||||||
|
|
||||||
class MassChecker:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|||||||
+5
-3
@@ -16,10 +16,12 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class TDATA:
|
class TDATA:
|
||||||
def __init__(self, path: str = "tdata"):
|
def __init__(self, path: Path, temp_path: Path):
|
||||||
try:
|
try:
|
||||||
self.path = path
|
self.path = path
|
||||||
if not Path(self.path).exists():
|
self.temp_path = temp_path
|
||||||
|
|
||||||
|
if not self.path.exists():
|
||||||
logger.error(f"Путь '{self.path}' не существует")
|
logger.error(f"Путь '{self.path}' не существует")
|
||||||
raise FileNotFoundError(f"Путь '{self.path}' не существует")
|
raise FileNotFoundError(f"Путь '{self.path}' не существует")
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ class TDATA:
|
|||||||
if self.desktop:
|
if self.desktop:
|
||||||
return self.desktop
|
return self.desktop
|
||||||
|
|
||||||
return TDesktop(self.path, api=self.api)
|
return TDesktop(self.path.absolute(), api=self.api)
|
||||||
|
|
||||||
except TFileNotFound as e:
|
except TFileNotFound as e:
|
||||||
logger.warning(f"Файлы TDATA не найдены или неполные: {e}")
|
logger.warning(f"Файлы TDATA не найдены или неполные: {e}")
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from shutil import move
|
||||||
|
from pathlib import Path
|
||||||
|
from countryflag import getflag
|
||||||
|
|
||||||
|
from checker import Result, Checker, SessionInvalid
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Worker:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_path: str = "tdata",
|
||||||
|
output_path: str = "data",
|
||||||
|
temp_path: str = "temp",
|
||||||
|
):
|
||||||
|
self.input_path = Path(input_path).absolute()
|
||||||
|
self.output_path = Path(output_path).absolute()
|
||||||
|
self.temp_path = Path(temp_path).absolute()
|
||||||
|
|
||||||
|
self.valid_path = self.output_path.joinpath("valid")
|
||||||
|
self.invalid_path = self.output_path.joinpath("invalid")
|
||||||
|
|
||||||
|
self.all_path = self.valid_path.joinpath("all.txt")
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
if not self.input_path.exists():
|
||||||
|
self.input_path.mkdir()
|
||||||
|
if not self.output_path.exists():
|
||||||
|
self.output_path.mkdir()
|
||||||
|
|
||||||
|
if not self.temp_path.exists():
|
||||||
|
self.temp_path.mkdir()
|
||||||
|
else:
|
||||||
|
self.__cleanup()
|
||||||
|
|
||||||
|
if not self.valid_path.exists():
|
||||||
|
self.valid_path.mkdir()
|
||||||
|
if not self.invalid_path.exists():
|
||||||
|
self.invalid_path.mkdir()
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.__cleanup()
|
||||||
|
if self.client:
|
||||||
|
logger.debug("Отключение клиента Telethon")
|
||||||
|
try:
|
||||||
|
await self.client.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __cleanup(self):
|
||||||
|
if self.temp_path.exists():
|
||||||
|
deleted_count = 0
|
||||||
|
for file in os.listdir(path=self.temp_path):
|
||||||
|
file_path = self.temp_path.joinpath(str(file))
|
||||||
|
try:
|
||||||
|
if file_path.isfile() and ".gitinclude" not in file_path:
|
||||||
|
file_path.unlink(missing_ok=True)
|
||||||
|
elif file_path.isdir():
|
||||||
|
file_path.rmdir()
|
||||||
|
deleted_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить {file_path}: {e}")
|
||||||
|
|
||||||
|
if deleted_count > 0:
|
||||||
|
logger.debug(f"Удалено {deleted_count} временных файлов")
|
||||||
|
|
||||||
|
def __list_tdata(self):
|
||||||
|
tdatas = []
|
||||||
|
|
||||||
|
if self.input_path.exists():
|
||||||
|
for content in os.listdir(path=self.input_path):
|
||||||
|
joined = self.input_path.joinpath(content)
|
||||||
|
if joined.is_dir():
|
||||||
|
tdatas.append(joined.absolute())
|
||||||
|
|
||||||
|
return tdatas
|
||||||
|
|
||||||
|
def __template(self, result: Result):
|
||||||
|
return (
|
||||||
|
f"👤 [{getflag(countries=result.country)} {result.country}] {result.me.phone} — {result.me.id}\n"
|
||||||
|
f"⭐️ {result.stars}\n"
|
||||||
|
f"💎 Premium: {'true' if result.me.premium else 'false'}\n"
|
||||||
|
f"👥 {len(result.chats)}/{len(result.contacts)}\n"
|
||||||
|
f"🍀 {len(result.admin_channels)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __append(self, content: str, path: Path):
|
||||||
|
with open(file=path, mode="+a", encoding="utf-8") as file:
|
||||||
|
file.write(content)
|
||||||
|
|
||||||
|
def __write(self, content: str, path: Path):
|
||||||
|
with open(file=path, mode="+w", encoding="utf-8") as file:
|
||||||
|
file.write(content)
|
||||||
|
|
||||||
|
def __save_valid(self, user_id: int, result: Result, tdata: Path):
|
||||||
|
id_path = self.output_path.joinpath(str(user_id))
|
||||||
|
if not id_path.absolute().exists():
|
||||||
|
id_path.mkdir()
|
||||||
|
|
||||||
|
content = self.__template(result=result)
|
||||||
|
id_txt_path = id_path.joinpath(f"{user_id}.txt")
|
||||||
|
|
||||||
|
# Append to all.txt
|
||||||
|
self.__append(content=content, path=self.all_path)
|
||||||
|
|
||||||
|
# Write to id.txt
|
||||||
|
self.__write(content=content, path=id_txt_path)
|
||||||
|
|
||||||
|
tdata_old_path = self.input_path.joinpath(tdata).absolute()
|
||||||
|
|
||||||
|
tdata_relative_id_path = id_path.joinpath(tdata)
|
||||||
|
tdata_new_path = self.valid_path.joinpath(tdata_relative_id_path).absolute()
|
||||||
|
|
||||||
|
# Move tdata
|
||||||
|
move(src=tdata_old_path, dst=tdata_new_path)
|
||||||
|
|
||||||
|
def __save_invalid(self, tdata: Path):
|
||||||
|
tdata_old_path = self.input_path.joinpath(tdata).absolute()
|
||||||
|
tdata_new_path = self.invalid_path.joinpath(tdata).absolute()
|
||||||
|
|
||||||
|
# Move tdata
|
||||||
|
move(src=tdata_old_path, dst=tdata_new_path)
|
||||||
|
|
||||||
|
async def check_one(self, tdata: str):
|
||||||
|
tdata_absolute = Path(tdata).absolute()
|
||||||
|
tdata_relative = Path(tdata).relative_to(self.input_path)
|
||||||
|
|
||||||
|
logger.info(f"Начало проверки TDATA: {tdata_absolute}")
|
||||||
|
try:
|
||||||
|
async with Checker(path=tdata_absolute) as checker:
|
||||||
|
result = await checker.check_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__save_valid(user_id=result.me.id, tdata=tdata_relative)
|
||||||
|
|
||||||
|
except SessionInvalid:
|
||||||
|
self.__save_invalid(tdata=tdata_relative)
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def check_all(self):
|
||||||
|
tdatas = self.__list_tdata()
|
||||||
|
|
||||||
|
for tdata in tdatas:
|
||||||
|
await self.check_one(tdata=tdata)
|
||||||
Reference in New Issue
Block a user