Compare commits

..

10 Commits

Author SHA1 Message Date
9c14541b1a fixed 2025-05-20 17:41:38 +03:00
a68896dd4e debug 2025-05-20 17:15:31 +03:00
a56d0c39e8 debug 2025-05-20 13:08:14 +03:00
325a65a92e as prop 2025-05-19 21:14:52 +03:00
e1c96bc045 get phone 2025-05-19 21:14:25 +03:00
bdb3c6ddf4 timeout by asyncio 2025-05-19 19:56:23 +03:00
dc4c6d54f1 fixed 2025-05-19 15:00:21 +03:00
b6b37753af with proxy support 2025-05-19 10:30:14 +03:00
f9dafb795d fixed 2025-05-18 13:58:13 +03:00
50b0c16048 refactor 2025-05-18 13:30:24 +03:00
9 changed files with 203 additions and 131 deletions

View File

@ -1,9 +1,10 @@
from .async_base_session import AsyncBaseSession
from .base_data import BaseData
from .base_session import BaseSession
from .base_thon import BaseData, BaseThon
from .base_thon import BaseThon
from .json_async_converter import JsonAsyncConverter
from .json_converter import JsonConverter
from .models import BaseThonOptions, BaseThonSearchCodeOptions
from .models import ThonOptions, ThonSearchCodeOptions
__all__ = [
"BaseThon",
@ -12,6 +13,6 @@ __all__ = [
"BaseData",
"JsonAsyncConverter",
"AsyncBaseSession",
"BaseThonOptions",
"BaseThonSearchCodeOptions",
"ThonOptions",
"ThonSearchCodeOptions",
]

View File

@ -7,8 +7,11 @@ from jsoner.async_ import json_read
class AsyncBaseSession:
def __init__(self, base_dir: Path, errors_dir: Path, banned_dir: Path):
self.base_dir = base_dir
self.base_dir.mkdir(exist_ok=True)
self.errors_dir = errors_dir
self.errors_dir.mkdir(exist_ok=True)
self.banned_dir = banned_dir
self.banned_dir.mkdir(exist_ok=True)
self.json_errors: set[Path] = set()
async def __aiter__(self) -> AsyncGenerator[tuple[Path, Path, dict], None]:

110
basethon/base_data.py Normal file
View File

@ -0,0 +1,110 @@
from telethon.sessions import StringSession
class BaseData:
def __init__(self, json_data: dict, raise_error: bool):
self.__json_data, self.__raise_error = json_data, raise_error
@property
def json_data(self) -> dict:
return self.__json_data
def json_data_edit(self, key: str, value: str | int | bool | None):
self.__json_data[key] = value
@property
def session_file(self) -> str:
if not (session_file := self.json_data.get("session_file")):
if self.__raise_error:
raise ValueError("ERROR_SESSION_FILE:BASE_THON")
return ""
return session_file
@property
def string_session(self) -> StringSession:
if not (string_session := self.json_data.get("string_session")):
if self.__raise_error:
raise ValueError("ERROR_STRING_SESSION:BASE_THON")
return StringSession()
return StringSession(string_session)
@property
def app_id(self) -> int:
"""Api Id"""
if api_id := self.json_data.get("api_id"):
return api_id
if not (app_id := self.json_data.get("app_id")):
raise ValueError("ERROR_APP_ID:BASE_THON")
return app_id
@property
def app_hash(self) -> str:
"""Api Hash"""
if api_hash := self.json_data.get("api_hash"):
return api_hash
if not (app_hash := self.json_data.get("app_hash")):
raise ValueError("ERROR_APP_HASH:BASE_THON")
return app_hash
@property
def device(self) -> str:
"""Device Model"""
if device_model := self.json_data.get("device_model"):
return device_model
if not (device := self.json_data.get("device")):
raise ValueError("ERROR_DEVICE:BASE_THON")
return device
@property
def sdk(self) -> str:
"""System Version"""
if system_version := self.json_data.get("system_version"):
return system_version
if not (sdk := self.json_data.get("sdk")):
raise ValueError("ERROR_SDK:BASE_THON")
return sdk
@property
def app_version(self) -> str:
"""App Version"""
if not (app_version := self.json_data.get("app_version")):
raise ValueError("ERROR_APP_VERSION:BASE_THON")
return app_version
@property
def lang_pack(self) -> str:
"""Lang Pack"""
if lang_code := self.json_data.get("lang_code"):
return lang_code
return self.json_data.get("lang_pack", "en")
@property
def system_lang_code(self) -> str:
"""System Lang Code"""
if system_lang_code := self.json_data.get("system_lang_code"):
return system_lang_code
return self.json_data.get("system_lang_pack", "en-us")
@property
def twostep(self) -> str | None:
"""2FA"""
if password := self.json_data.get("password"):
return password
if twofa := self.json_data.get("twoFA"):
return twofa
if twostep := self.json_data.get("twostep"):
return twostep
@property
def proxy(self) -> dict | tuple:
if not (proxy := self.json_data.get("proxy")):
if self.__raise_error:
raise ValueError("ERROR_PROXY:BASE_THON")
return {}
return proxy
@property
def tz_offset(self) -> int:
if tz_offset := self.json_data.get("tz_offset", 0):
return tz_offset
return 0

View File

@ -7,135 +7,35 @@ from random import randint
from typing import Self
from telethon import TelegramClient
from telethon.sessions import StringSession
from telethon.tl.functions.account import UpdateStatusRequest
from telethon.tl.functions.help import GetCountriesListRequest, GetNearestDcRequest
from telethon.tl.functions.langpack import GetLangPackRequest
from telethon.types import JsonNumber, JsonObject, JsonObjectValue, JsonString
from telethon.types import (
InputPeerUser,
JsonNumber,
JsonObject,
JsonObjectValue,
JsonString,
User,
)
from basethon.models import BaseThonOptions, BaseThonSearchCodeOptions
from basethon.base_data import BaseData
from basethon.models import ThonOptions, ThonSearchCodeOptions
from .constants import API_PACKS
from .exceptions import ThonBannedError
class BaseData:
def __init__(self, json_data: dict, raise_error: bool):
self.__json_data, self.__raise_error = json_data, raise_error
@property
def json_data(self) -> dict:
return self.__json_data
def json_data_edit(self, key: str, value: str | int | bool | None):
self.__json_data[key] = value
@property
def session_file(self) -> str:
if not (session_file := self.json_data.get("session_file")):
if self.__raise_error:
raise ValueError("ERROR_SESSION_FILE:BASE_THON")
return ""
return session_file
@property
def string_session(self) -> StringSession:
if not (string_session := self.json_data.get("string_session")):
if self.__raise_error:
raise ValueError("ERROR_STRING_SESSION:BASE_THON")
return StringSession()
return StringSession(string_session)
@property
def app_id(self) -> int:
"""Api Id"""
if api_id := self.json_data.get("api_id"):
return api_id
if not (app_id := self.json_data.get("app_id")):
raise ValueError("ERROR_APP_ID:BASE_THON")
return app_id
@property
def app_hash(self) -> str:
"""Api Hash"""
if api_hash := self.json_data.get("api_hash"):
return api_hash
if not (app_hash := self.json_data.get("app_hash")):
raise ValueError("ERROR_APP_HASH:BASE_THON")
return app_hash
@property
def device(self) -> str:
"""Device Model"""
if device_model := self.json_data.get("device_model"):
return device_model
if not (device := self.json_data.get("device")):
raise ValueError("ERROR_DEVICE:BASE_THON")
return device
@property
def sdk(self) -> str:
"""System Version"""
if system_version := self.json_data.get("system_version"):
return system_version
if not (sdk := self.json_data.get("sdk")):
raise ValueError("ERROR_SDK:BASE_THON")
return sdk
@property
def app_version(self) -> str:
"""App Version"""
if not (app_version := self.json_data.get("app_version")):
raise ValueError("ERROR_APP_VERSION:BASE_THON")
return app_version
@property
def lang_pack(self) -> str:
"""Lang Pack"""
if lang_code := self.json_data.get("lang_code"):
return lang_code
return self.json_data.get("lang_pack", "en")
@property
def system_lang_code(self) -> str:
"""System Lang Code"""
if system_lang_code := self.json_data.get("system_lang_code"):
return system_lang_code
return self.json_data.get("system_lang_pack", "en-us")
@property
def twostep(self) -> str | None:
"""2FA"""
if password := self.json_data.get("password"):
return password
if twofa := self.json_data.get("twoFA"):
return twofa
if twostep := self.json_data.get("twostep"):
return twostep
@property
def proxy(self) -> dict | tuple:
if not (proxy := self.json_data.get("proxy")):
if self.__raise_error:
raise ValueError("ERROR_PROXY:BASE_THON")
return {}
return proxy
@property
def tz_offset(self) -> int:
if tz_offset := self.json_data.get("tz_offset", 0):
return tz_offset
return 0
class BaseThon(BaseData):
def __init__(self, options: BaseThonOptions):
def __init__(self, options: ThonOptions):
self.__item = options.item
self.__retries = options.retries
self.__timeout = options.timeout
self._logger = logging.getLogger("basethon")
self._async_check_timeout = options.async_check_timeout
super().__init__(options.json_data, options.raise_error)
self.__client = self.__get_client()
self.me: User | InputPeerUser | None = None
@property
def client(self) -> TelegramClient:
@ -187,7 +87,7 @@ class BaseThon(BaseData):
with contextlib.suppress(Exception):
await self.client(GetCountriesListRequest(self.lang_pack, 0))
async def check(self) -> str:
async def _check(self) -> str:
try:
await self.client.connect()
if not await self.client.is_user_authorized():
@ -195,6 +95,7 @@ class BaseThon(BaseData):
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()
@ -207,7 +108,24 @@ class BaseThon(BaseData):
self._logger.exception(e)
return f"ERROR_AUTH:{e}"
async def search_code(self, options: BaseThonSearchCodeOptions) -> str:
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 "ERROR_AUTH:CONNECTION_ERROR: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()
future = datetime.now() + timedelta(seconds=options.wait_time)
while future > datetime.now():
async for message in self.client.iter_messages(
@ -217,7 +135,12 @@ class BaseThon(BaseData):
if not message.date:
continue
now = datetime.now(tz=timezone.utc)
date = now - timedelta(minutes=options.date_delta)
date = now - timedelta(seconds=options.date_delta)
self._logger.debug(
f"{self.__item} {message.text}\n\n"
f"Current date: {message.date}\n"
f"Delta date: {date}"
)
if message.date < date:
continue
if not (match := re.search(options.regexp, message.text)):

View File

@ -11,7 +11,7 @@ from basethon.async_base_session import AsyncBaseSession
from basethon.proxy_parser import ProxyParser
class JsonAsyncConverter(AsyncBaseSession):
class JsonAsyncConverter:
def __init__(
self,
base_dir: Path,
@ -28,7 +28,7 @@ class JsonAsyncConverter(AsyncBaseSession):
proxy: прокси для подключения формат: http:ip:port:user:pswd
json_write: записывать в json файл информацию о string_session + proxy?
"""
super().__init__(base_dir, errors_dir, banned_dir)
self.__base_session = AsyncBaseSession(base_dir, errors_dir, banned_dir)
self.__api_id, self.__api_hash = 2040, "b18441a1ff607e10a989891a5462e627"
self.__json_write = json_write
self.__proxy = ProxyParser(proxy).asdict_thon
@ -71,7 +71,7 @@ class JsonAsyncConverter(AsyncBaseSession):
string_session: str
proxy: dict
"""
async for item, json_file, json_data in self:
async for item, json_file, json_data in self.__base_session:
_item, _json_file, _json_data = await self._main(item, json_file, json_data)
yield _item, _json_file, _json_data
@ -82,7 +82,7 @@ class JsonAsyncConverter(AsyncBaseSession):
"""
count = 0
self.__json_write = True
async for item, json_file, json_data in self:
async for item, json_file, json_data in self.__base_session:
await self._main(item, json_file, json_data)
count += 1
return count

View File

@ -11,7 +11,7 @@ from .base_session import BaseSession
from .proxy_parser import ProxyParser
class JsonConverter(BaseSession):
class JsonConverter:
def __init__(
self,
base_dir: Path,
@ -28,7 +28,7 @@ class JsonConverter(BaseSession):
proxy: прокси для подключения формат: http:ip:port:user:pswd
json_write: записывать в json файл информацию о string_session + proxy?
"""
super().__init__(base_dir, errors_dir, banned_dir)
self.__base_session = BaseSession(base_dir, errors_dir, banned_dir)
self.__api_id, self.__api_hash = 2040, "b18441a1ff607e10a989891a5462e627"
self.__json_write = json_write
self.__proxy = ProxyParser(proxy).asdict_thon
@ -82,7 +82,7 @@ class JsonConverter(BaseSession):
"""
count = 0
self.__json_write = True
for item, json_file, json_data in self:
for item, json_file, json_data in self.__base_session:
self._main(item, json_file, json_data)
count += 1
return count

View File

@ -3,18 +3,23 @@ from pathlib import Path
@dataclass
class BaseThonOptions:
class ThonOptions:
item: Path | None
json_data: dict
retries: int = 10
timeout: int = 10
raise_error: bool = True
async_check_timeout: int | None = 0
def __post_init__(self):
if self.async_check_timeout == 0:
self.async_check_timeout = self.retries * self.timeout
@dataclass
class BaseThonSearchCodeOptions:
class ThonSearchCodeOptions:
limit: int = 1
date_delta: int = 60
date_delta: int = 80
wait_time: int = 300
entity: int | str = 777000
regexp: str = r"\b\d{5,6}\b"

30
poetry.lock generated
View File

@ -11,6 +11,17 @@ files = [
{file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
]
[[package]]
name = "async-timeout"
version = "5.0.1"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.8"
files = [
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
]
[[package]]
name = "jsoner"
version = "0.1.0"
@ -50,6 +61,23 @@ files = [
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
]
[[package]]
name = "python-socks"
version = "2.1.1"
description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python"
optional = false
python-versions = "*"
files = [
{file = "python-socks-2.1.1.tar.gz", hash = "sha256:3bb68964c97690d5a3eab6c12a772f415725f295b148cbe1ca8870cb47ebcb96"},
{file = "python_socks-2.1.1-py3-none-any.whl", hash = "sha256:6174278b0e005bd36b5d43681a0a3d816922852c9bccc2eceb17c60f4c8d4048"},
]
[package.extras]
anyio = ["anyio (>=3.3.4)"]
asyncio = ["async-timeout (>=3.0.1)"]
curio = ["curio (>=1.4)"]
trio = ["trio (>=0.16.0)"]
[[package]]
name = "rsa"
version = "4.9.1"
@ -85,4 +113,4 @@ cryptg = ["cryptg"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "d48b78dd5bc745693c770b4a2f96a8be461e21385bd8f84ae71f9906672cf433"
content-hash = "0302bb04698ae890994aa82ee4ad64ef42193bd6f1d0f987d10a448540bf3bf5"

View File

@ -9,6 +9,8 @@ readme = "README.md"
python = "^3.12"
telethon = "^1.37.0"
jsoner = {git = "https://github.com/trojvn/jsoner.git"}
async-timeout = "^5.0.1"
python-socks = "2.1.1"
[build-system]