Files
thon/thon/process.py
trojvn 38078b36b3 (thon): add structured logging and improve error handling
Add logging infrastructure across the codebase with a consistent logger named "thon" and replace print statements with appropriate log levels (debug, info, warning, error). Improve error handling in the session processing pipeline by:

- Adding proper error message tracking in Thon.__aiter__
- Using shutil.move instead of os.rename for better cross-platform compatibility
- Enhancing the move utility function with better error handling and retry logic
- Adding debug logging in Converter for session conversion tracking
- Centralizing logging configuration in the main module

This change improves debugging capabilities and provides better visibility into the application's operation while maintaining the same functionality.
2026-04-02 23:06:21 +03:00

197 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import contextlib
import logging
from datetime import datetime, timedelta, timezone
from random import randint
from typing import Self # ty:ignore[unresolved-import]
from telethon import TelegramClient
from telethon.tl.functions.account import UpdateStatusRequest
from telethon.tl.functions.help import GetCountriesListRequest, GetNearestDcRequest
from telethon.tl.functions.langpack import GetLangPackRequest
from telethon.tl.types import (
InputPeerUser,
JsonNumber,
JsonObject,
JsonObjectValue,
JsonString,
User,
)
from thon.data import Data
from thon.exceptions import ThonBannedError
from thon.models.misc import ThonSearchCodeOptions
from thon.models.options import ThonOptions
from thon.models.session import Session
from thon.utils import extract_verification_code
logger = logging.getLogger("thon")
API_PACKS = {
4: "android",
5: "android",
6: "android",
8: "ios",
9: "macos",
2834: "macos",
2040: "tdesktop",
10840: "ios",
17349: "tdesktop",
21724: "android",
16623: "android",
2496: "",
}
class ProcessThon(Data):
def __init__(self, session: Session, options: ThonOptions):
self.__item = session.item
self.__retries = options.retries
self.__timeout = options.timeout
self._async_check_timeout = options.check_timeout
super().__init__(session.json_data)
self.__client = self.__get_client()
self.me: User | InputPeerUser | None = None
@property
def client(self) -> TelegramClient:
return self.__client
def __get_client(self) -> TelegramClient:
__session = str(self.__item) if self.__item else self.string_session
proxy = self.proxy
logger.debug(f"{self.session_file} | {proxy}")
client = TelegramClient(
session=__session,
api_id=self.app_id,
api_hash=self.app_hash,
device_model=self.device,
app_version=self.app_version,
system_lang_code=self.system_lang_code,
lang_code=self.lang_pack,
connection_retries=self.__retries,
request_retries=self.__retries,
proxy=proxy,
timeout=self.__timeout,
)
installer = JsonObjectValue("installer", JsonString("com.android.vending"))
if self.app_id in (1, 8):
installer = JsonObjectValue("installer", JsonString("com.apple.AppStore"))
package = JsonObjectValue("package_id", JsonString("org.telegram.messenger"))
if self.app_id in (1, 8):
package = JsonObjectValue("package_id", JsonString("ph.telegra.Telegraph"))
perf_cat = JsonObjectValue("perf_cat", JsonNumber(2))
tz_offset = JsonObjectValue("tz_offset", JsonNumber(self.tz_offset))
if self.tz_offset:
# noinspection PyProtectedMember
client._init_request.params = JsonObject([tz_offset])
if self.app_id in (4, 6):
_list = [installer, package, perf_cat]
if self.tz_offset:
_list.append(tz_offset)
# noinspection PyProtectedMember
client._init_request.params = JsonObject(_list)
# noinspection PyProtectedMember
client._init_request.lang_pack = API_PACKS.get(self.app_id, "android")
return client
async def get_additional_data(self):
lang_pack = API_PACKS.get(self.app_id, "")
with contextlib.suppress(Exception):
await self.client(GetLangPackRequest(lang_pack, self.lang_pack))
with contextlib.suppress(Exception):
await self.client(GetNearestDcRequest())
with contextlib.suppress(Exception):
await self.client(GetCountriesListRequest(self.lang_pack, 0))
async def __check(self) -> str:
try:
await self.client.connect()
if not await self.client.is_user_authorized():
return f"{self.session_file} | Забанен / Banned"
await self.get_additional_data()
with contextlib.suppress(Exception):
await self.client(UpdateStatusRequest(offline=False))
self.me = await self.client.get_me()
return "OK"
except ConnectionError:
await self.disconnect()
return f"{self.session_file} | Ошибка подключения / ConnectionError"
except ThonBannedError:
await self.disconnect()
return f"{self.session_file} | Забанен / Banned"
except Exception as e:
await self.disconnect()
logger.exception(e)
return f"{self.session_file} | Ошибка авторизации / AuthorizationError: {e}"
async def check(self) -> str:
if not self._async_check_timeout:
return await self.__check()
try:
return await asyncio.wait_for(self.__check(), self._async_check_timeout)
except asyncio.TimeoutError:
return f"{self.session_file} | Таймаут / Timeout"
@property
def phone(self) -> str:
if not self.me:
return ""
if isinstance(self.me, User):
return self.me.phone or ""
return ""
async def search_code(self, options: ThonSearchCodeOptions | None = None) -> str:
options = options or ThonSearchCodeOptions()
end_time = datetime.now() + timedelta(seconds=options.wait_time)
while datetime.now() < end_time:
async for m in self.client.iter_messages(
entity=options.entity, limit=options.limit
):
if not m.date:
continue
cutoff_date = datetime.now(tz=timezone.utc) - timedelta(
seconds=options.date_delta
)
if m.date < cutoff_date:
continue
if code := extract_verification_code(m.text, options.regexp):
return code
await asyncio.sleep(randint(*options.sleep_time))
return ""
async def disconnect(self):
"""Полное отключение клиента с очисткой ресурсов"""
# Сначала переводим в оффлайн (если есть)
with contextlib.suppress(Exception):
await self.client(UpdateStatusRequest(offline=True))
# Затем отключаем соединение (если есть)
with contextlib.suppress(Exception):
await self.client.disconnect()
# Явно удаляем client из памяти
with contextlib.suppress(Exception):
del self.__client
async def __aenter__(self) -> Self | str:
r = await self.check()
logger.info(f"{self.session_file} | {r}")
if r != "OK":
await self.disconnect()
return r
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.disconnect()