Compare commits
No commits in common. "telethon_version" and "main" have entirely different histories.
telethon_v
...
main
@ -25,8 +25,8 @@ class BaseModelWithSerializeDatetime(BaseModel):
|
|||||||
|
|
||||||
class MessageFromChatSchema(BaseModelWithSerializeDatetime):
|
class MessageFromChatSchema(BaseModelWithSerializeDatetime):
|
||||||
id: PositiveInt
|
id: PositiveInt
|
||||||
user_id: int
|
user_id: PositiveInt
|
||||||
chat_id: int
|
chat_id: NegativeInt
|
||||||
text: str
|
text: str
|
||||||
message_time: datetime
|
message_time: datetime
|
||||||
|
|
||||||
|
2
telegram-application/.gitignore
vendored
2
telegram-application/.gitignore
vendored
@ -161,7 +161,7 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
/migrations/versions
|
/alembic/versions
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
.env
|
.env
|
||||||
|
@ -1,93 +1,22 @@
|
|||||||
from telethon.errors import FloodWaitError
|
from groq import AsyncGroq
|
||||||
from telethon.tl.functions.channels import JoinChannelRequest
|
|
||||||
import asyncio
|
|
||||||
from telethon import TelegramClient
|
|
||||||
from telethon.sessions import StringSession
|
|
||||||
from src.core.settings.base import settings
|
from src.core.settings.base import settings
|
||||||
import time
|
# Убедитесь, что переменная окружения GROQ_API_KEY установлена
|
||||||
import openpyxl
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Конфигурация Excel-файла
|
|
||||||
EXCEL_FILE_PATH = "/home/harold/Documents/chats.xlsx"
|
|
||||||
SHEET_NAME = 'Чаты' # Имя листа в Excel
|
|
||||||
LINK_COLUMN = 3 # Номер столбца со ссылками (3 = колонка C)
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
client = AsyncGroq(
|
||||||
# Загрузка групп из Excel
|
api_key=settings.GROQ.API_KEY,
|
||||||
groups = []
|
)
|
||||||
try:
|
|
||||||
# Проверка существования файла
|
|
||||||
if not os.path.exists(EXCEL_FILE_PATH):
|
|
||||||
raise FileNotFoundError(f"Excel файл не найден: {EXCEL_FILE_PATH}")
|
|
||||||
|
|
||||||
wb = openpyxl.load_workbook(EXCEL_FILE_PATH)
|
|
||||||
|
|
||||||
# Проверка существования листа
|
async def create_request_ai(messages: list):
|
||||||
if SHEET_NAME not in wb.sheetnames:
|
chat_completion = await client.chat.completions.create(
|
||||||
raise ValueError(f"Лист '{SHEET_NAME}' не найден в файле")
|
messages=messages,
|
||||||
|
model="llama-3.3-70b-versatile",
|
||||||
sheet = wb[SHEET_NAME]
|
temperature=2,
|
||||||
|
|
||||||
# Чтение данных, пропуская заголовок
|
|
||||||
for row in range(2, sheet.max_row + 1):
|
|
||||||
link_cell = sheet.cell(row=row, column=LINK_COLUMN)
|
|
||||||
if link_cell.value and 't.me/' in link_cell.value:
|
|
||||||
# Извлекаем username из ссылки
|
|
||||||
username = link_cell.value.split('t.me/')[-1].split('/')[0].split('?')[0]
|
|
||||||
groups.append(username)
|
|
||||||
|
|
||||||
if not groups:
|
|
||||||
print("⚠️ В файле не найдено валидных ссылок на Telegram группы")
|
|
||||||
return
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Ошибка при чтении Excel файла: {str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Инициализация Telegram клиента
|
|
||||||
client = TelegramClient(
|
|
||||||
session=StringSession(settings.ACCOUNT.SESSION),
|
|
||||||
api_id=settings.ACCOUNT.API_ID,
|
|
||||||
api_hash=settings.ACCOUNT.API_HASH,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await client.start()
|
print(chat_completion.choices[0].message.content)
|
||||||
|
|
||||||
print(f"\nНайдено {len(groups)} групп в файле. Начинаю подписку...")
|
# if __name__ == "__main__":
|
||||||
|
# asyncio.run(create_request())
|
||||||
success_count = 0
|
|
||||||
for i, username in enumerate(groups, 1):
|
|
||||||
try:
|
|
||||||
# Пытаемся найти группу
|
|
||||||
group = await client.get_entity(username)
|
|
||||||
# Подписываемся
|
|
||||||
await client(JoinChannelRequest(group))
|
|
||||||
print(f"{i}. ✓ Успешно: @{username}")
|
|
||||||
success_count += 1
|
|
||||||
|
|
||||||
except FloodWaitError as e:
|
|
||||||
wait_time = e.seconds + 5
|
|
||||||
print(f"{i}. ⏳ Ожидаем {wait_time} секунд из-за ограничения Telegram")
|
|
||||||
time.sleep(wait_time)
|
|
||||||
# Повторяем попытку после ожидания
|
|
||||||
try:
|
|
||||||
await client(JoinChannelRequest(username))
|
|
||||||
print(f"{i}. ✓ Успешно после ожидания: @{username}")
|
|
||||||
success_count += 1
|
|
||||||
except Exception as e:
|
|
||||||
print(f"{i}. ✗ Ошибка повторной попытки для @{username}: {str(e)}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"{i}. ✗ Ошибка в @{username}: {str(e)}")
|
|
||||||
|
|
||||||
# Задержка между запросами
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
print(f"\nПроцесс завершен! Успешно подписано на {success_count} из {len(groups)} групп")
|
|
||||||
await client.disconnect()
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# if __name__ == '__main__':
|
|
||||||
# asyncio.run(main())
|
|
@ -1,117 +0,0 @@
|
|||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# path to migration scripts
|
|
||||||
script_location = migrations
|
|
||||||
|
|
||||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
|
||||||
# Uncomment the line below if you want the files to be prepended with date and time
|
|
||||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
|
||||||
# for all available tokens
|
|
||||||
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
|
||||||
|
|
||||||
# sys.path path, will be prepended to sys.path if present.
|
|
||||||
# defaults to the current working directory.
|
|
||||||
prepend_sys_path = .
|
|
||||||
|
|
||||||
# timezone to use when rendering the date within the migration file
|
|
||||||
# as well as the filename.
|
|
||||||
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
|
||||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
|
||||||
# string value is passed to ZoneInfo()
|
|
||||||
# leave blank for localtime
|
|
||||||
# timezone =
|
|
||||||
|
|
||||||
# max length of characters to apply to the
|
|
||||||
# "slug" field
|
|
||||||
# truncate_slug_length = 40
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
# set to 'true' to allow .pyc and .pyo files without
|
|
||||||
# a source .py file to be detected as revisions in the
|
|
||||||
# versions/ directory
|
|
||||||
# sourceless = false
|
|
||||||
|
|
||||||
# version location specification; This defaults
|
|
||||||
# to migrations/versions. When using multiple version
|
|
||||||
# directories, initial revisions must be specified with --version-path.
|
|
||||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
|
||||||
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
|
|
||||||
|
|
||||||
# version path separator; As mentioned above, this is the character used to split
|
|
||||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
|
||||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
|
||||||
# Valid values for version_path_separator are:
|
|
||||||
#
|
|
||||||
# version_path_separator = :
|
|
||||||
# version_path_separator = ;
|
|
||||||
# version_path_separator = space
|
|
||||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
|
||||||
|
|
||||||
# set to 'true' to search source files recursively
|
|
||||||
# in each "version_locations" directory
|
|
||||||
# new in Alembic version 1.10
|
|
||||||
# recursive_version_locations = false
|
|
||||||
|
|
||||||
# the output encoding used when revision files
|
|
||||||
# are written from script.py.mako
|
|
||||||
# output_encoding = utf-8
|
|
||||||
|
|
||||||
sqlalchemy.url = postgresql://%(DB_USER)s:%(DB_PASS)s@%(DB_HOST)s:%(DB_PORT)s/%(DB_NAME)s
|
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
|
||||||
# post_write_hooks defines scripts or Python functions that are run
|
|
||||||
# on newly generated revision scripts. See the documentation for further
|
|
||||||
# detail and examples
|
|
||||||
|
|
||||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
||||||
# hooks = black
|
|
||||||
# black.type = console_scripts
|
|
||||||
# black.entrypoint = black
|
|
||||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
|
||||||
# hooks = ruff
|
|
||||||
# ruff.type = exec
|
|
||||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
|
||||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
||||||
|
|
@ -8,25 +8,25 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env_prod
|
- .env_prod
|
||||||
depends_on:
|
depends_on:
|
||||||
# postgres:
|
postgres:
|
||||||
# condition: service_started
|
condition: service_started
|
||||||
redis:
|
redis:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
|
||||||
#
|
|
||||||
# postgres:
|
postgres:
|
||||||
# image: postgres:13
|
image: postgres:13
|
||||||
# container_name: postgres
|
container_name: postgres
|
||||||
# restart: unless-stopped
|
restart: unless-stopped
|
||||||
# environment:
|
environment:
|
||||||
# POSTGRES_DB: postgres
|
POSTGRES_DB: postgres
|
||||||
# POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
# POSTGRES_PASSWORD: jkjklkjqpnn34234
|
POSTGRES_PASSWORD: jkjklkjqpnn34234
|
||||||
# ports:
|
ports:
|
||||||
# - "9151:5432"
|
- "9151:5432"
|
||||||
# volumes:
|
volumes:
|
||||||
# - ./docker/postgres/data:/var/lib/postgresql/data
|
- ./docker/postgres/data:/var/lib/postgresql/data
|
||||||
# - ./sql_scripts/create_table.sql:/docker-entrypoint-initdb.d/create_table.sql
|
- ./sql_scripts/create_table.sql:/docker-entrypoint-initdb.d/create_table.sql
|
||||||
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Generic single-database configuration with an async dbapi.
|
|
@ -1,94 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
from sqlalchemy import pool
|
|
||||||
from sqlalchemy.engine import Connection
|
|
||||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
from src.core.database import Base
|
|
||||||
from src.core.settings.base import settings
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
if config.config_file_name is not None:
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
|
||||||
# for 'autogenerate' support
|
|
||||||
# from myapp import mymodel
|
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
target_metadata = Base.metadata
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
|
||||||
# can be acquired:
|
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
config.set_main_option('sqlalchemy.url', settings.POSTGRES.async_connect_url.unicode_string())
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
url = config.get_main_option('sqlalchemy.url')
|
|
||||||
context.configure(
|
|
||||||
url=url,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
literal_binds=True,
|
|
||||||
dialect_opts={'paramstyle': 'named'},
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def do_run_migrations(connection: Connection) -> None:
|
|
||||||
context.configure(connection=connection, target_metadata=target_metadata)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
async def run_async_migrations() -> None:
|
|
||||||
"""In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
connectable = async_engine_from_config(
|
|
||||||
config.get_section(config.config_ini_section, {}),
|
|
||||||
prefix='sqlalchemy.',
|
|
||||||
poolclass=pool.NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
async with connectable.connect() as connection:
|
|
||||||
await connection.run_sync(do_run_migrations)
|
|
||||||
|
|
||||||
await connectable.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online() -> None:
|
|
||||||
"""Run migrations in 'online' mode."""
|
|
||||||
|
|
||||||
asyncio.run(run_async_migrations())
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
@ -1,24 +0,0 @@
|
|||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = ${repr(up_revision)}
|
|
||||||
down_revision = ${repr(down_revision)}
|
|
||||||
branch_labels = ${repr(branch_labels)}
|
|
||||||
depends_on = ${repr(depends_on)}
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
87
telegram-application/poetry.lock
generated
87
telegram-application/poetry.lock
generated
@ -33,26 +33,6 @@ files = [
|
|||||||
pamqp = "3.3.0"
|
pamqp = "3.3.0"
|
||||||
yarl = "*"
|
yarl = "*"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "alembic"
|
|
||||||
version = "1.16.2"
|
|
||||||
description = "A database migration tool for SQLAlchemy."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.9"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "alembic-1.16.2-py3-none-any.whl", hash = "sha256:5f42e9bd0afdbd1d5e3ad856c01754530367debdebf21ed6894e34af52b3bb03"},
|
|
||||||
{file = "alembic-1.16.2.tar.gz", hash = "sha256:e53c38ff88dadb92eb22f8b150708367db731d58ad7e9d417c9168ab516cbed8"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Mako = "*"
|
|
||||||
SQLAlchemy = ">=1.4.0"
|
|
||||||
typing-extensions = ">=4.12"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
tz = ["tzdata"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -399,14 +379,14 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "faststream"
|
name = "faststream"
|
||||||
version = "0.5.42"
|
version = "0.5.34"
|
||||||
description = "FastStream: the simplest way to work with a messaging queues"
|
description = "FastStream: the simplest way to work with a messaging queues"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "faststream-0.5.42-py3-none-any.whl", hash = "sha256:dbbbbca36bc22ac017c0cca2acffb81dc6569c1813055367bf7acd6c46c6cbb9"},
|
{file = "faststream-0.5.34-py3-none-any.whl", hash = "sha256:aa7f61d6968a68f13ebf755cce9e8bf11b00717c28b2ef66e896b5d652a6c6a2"},
|
||||||
{file = "faststream-0.5.42.tar.gz", hash = "sha256:7f115dc4c9eb53a76a5ee1194191179f8161ed678ad0cb744ace2743bfd5002e"},
|
{file = "faststream-0.5.34.tar.gz", hash = "sha256:84615968c5768ebaa89b72ae66b53e5302c08e7d18b341ef5193e54cb6ba8623"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -415,21 +395,21 @@ fast-depends = ">=2.4.0b0,<3.0.0"
|
|||||||
typing-extensions = ">=4.8.0"
|
typing-extensions = ">=4.8.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
cli = ["typer (>=0.9,!=0.12,<=0.15.4)", "watchfiles (>=0.15.0,<1.1.0)"]
|
cli = ["typer (>=0.9,!=0.12,<=0.15.1)", "watchfiles (>=0.15.0,<1.1.0)"]
|
||||||
confluent = ["confluent-kafka (>=2,!=2.8.1,<3)", "confluent-kafka (>=2.6,!=2.8.1,<3)"]
|
confluent = ["confluent-kafka (>=2,<3)", "confluent-kafka (>=2.6,<3)"]
|
||||||
dev = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "bandit (==1.7.10)", "bandit (==1.8.3)", "cairosvg", "codespell (==2.4.1)", "confluent-kafka (>=2,!=2.8.1,<3)", "confluent-kafka (>=2.6,!=2.8.1,<3)", "confluent-kafka-stubs", "coverage[toml] (==7.6.1)", "coverage[toml] (==7.8.0)", "detect-secrets (==1.5.0)", "dirty-equals (==0.9.0)", "email-validator (==2.2.0)", "fastapi (==0.115.12)", "httpx (==0.28.1)", "mdx-include (==1.4.2)", "mike (==2.1.3)", "mkdocs-git-revision-date-localized-plugin (==1.4.5)", "mkdocs-glightbox (==0.4.0)", "mkdocs-literate-nav (==0.6.2)", "mkdocs-macros-plugin (==1.3.7)", "mkdocs-material (==9.6.14)", "mkdocs-minify-plugin (==0.8.0)", "mkdocs-static-i18n (==1.3.0)", "mkdocstrings[python] (==0.26.1)", "mkdocstrings[python] (==0.29.1)", "mypy (==1.15.0)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "pillow", "pre-commit (==3.5.0)", "pre-commit (==4.2.0)", "prometheus-client (>=0.20.0,<0.30.0)", "pydantic-settings (>=2.0.0,<3.0.0)", "pytest (==8.3.5)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.26.0)", "pyyaml (==6.0.2)", "redis (>=5.0.0,<7.0.0)", "requests", "ruff (==0.11.10)", "semgrep (==1.122.0)", "semgrep (==1.99.0)", "typer (>=0.9,!=0.12,<=0.15.4)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "typing-extensions (>=4.8.0,<4.12.1)", "watchfiles (>=0.15.0,<1.1.0)"]
|
dev = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "bandit (==1.8.0)", "cairosvg", "codespell (==2.3.0)", "confluent-kafka (>=2,<3)", "confluent-kafka (>=2.6,<3)", "confluent-kafka-stubs", "coverage[toml] (==7.6.1)", "coverage[toml] (==7.6.10)", "detect-secrets (==1.5.0)", "dirty-equals (==0.8.0)", "email-validator (==2.2.0)", "fastapi (==0.115.6)", "httpx (==0.28.1)", "mdx-include (==1.4.2)", "mike (==2.1.3)", "mkdocs-git-revision-date-localized-plugin (==1.3.0)", "mkdocs-glightbox (==0.4.0)", "mkdocs-literate-nav (==0.6.1)", "mkdocs-macros-plugin (==1.3.7)", "mkdocs-material (==9.5.49)", "mkdocs-minify-plugin (==0.8.0)", "mkdocs-static-i18n (==1.2.3)", "mkdocstrings[python] (==0.27.0)", "mypy (==1.14.1)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "pillow", "pre-commit (==3.5.0)", "pre-commit (==4.0.1)", "prometheus-client (>=0.20.0,<0.30.0)", "pydantic-settings (>=2.0.0,<3.0.0)", "pytest (==8.3.4)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.25.1)", "pyyaml (==6.0.2)", "redis (>=5.0.0,<6.0.0)", "requests", "ruff (==0.8.6)", "semgrep (==1.101.0)", "typer (>=0.9,!=0.12,<=0.15.1)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "typing-extensions (>=4.8.0,<4.12.1)", "watchfiles (>=0.15.0,<1.1.0)"]
|
||||||
devdocs = ["cairosvg", "mdx-include (==1.4.2)", "mike (==2.1.3)", "mkdocs-git-revision-date-localized-plugin (==1.4.5)", "mkdocs-glightbox (==0.4.0)", "mkdocs-literate-nav (==0.6.2)", "mkdocs-macros-plugin (==1.3.7)", "mkdocs-material (==9.6.14)", "mkdocs-minify-plugin (==0.8.0)", "mkdocs-static-i18n (==1.3.0)", "mkdocstrings[python] (==0.26.1)", "mkdocstrings[python] (==0.29.1)", "pillow", "requests"]
|
devdocs = ["cairosvg", "mdx-include (==1.4.2)", "mike (==2.1.3)", "mkdocs-git-revision-date-localized-plugin (==1.3.0)", "mkdocs-glightbox (==0.4.0)", "mkdocs-literate-nav (==0.6.1)", "mkdocs-macros-plugin (==1.3.7)", "mkdocs-material (==9.5.49)", "mkdocs-minify-plugin (==0.8.0)", "mkdocs-static-i18n (==1.2.3)", "mkdocstrings[python] (==0.27.0)", "pillow", "requests"]
|
||||||
kafka = ["aiokafka (>=0.9,<0.13)"]
|
kafka = ["aiokafka (>=0.9,<0.13)"]
|
||||||
lint = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "bandit (==1.7.10)", "bandit (==1.8.3)", "codespell (==2.4.1)", "confluent-kafka (>=2,!=2.8.1,<3)", "confluent-kafka (>=2.6,!=2.8.1,<3)", "confluent-kafka-stubs", "mypy (==1.15.0)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<7.0.0)", "ruff (==0.11.10)", "semgrep (==1.122.0)", "semgrep (==1.99.0)", "typer (>=0.9,!=0.12,<=0.15.4)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "watchfiles (>=0.15.0,<1.1.0)"]
|
lint = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "bandit (==1.8.0)", "codespell (==2.3.0)", "confluent-kafka (>=2,<3)", "confluent-kafka (>=2.6,<3)", "confluent-kafka-stubs", "mypy (==1.14.1)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<6.0.0)", "ruff (==0.8.6)", "semgrep (==1.101.0)", "typer (>=0.9,!=0.12,<=0.15.1)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "watchfiles (>=0.15.0,<1.1.0)"]
|
||||||
nats = ["nats-py (>=2.7.0,<=3.0.0)"]
|
nats = ["nats-py (>=2.7.0,<=3.0.0)"]
|
||||||
optionals = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "confluent-kafka (>=2,!=2.8.1,<3)", "confluent-kafka (>=2.6,!=2.8.1,<3)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<7.0.0)", "typer (>=0.9,!=0.12,<=0.15.4)", "watchfiles (>=0.15.0,<1.1.0)"]
|
optionals = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "confluent-kafka (>=2,<3)", "confluent-kafka (>=2.6,<3)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<6.0.0)", "typer (>=0.9,!=0.12,<=0.15.1)", "watchfiles (>=0.15.0,<1.1.0)"]
|
||||||
otel = ["opentelemetry-sdk (>=1.24.0,<2.0.0)"]
|
otel = ["opentelemetry-sdk (>=1.24.0,<2.0.0)"]
|
||||||
prometheus = ["prometheus-client (>=0.20.0,<0.30.0)"]
|
prometheus = ["prometheus-client (>=0.20.0,<0.30.0)"]
|
||||||
rabbit = ["aio-pika (>=9,<10)"]
|
rabbit = ["aio-pika (>=9,<10)"]
|
||||||
redis = ["redis (>=5.0.0,<7.0.0)"]
|
redis = ["redis (>=5.0.0,<6.0.0)"]
|
||||||
test-core = ["coverage[toml] (==7.6.1)", "coverage[toml] (==7.8.0)", "dirty-equals (==0.9.0)", "pytest (==8.3.5)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.26.0)", "typing-extensions (>=4.8.0,<4.12.1)"]
|
test-core = ["coverage[toml] (==7.6.1)", "coverage[toml] (==7.6.10)", "dirty-equals (==0.8.0)", "pytest (==8.3.4)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.25.1)", "typing-extensions (>=4.8.0,<4.12.1)"]
|
||||||
testing = ["coverage[toml] (==7.6.1)", "coverage[toml] (==7.8.0)", "dirty-equals (==0.9.0)", "email-validator (==2.2.0)", "fastapi (==0.115.12)", "httpx (==0.28.1)", "pydantic-settings (>=2.0.0,<3.0.0)", "pytest (==8.3.5)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.26.0)", "pyyaml (==6.0.2)", "typing-extensions (>=4.8.0,<4.12.1)"]
|
testing = ["coverage[toml] (==7.6.1)", "coverage[toml] (==7.6.10)", "dirty-equals (==0.8.0)", "email-validator (==2.2.0)", "fastapi (==0.115.6)", "httpx (==0.28.1)", "pydantic-settings (>=2.0.0,<3.0.0)", "pytest (==8.3.4)", "pytest-asyncio (==0.24.0)", "pytest-asyncio (==0.25.1)", "pyyaml (==6.0.2)", "typing-extensions (>=4.8.0,<4.12.1)"]
|
||||||
types = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "confluent-kafka (>=2,!=2.8.1,<3)", "confluent-kafka (>=2.6,!=2.8.1,<3)", "confluent-kafka-stubs", "mypy (==1.15.0)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<7.0.0)", "typer (>=0.9,!=0.12,<=0.15.4)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "watchfiles (>=0.15.0,<1.1.0)"]
|
types = ["aio-pika (>=9,<10)", "aiokafka (>=0.9,<0.13)", "confluent-kafka (>=2,<3)", "confluent-kafka (>=2.6,<3)", "confluent-kafka-stubs", "mypy (==1.14.1)", "nats-py (>=2.7.0,<=3.0.0)", "opentelemetry-sdk (>=1.24.0,<2.0.0)", "prometheus-client (>=0.20.0,<0.30.0)", "redis (>=5.0.0,<6.0.0)", "typer (>=0.9,!=0.12,<=0.15.1)", "types-aiofiles", "types-deprecated", "types-docutils", "types-pygments", "types-pyyaml", "types-redis", "types-setuptools", "types-ujson", "watchfiles (>=0.15.0,<1.1.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-ai-generativelanguage"
|
name = "google-ai-generativelanguage"
|
||||||
@ -900,26 +880,6 @@ files = [
|
|||||||
pyaes = "1.6.1"
|
pyaes = "1.6.1"
|
||||||
pysocks = "1.7.1"
|
pysocks = "1.7.1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mako"
|
|
||||||
version = "1.3.10"
|
|
||||||
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"},
|
|
||||||
{file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
MarkupSafe = ">=0.9.2"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
babel = ["Babel"]
|
|
||||||
lingua = ["lingua"]
|
|
||||||
testing = ["pytest"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
@ -1708,25 +1668,6 @@ anyio = ">=3.6.2,<5"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
|
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "telethon"
|
|
||||||
version = "1.40.0"
|
|
||||||
description = "Full-featured Telegram client library for Python 3"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "Telethon-1.40.0-py3-none-any.whl", hash = "sha256:146fd4cb2a7afa66bc67f9c2167756096a37b930f65711a3e7399ec9874dcfa7"},
|
|
||||||
{file = "telethon-1.40.0.tar.gz", hash = "sha256:40e83326877a2e68b754d4b6d0d1ca5ac924110045b039e02660f2d67add97db"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyaes = "*"
|
|
||||||
rsa = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
cryptg = ["cryptg"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tgcrypto"
|
name = "tgcrypto"
|
||||||
version = "1.2.5"
|
version = "1.2.5"
|
||||||
@ -1994,4 +1935,4 @@ propcache = ">=0.2.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.11, <4.0"
|
python-versions = ">=3.11, <4.0"
|
||||||
content-hash = "2eda2ed313011bb0b1c97d9018d87190d2d2ca630cf0eb8a4dca1ea73612ba53"
|
content-hash = "8d9d9fd14dae4dfc99b18096b87a59e6bca68ecf03bdc411f4b2c428e556c9a6"
|
||||||
|
@ -23,9 +23,7 @@ dependencies = [
|
|||||||
"sqladmin (>=0.20.1,<0.21.0)",
|
"sqladmin (>=0.20.1,<0.21.0)",
|
||||||
"fastapi (>=0.115.12,<0.116.0)",
|
"fastapi (>=0.115.12,<0.116.0)",
|
||||||
"uvicorn (>=0.34.2,<0.35.0)",
|
"uvicorn (>=0.34.2,<0.35.0)",
|
||||||
"emoji (>=2.14.1,<3.0.0)",
|
"emoji (>=2.14.1,<3.0.0)"
|
||||||
"telethon (>=1.40.0,<2.0.0)",
|
|
||||||
"alembic (>=1.16.2,<2.0.0)"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
from telethon.sync import TelegramClient
|
|
||||||
from telethon.sessions import StringSession
|
|
||||||
|
|
||||||
from src.core.settings.base import settings
|
|
||||||
|
|
||||||
with TelegramClient(StringSession(), settings.ACCOUNT.API_ID, settings.ACCOUNT.API_HASH) as client:
|
|
||||||
print(client.session.save())
|
|
@ -11,7 +11,6 @@ if TYPE_CHECKING:
|
|||||||
class TgChat(Base):
|
class TgChat(Base):
|
||||||
chat_type: Mapped[str]
|
chat_type: Mapped[str]
|
||||||
title: Mapped[str]
|
title: Mapped[str]
|
||||||
username: Mapped[str | None]
|
|
||||||
|
|
||||||
message_relationship: Mapped[list["TgMessage"]] = relationship(
|
message_relationship: Mapped[list["TgMessage"]] = relationship(
|
||||||
back_populates="chat_relationship",
|
back_populates="chat_relationship",
|
||||||
|
@ -4,5 +4,4 @@ from pydantic import BaseModel
|
|||||||
class AccountSettings(BaseModel):
|
class AccountSettings(BaseModel):
|
||||||
API_ID: int
|
API_ID: int
|
||||||
API_HASH: str
|
API_HASH: str
|
||||||
NAME: str = "tg_account"
|
NAME: str = "tg_account"
|
||||||
SESSION: str
|
|
@ -1,16 +1 @@
|
|||||||
from enum import Enum
|
MESSAGE_CHANG_SIZE: int = 5
|
||||||
|
|
||||||
from telethon.types import Message, User, Chat, Channel
|
|
||||||
|
|
||||||
|
|
||||||
MESSAGE_CHANG_SIZE: int = 30
|
|
||||||
|
|
||||||
TELETHON_CHAT_TYPES = User | Chat | Channel
|
|
||||||
|
|
||||||
|
|
||||||
class CustomChatTypes(Enum):
|
|
||||||
PRIVATE_GROUP = "private_group"
|
|
||||||
STANDARD_GROUP = "standard_group"
|
|
||||||
SUPERGROUP = "supergroup"
|
|
||||||
BROADCAST_CHANNEL = "broadcast_channel"
|
|
||||||
BOT = "bot"
|
|
@ -1,58 +1,39 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from src.core.settings.base import settings
|
from pyrogram import Client
|
||||||
from src.core.tg_service.constants import TELETHON_CHAT_TYPES
|
from pyrogram.types import Message
|
||||||
|
|
||||||
from src.core.tg_service.schemas import MessageFromChatSchema
|
from src.core.tg_service.schemas import MessageFromChatSchema
|
||||||
from src.core.tg_service.service import check_user_exists, check_chat_exists, check_chunk_state_and_publish
|
from src.core.tg_service.service import check_user_exists, check_chat_exists, check_chunk_state_and_publish
|
||||||
from src.core.tg_service import utils as api_tg_utils
|
from src.core.tg_service import utils as api_tg_utils
|
||||||
from src.telethon_client import telethon_client
|
|
||||||
|
|
||||||
from telethon.events import NewMessage
|
|
||||||
from telethon.types import Message, User, Chat, Channel
|
|
||||||
|
|
||||||
DATA: dict[int, list[MessageFromChatSchema]] = dict()
|
DATA: dict[int, list[MessageFromChatSchema]] = dict()
|
||||||
|
|
||||||
|
|
||||||
lock = asyncio.Lock()
|
lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def message_listener(client: Client, message: Message):
|
||||||
@telethon_client.on(event=NewMessage)
|
print("received message")
|
||||||
async def message_listener(event: NewMessage.Event) -> None:
|
if api_tg_utils.check_message_condition(message):
|
||||||
print("received new message")
|
|
||||||
message: Message = event.message
|
|
||||||
sender: User = await event.get_sender()
|
|
||||||
chat: TELETHON_CHAT_TYPES = await event.get_chat()
|
|
||||||
|
|
||||||
chat_type, chat_username = api_tg_utils.handle_chat_type(chat)
|
|
||||||
if api_tg_utils.check_message_condition(
|
|
||||||
message=message,
|
|
||||||
sender=sender,
|
|
||||||
chat_type=chat_type,
|
|
||||||
):
|
|
||||||
async with lock:
|
async with lock:
|
||||||
await check_user_exists(
|
await check_user_exists(
|
||||||
username=sender.username,
|
user_pyrogram=message.from_user,
|
||||||
first_name=sender.first_name,
|
|
||||||
last_name=sender.last_name,
|
|
||||||
user_id=sender.id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await check_chat_exists(
|
await check_chat_exists(
|
||||||
chat_id=chat.id,
|
chat_pyrogram=message.chat,
|
||||||
chat_type=chat_type.value,
|
|
||||||
chat_title=chat.title,
|
|
||||||
chat_username=chat_username,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
message_schema = MessageFromChatSchema(
|
message_schema = MessageFromChatSchema(
|
||||||
id=message.id,
|
id=message.id,
|
||||||
user_id=sender.id,
|
user_id=message.from_user.id,
|
||||||
chat_id=chat.id,
|
chat_id=message.chat.id,
|
||||||
text=message.text,
|
text=message.text,
|
||||||
message_time=message.date,
|
message_time=message.date,
|
||||||
)
|
)
|
||||||
await check_chunk_state_and_publish(
|
await check_chunk_state_and_publish(
|
||||||
data=DATA,
|
data=DATA,
|
||||||
message_schema=message_schema,
|
message_schema=message_schema,
|
||||||
chat_id=chat.id,
|
chat_id=message.chat.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ async def send_to_tg_from_bot(text: str):
|
|||||||
"parse_mode": "Markdown",
|
"parse_mode": "Markdown",
|
||||||
}
|
}
|
||||||
async with AsyncClient() as client:
|
async with AsyncClient() as client:
|
||||||
response = await client.post(url, json=payload, timeout=30 )
|
response = await client.post(url, json=payload)
|
||||||
print(response)
|
print(response)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
print(response.text)
|
print(response.text)
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
from telethon.errors.rpcerrorlist import MessageIdInvalidError
|
|
||||||
|
|
||||||
from src.core.database import TgMessage, TgChat, User
|
from src.core.database import TgMessage, TgChat, User
|
||||||
from src.core.settings.base import settings
|
|
||||||
from src.core.tg_service.notify_sender import send_to_tg_from_bot
|
from src.core.tg_service.notify_sender import send_to_tg_from_bot
|
||||||
from src.core.tg_service.utils import create_and_format_message, create_text_for_error_message_success
|
from src.core.tg_service.utils import create_and_format_message
|
||||||
|
|
||||||
from src.telethon_client import telethon_client
|
|
||||||
|
|
||||||
|
|
||||||
async def notify_for_success(
|
async def notify_for_success(
|
||||||
@ -14,9 +10,8 @@ async def notify_for_success(
|
|||||||
success_reason: str,
|
success_reason: str,
|
||||||
user_model: User,
|
user_model: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
source_chat = await telethon_client.get_entity(int("-100" + str(chat.id)))
|
|
||||||
|
|
||||||
message = create_and_format_message(
|
message = create_and_format_message(
|
||||||
|
messages=messages,
|
||||||
reason=success_reason,
|
reason=success_reason,
|
||||||
user_model=user_model,
|
user_model=user_model,
|
||||||
chat=chat,
|
chat=chat,
|
||||||
@ -24,21 +19,4 @@ async def notify_for_success(
|
|||||||
|
|
||||||
await send_to_tg_from_bot(
|
await send_to_tg_from_bot(
|
||||||
text=message
|
text=message
|
||||||
)
|
)
|
||||||
|
|
||||||
for message in messages:
|
|
||||||
try:
|
|
||||||
await telethon_client.forward_messages(
|
|
||||||
entity=settings.NOTIFY.CHAT_ID,
|
|
||||||
messages=message.id,
|
|
||||||
from_peer=source_chat,
|
|
||||||
)
|
|
||||||
except MessageIdInvalidError:
|
|
||||||
await send_to_tg_from_bot(
|
|
||||||
text=create_text_for_error_message_success(
|
|
||||||
"🔒 Удаленное сообщение\n\n"
|
|
||||||
"⚠️ Сообщение удалено или недоступно\n\n"
|
|
||||||
f"📝 Текст:`{message.text}`"
|
|
||||||
f"👤 ID_USER:`{message.user_id}`"
|
|
||||||
)
|
|
||||||
)
|
|
@ -8,7 +8,7 @@ from src.core.common.schemas import BaseModelWithSerializeDatetime
|
|||||||
class MessageFromChatSchema(BaseModelWithSerializeDatetime):
|
class MessageFromChatSchema(BaseModelWithSerializeDatetime):
|
||||||
id: PositiveInt
|
id: PositiveInt
|
||||||
user_id: PositiveInt
|
user_id: PositiveInt
|
||||||
chat_id: int
|
chat_id: NegativeInt
|
||||||
text: str
|
text: str
|
||||||
message_time: datetime
|
message_time: datetime
|
||||||
|
|
||||||
|
@ -16,16 +16,13 @@ lock = asyncio.Lock()
|
|||||||
|
|
||||||
|
|
||||||
async def check_user_exists(
|
async def check_user_exists(
|
||||||
username: str,
|
user_pyrogram: PyroUser,
|
||||||
first_name: str,
|
|
||||||
last_name: str,
|
|
||||||
user_id: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
user_schema = UserFromMessageSchema(
|
user_schema = UserFromMessageSchema(
|
||||||
username=username,
|
username=user_pyrogram.username,
|
||||||
first_name=first_name,
|
first_name=user_pyrogram.first_name,
|
||||||
last_name=last_name,
|
last_name=user_pyrogram.last_name,
|
||||||
id=user_id,
|
id=user_pyrogram.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_model = await redis_client.get(name=str(user_schema.id))
|
user_model = await redis_client.get(name=str(user_schema.id))
|
||||||
@ -45,35 +42,24 @@ async def check_user_exists(
|
|||||||
|
|
||||||
|
|
||||||
async def check_chat_exists(
|
async def check_chat_exists(
|
||||||
chat_id: int,
|
chat_pyrogram: PyroChat
|
||||||
chat_type: str,
|
|
||||||
chat_title: str,
|
|
||||||
chat_username: str | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
chat = await redis_client.get(name=str(chat_id))
|
chat = await redis_client.get(name=str(chat_pyrogram.id))
|
||||||
|
|
||||||
if chat:
|
if chat:
|
||||||
return
|
return
|
||||||
|
|
||||||
async with db_helper.get_async_session_not_closed() as session:
|
async with db_helper.get_async_session_not_closed() as session:
|
||||||
chat = await session.get(TgChat, chat_id)
|
chat = await session.get(TgChat, chat_pyrogram.id)
|
||||||
|
|
||||||
if not chat:
|
if not chat:
|
||||||
chat = await tg_crud.create_new_chat(
|
await tg_crud.create_new_chat(
|
||||||
session=session,
|
session=session,
|
||||||
id=chat_id,
|
id=chat_pyrogram.id,
|
||||||
chat_type=chat_type,
|
chat_type=chat_pyrogram.type.value,
|
||||||
title=chat_title,
|
title=chat_pyrogram.title,
|
||||||
username=chat_username,
|
|
||||||
)
|
)
|
||||||
|
await redis_client.set(name=str(chat_pyrogram.id), value=str(True))
|
||||||
if not chat.username and chat_username:
|
|
||||||
chat.username = chat_username
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await redis_client.set(name=str(chat_id), value=str(True))
|
|
||||||
|
|
||||||
|
|
||||||
async def check_chunk_state_and_publish(
|
async def check_chunk_state_and_publish(
|
||||||
|
@ -1,34 +1,25 @@
|
|||||||
import emoji
|
import emoji
|
||||||
|
|
||||||
# from telethon.events import NewMessage
|
from pyrogram.types import Message
|
||||||
from telethon.tl.types import Message, User as UserTelethon, Chat, Channel
|
from pyrogram.enums import ChatType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from src.core.database import TgMessage, User, TgChat
|
from src.core.database import TgMessage, User, TgChat
|
||||||
|
|
||||||
from src.core.tg_service.constants import TELETHON_CHAT_TYPES, CustomChatTypes
|
|
||||||
|
|
||||||
|
|
||||||
def check_message_condition(
|
def check_message_condition(
|
||||||
message: Message,
|
message: Message,
|
||||||
sender: User,
|
|
||||||
chat_type: CustomChatTypes,
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if not sender:
|
if message.chat.type in [ChatType.PRIVATE, ChatType.BOT]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(sender, Channel):
|
if not message.from_user:
|
||||||
return False
|
|
||||||
|
|
||||||
if chat_type == CustomChatTypes.PRIVATE_GROUP:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not message.text:
|
if not message.text:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if sender.username:
|
if message.from_user.username:
|
||||||
if 'bot' in sender.username.lower():
|
if 'bot' in message.from_user.username.lower():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
emoji_count = emoji.emoji_count(str(message.text))
|
emoji_count = emoji.emoji_count(str(message.text))
|
||||||
@ -37,48 +28,47 @@ def check_message_condition(
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def escape_markdown_v2(text: str) -> str:
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
escape_chars = '_*[]()~`>#+-=|{}.!'
|
|
||||||
return ''.join('\\' + char if char in escape_chars else char for char in text)
|
|
||||||
|
|
||||||
def validate_markdown(text: str) -> str:
|
|
||||||
"""Ensure all markdown entities are properly closed"""
|
|
||||||
# Count backticks to ensure pairs
|
|
||||||
backtick_count = text.count('`')
|
|
||||||
if backtick_count % 3 != 0: # Code blocks use triple backticks
|
|
||||||
# Remove unpaired backticks
|
|
||||||
text = text.replace('`', 'ʻ') # Replace with similar-looking character
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_format_message(
|
def create_and_format_message(
|
||||||
|
messages: list[TgMessage],
|
||||||
reason: str,
|
reason: str,
|
||||||
chat: TgChat,
|
chat: TgChat,
|
||||||
user_model: User | Channel,
|
user_model: User,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
def escape_markdown_v2(text: str) -> str:
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
escape_chars = '_*[]()~`>#+-=|{}.!'
|
||||||
|
return ''.join('\\' + char if char in escape_chars else char for char in text)
|
||||||
|
|
||||||
|
def validate_markdown(text: str) -> str:
|
||||||
|
"""Ensure all markdown entities are properly closed"""
|
||||||
|
# Count backticks to ensure pairs
|
||||||
|
backtick_count = text.count('`')
|
||||||
|
if backtick_count % 3 != 0: # Code blocks use triple backticks
|
||||||
|
# Remove unpaired backticks
|
||||||
|
text = text.replace('`', 'ʻ') # Replace with similar-looking character
|
||||||
|
return text
|
||||||
|
|
||||||
# User info
|
# User info
|
||||||
username = escape_markdown_v2(user_model.username) if user_model.username else f"ID: {user_model.id}"
|
username = escape_markdown_v2(user_model.username) if user_model.username else f"ID: {user_model.id}"
|
||||||
user_link = f"[{username}](tg://user?id={user_model.id})" if user_model.username else f"ID: {user_model.id}"
|
user_link = f"[{username}](tg://user?id={user_model.id})" if user_model.username else f"ID: {user_model.id}"
|
||||||
|
|
||||||
# Chat info
|
# Chat info
|
||||||
chat_title = escape_markdown_v2(chat.title)
|
chat_title = escape_markdown_v2(chat.title)
|
||||||
|
chat_link = (
|
||||||
if chat.username:
|
f"https://t.me/c/{str(chat.id)[4:]}"
|
||||||
chat_link = f"https://t.me/{chat.username}"
|
if str(chat.id).startswith("-100")
|
||||||
else:
|
else f"https://t.me/{chat.username}" if chat.username
|
||||||
chat_link = (
|
else f"Chat ID: {chat.id}"
|
||||||
f"https://t.me/c/{str(chat.id)[4:]}"
|
)
|
||||||
if str(chat.id).startswith("-100")
|
|
||||||
else f"Chat ID: {chat.id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Escape reason
|
# Escape reason
|
||||||
reason_escaped = escape_markdown_v2(reason)
|
reason_escaped = escape_markdown_v2(reason)
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
full_message = (
|
header = (
|
||||||
f"🔥 *Найдена успешка!*\n"
|
f"🔥 *Найдена успешка!*\n"
|
||||||
f"👤 *Пользователь:* {user_link}\n"
|
f"👤 *Пользователь:* {user_link}\n"
|
||||||
f"🐩 *Чат:* [{chat_title}]({chat_link})\n"
|
f"🐩 *Чат:* [{chat_title}]({chat_link})\n"
|
||||||
@ -87,38 +77,57 @@ def create_and_format_message(
|
|||||||
)
|
)
|
||||||
|
|
||||||
MAX_LENGTH = 4096
|
MAX_LENGTH = 4096
|
||||||
|
header_length = len(header)
|
||||||
|
available_length = MAX_LENGTH - header_length - 50 # Extra buffer for safety
|
||||||
|
|
||||||
|
message_blocks = []
|
||||||
|
current_length = 0
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
if not msg.text:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Clean and escape text
|
||||||
|
clean_text = msg.text.replace('```', 'ʻʻʻ') # Replace triple backticks
|
||||||
|
escaped_text = escape_markdown_v2(clean_text)
|
||||||
|
escaped_text = validate_markdown(escaped_text)
|
||||||
|
|
||||||
|
sender_username = escape_markdown_v2(
|
||||||
|
msg.user_relationship.username or f"ID:{msg.user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create message block with proper code block formatting
|
||||||
|
block = (
|
||||||
|
f"**{sender_username}:**\n"
|
||||||
|
f"```\n"
|
||||||
|
f"{escaped_text}\n"
|
||||||
|
f"```\n\n"
|
||||||
|
)
|
||||||
|
block_length = len(block)
|
||||||
|
|
||||||
|
if current_length + block_length > available_length:
|
||||||
|
remaining_space = available_length - current_length
|
||||||
|
if remaining_space > 30: # Enough space for a truncated message
|
||||||
|
truncated_text = escaped_text[:remaining_space - 30].rsplit(' ', 1)[0] + "..."
|
||||||
|
block = (
|
||||||
|
f"**{sender_username}:**\n"
|
||||||
|
f"```\n"
|
||||||
|
f"{truncated_text}\n"
|
||||||
|
f"```\n\n"
|
||||||
|
f"_... сообщение обрезано ..._\n"
|
||||||
|
)
|
||||||
|
message_blocks.append(block)
|
||||||
|
break
|
||||||
|
|
||||||
|
message_blocks.append(block)
|
||||||
|
current_length += block_length
|
||||||
|
|
||||||
|
content = ''.join(message_blocks).strip()
|
||||||
|
full_message = header + content
|
||||||
|
|
||||||
# Final validation and truncation if needed
|
# Final validation and truncation if needed
|
||||||
full_message = validate_markdown(full_message)
|
full_message = validate_markdown(full_message)
|
||||||
if len(full_message) > MAX_LENGTH:
|
if len(full_message) > MAX_LENGTH:
|
||||||
full_message = full_message[:MAX_LENGTH - 30].rsplit('\n', 2)[0] + "\n```\n...\n```\n_... сообщение обрезано ..._"
|
full_message = full_message[:MAX_LENGTH - 30].rsplit('\n', 2)[0] + "\n```\n...\n```\n_... сообщение обрезано ..._"
|
||||||
|
|
||||||
return full_message
|
return full_message
|
||||||
|
|
||||||
|
|
||||||
def handle_chat_type(
|
|
||||||
chat: TELETHON_CHAT_TYPES
|
|
||||||
) -> tuple[CustomChatTypes, str | None]:
|
|
||||||
chat_username = None
|
|
||||||
if isinstance(chat, UserTelethon):
|
|
||||||
chat_type = CustomChatTypes.PRIVATE_GROUP
|
|
||||||
elif isinstance(chat, Chat):
|
|
||||||
chat_type = CustomChatTypes.STANDARD_GROUP
|
|
||||||
elif isinstance(chat, Channel):
|
|
||||||
chat_username = chat.username
|
|
||||||
if chat.megagroup:
|
|
||||||
chat_type = CustomChatTypes.SUPERGROUP
|
|
||||||
else:
|
|
||||||
chat_type = CustomChatTypes.BROADCAST_CHANNEL
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid chat type check it!{chat}")
|
|
||||||
|
|
||||||
return chat_type, chat_username
|
|
||||||
|
|
||||||
|
|
||||||
def create_text_for_error_message_success(
|
|
||||||
text: str
|
|
||||||
) -> str:
|
|
||||||
text = escape_markdown_v2(text)
|
|
||||||
return validate_markdown(text)
|
|
@ -25,20 +25,15 @@ async def bulk_create_success_reasons(
|
|||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def get_messages_by_slice_id_and_user_id(
|
async def get_messages_by_slice_id(
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
slice_id: UUID,
|
slice_id: UUID
|
||||||
user_id: int,
|
|
||||||
) -> list[TgMessage]:
|
) -> list[TgMessage]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(TgMessage)
|
select(TgMessage)
|
||||||
.options(joinedload(TgMessage.user_relationship))
|
.options(joinedload(TgMessage.user_relationship))
|
||||||
.where(
|
.where(
|
||||||
TgMessage.slice_id == slice_id,
|
TgMessage.slice_id == slice_id
|
||||||
TgMessage.user_id == user_id,
|
|
||||||
)
|
|
||||||
.order_by(
|
|
||||||
TgMessage.message_time.asc()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@ from faststream import Depends
|
|||||||
|
|
||||||
from src.core.database import User, TgChat
|
from src.core.database import User, TgChat
|
||||||
from src.core.tg_service.notify_success import notify_for_success
|
from src.core.tg_service.notify_success import notify_for_success
|
||||||
from src.core.workers.schemas import ResponseFromGeminiSchema, SuccessChatFromAiSchema
|
from src.core.workers.schemas import ResponseFromGeminiSchema
|
||||||
from src.core.database.connect import db_helper
|
from src.core.database.connect import db_helper
|
||||||
from src.core.rabbitmq.connect import success_gemini_subscriber
|
from src.core.rabbitmq.connect import success_gemini_subscriber
|
||||||
from src.core.workers import crud as workers_crud
|
from src.core.workers import crud as workers_crud
|
||||||
@ -17,25 +17,27 @@ async def create_success_record(
|
|||||||
message: ResponseFromGeminiSchema,
|
message: ResponseFromGeminiSchema,
|
||||||
session: Annotated[AsyncSession, Depends(db_helper.get_async_session)],
|
session: Annotated[AsyncSession, Depends(db_helper.get_async_session)],
|
||||||
):
|
):
|
||||||
await workers_crud.bulk_create_success_reasons(
|
print(message.success)
|
||||||
success_schema=message,
|
await workers_crud.bulk_create_success_reasons(
|
||||||
|
success_schema=message,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
for success_message in message.success:
|
||||||
|
messages = await workers_crud.get_messages_by_slice_id(
|
||||||
session=session,
|
session=session,
|
||||||
|
slice_id=success_message.slice_id,
|
||||||
)
|
)
|
||||||
for success_message in message.success:
|
user_model = await session.get(User, success_message.user_id)
|
||||||
messages = await workers_crud.get_messages_by_slice_id_and_user_id(
|
chat_model = await session.get(TgChat, messages[0].chat_id)
|
||||||
session=session,
|
|
||||||
slice_id=success_message.slice_id,
|
|
||||||
user_id=success_message.user_id
|
|
||||||
)
|
|
||||||
user_model = await session.get(User, success_message.user_id)
|
|
||||||
chat_model = await session.get(TgChat, messages[0].chat_id)
|
|
||||||
|
|
||||||
await notify_for_success(
|
|
||||||
messages=messages,
|
await notify_for_success(
|
||||||
chat=chat_model, # type:ignore
|
messages=messages,
|
||||||
user_model=user_model, # type:ignore
|
chat=chat_model, # type:ignore
|
||||||
success_reason=success_message.reason, # type:ignore
|
user_model=user_model, # type:ignore
|
||||||
)
|
success_reason=success_message.reason, # type:ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,44 +1,27 @@
|
|||||||
import asyncio
|
from pyrogram import Client, filters, idle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from pyrogram import filters, idle
|
|
||||||
from pyrogram.handlers import MessageHandler
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
|
from src.core.settings.base import settings
|
||||||
from src.core.tg_service.messages_handler import message_listener
|
from src.core.tg_service.messages_handler import message_listener
|
||||||
from src.core.rabbitmq.connect import broker, init_queue_and_publisher
|
from src.core.rabbitmq.connect import broker, init_queue_and_publisher
|
||||||
from src.telethon_client import telethon_client
|
|
||||||
|
|
||||||
# # app.add_handler(MessageHandler(
|
|
||||||
# # callback=message_listener,
|
|
||||||
# # filters=filters.all
|
|
||||||
# # ))
|
|
||||||
|
|
||||||
from telethon import TelegramClient, events, types
|
|
||||||
|
|
||||||
from src.core.settings.base import settings
|
|
||||||
|
|
||||||
# app = TelegramClient(
|
|
||||||
# settings.ACCOUNT.NAME,
|
|
||||||
# api_id=settings.ACCOUNT.API_ID,
|
|
||||||
# api_hash=settings.ACCOUNT.API_HASH,
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# @app.on(events.NewMessage)
|
|
||||||
# async def my_event_handler(event):
|
|
||||||
# print(event.message)
|
|
||||||
|
|
||||||
|
|
||||||
# telethon_client.start()
|
app = Client(
|
||||||
# telethon_client.run_until_disconnected()
|
name=settings.ACCOUNT.NAME,
|
||||||
|
api_id=settings.ACCOUNT.API_ID,
|
||||||
|
api_hash=settings.ACCOUNT.API_HASH,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_handler(MessageHandler(
|
||||||
|
callback=message_listener,
|
||||||
|
filters=filters.all
|
||||||
|
))
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
await app.start()
|
||||||
await broker.start()
|
await broker.start()
|
||||||
await init_queue_and_publisher()
|
await init_queue_and_publisher()
|
||||||
|
await idle()
|
||||||
|
await app.stop()
|
||||||
|
|
||||||
await telethon_client.start()
|
app.run(main())
|
||||||
await telethon_client.run_until_disconnected()
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
|
|
||||||
from telethon import TelegramClient
|
|
||||||
from telethon.sessions import StringSession
|
|
||||||
|
|
||||||
from src.core.settings.base import settings
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
|
||||||
telethon_client = TelegramClient(
|
|
||||||
session=StringSession(settings.ACCOUNT.SESSION),
|
|
||||||
api_id=settings.ACCOUNT.API_ID,
|
|
||||||
api_hash=settings.ACCOUNT.API_HASH,
|
|
||||||
loop=loop,
|
|
||||||
)
|
|
BIN
telegram-application/src/tg_account.session
Normal file
BIN
telegram-application/src/tg_account.session
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user