Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
6 changes: 6 additions & 0 deletions src/fph/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import annotations

from .router import router


__all__ = ['router']
27 changes: 27 additions & 0 deletions src/fph/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

from funpayhub.lib.properties import ListParameter

from funpayhub.app.dispatching import Router

from ..types import BotRotater


router = Router(name='chat_sync')


@router.on_parameter_value_changed(
lambda parameter, plugin_properties: parameter is plugin_properties.bot_tokens,
)
async def sync_bot_tokens(
parameter: ListParameter[str],
chat_sync_rotater: BotRotater,
) -> None:
new_tokens = set(parameter.value)
current_tokens = chat_sync_rotater.tokens

for token in new_tokens - current_tokens:
chat_sync_rotater.add_bot(token)

for token in current_tokens - new_tokens:
await chat_sync_rotater.remove_bot(token)
13 changes: 6 additions & 7 deletions src/funpay/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from funpayhub.app.telegram.ui.ids import MenuIds
from funpayhub.app.telegram.ui.builders.context import NewMessageMenuContext

from ..logger import logger


if TYPE_CHECKING:
from aiogram import Bot as TGBot
Expand Down Expand Up @@ -99,12 +101,7 @@ async def sync_new_message(


async def send_message_task(chat_id: int, thread_id: int, rotater: BotRotater, menu: Menu) -> None:
while True:
try:
bot = rotater.next_bot()
except StopIteration:
return

for bot in rotater.snapshot():
try:
await bot.send_message(
chat_id=chat_id,
Expand All @@ -113,4 +110,6 @@ async def send_message_task(chat_id: int, thread_id: int, rotater: BotRotater, m
)
return
except TelegramUnauthorizedError:
rotater.remove_bot(bot.token)
await rotater.remove_bot(bot.token)
except Exception:
logger.exception('Failed to send message to %d (%d thread)', chat_id, thread_id)
6 changes: 6 additions & 0 deletions src/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import annotations

from funpayhub.app.plugin.plugin import get_plugin_logger


logger = get_plugin_logger('com.github.qvvonk.funpayhub.chat_sync_plugin')
6 changes: 6 additions & 0 deletions src/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from funpayhub.app.plugin import Plugin

from .types import Registry, BotRotater
from .fph.router import router as chat_sync_hub_router
from .properties import ChatSyncProperties
from .funpay.router import router as chat_sync_fp_router
from .telegram.router import router as chat_sync_tg_router
Expand All @@ -16,6 +17,8 @@
from aiogram import Router as TGRouter
from funpaybotengine import Router as FPRouter

from funpayhub.app.dispatching import Router as HubRouter


class ChatSyncPlugin(Plugin):
_registry: Registry | None = None
Expand All @@ -28,6 +31,9 @@ async def pre_setup(self) -> None:
async def properties(self) -> ChatSyncProperties:
return ChatSyncProperties()

async def hub_routers(self) -> HubRouter:
return chat_sync_hub_router

async def funpay_routers(self) -> FPRouter:
return chat_sync_fp_router

Expand Down
24 changes: 17 additions & 7 deletions src/telegram/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
from io import BytesIO

from aiogram import Router
from aiogram.types import Message, ReactionTypeEmoji
from aiogram.filters import Command
from funpaybotengine.exceptions import FunPayBotEngineError


if TYPE_CHECKING:
from aiogram.types import Message, ReactionTypeEmoji
from chat_sync.src.types import Registry
from chat_sync.src.properties import ChatSyncProperties

Expand All @@ -37,7 +37,7 @@ async def need_to_resend(
async def setup_chat_sync_chat(
message: Message,
plugin_properties: ChatSyncProperties,
):
) -> None:
if plugin_properties.sync_chat_id.value:
await message.answer(
'❌ Sync-чат уже установлен. '
Expand All @@ -53,15 +53,25 @@ async def setup_chat_sync_chat(
await message.answer('✅ Sync-чат установлен.')


@r.message(~Command(re.compile('\S+')), need_to_resend)
async def send_to_funpay_chat(message: Message, chat_sync_registry: Registry, hub: FunPayHub):
@r.message(~Command(re.compile(r'\S+')), need_to_resend)
async def send_to_funpay_chat(
message: Message,
chat_sync_registry: Registry,
hub: FunPayHub,
) -> None:
funpay_chat_id = chat_sync_registry.tg_to_fp_pairs[message.message_thread_id]

image: BytesIO | None = None
text: str | None = None

if message.photo:
file = await message.bot.get_file(message.photo[-1].file_id)
if message.photo or message.sticker:
if message.sticker and (message.sticker.is_animated or message.sticker.is_video):
emoji = ReactionTypeEmoji(emoji='🗿')
await message.react(reaction=[emoji], is_big=True)
return
file = await message.bot.get_file(
message.photo[-1].file_id if message.photo else message.sticker.file_id
)
buffer = BytesIO()
await message.bot.download_file(file.file_path, buffer)
image = buffer
Expand All @@ -80,7 +90,7 @@ async def send_to_funpay_chat(message: Message, chat_sync_registry: Registry, hu
try:
emoji = ReactionTypeEmoji(emoji='🎉')
await message.react(reaction=[emoji], is_big=True)
except:
except Exception:
pass
except FunPayBotEngineError:
emoji = ReactionTypeEmoji(emoji='💩')
Expand Down
31 changes: 18 additions & 13 deletions src/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
from types import MappingProxyType
from pathlib import Path
from collections.abc import Iterator
from collections.abc import Iterable

from aiogram import Bot
from aiogram.enums import ParseMode
Expand Down Expand Up @@ -122,9 +122,9 @@ def path(self) -> Path:

class BotRotater:
"""
Циклический итератор Telegram ботов.
Round-robin пул Telegram ботов.

Хранит набор ботов и поочерёдно возвращает их по кругу (round-robin).
Хранит набор ботов и поочерёдно возвращает их по кругу через `next_bot()`.
Поддерживает добавление и удаление ботов во время работы.
"""

Expand All @@ -134,40 +134,45 @@ class BotRotater:
link_preview_is_disabled=True,
)

def __init__(self, tokens) -> None:
self._tokens = set(tokens)
self._bots = [self._bot_from_token(token) for token in self._tokens]
def __init__(self, tokens: Iterable[str]) -> None:
self._tokens: set[str] = set(tokens)
self._bots: list[Bot] = [self._bot_from_token(token) for token in self._tokens]
self._current_bot_index = 0

def __next__(self) -> Bot:
def next_bot(self) -> Bot | None:
"""
Возвращает следующего бота по кругу или `None`, если ботов нет.
"""
if not self._bots:
raise StopIteration
return None

if self._current_bot_index > len(self._bots) - 1:
self._current_bot_index = 0
bot = self._bots[self._current_bot_index]
self._current_bot_index += 1
return bot

def __iter__(self) -> Iterator[Bot]:
return self
def snapshot(self) -> list[Bot]:
return list(self._bots)

def add_bot(self, token: str) -> None:
if token in self._tokens:
raise ValueError('Bot with this token already exists.')
self._tokens.add(token)
self._bots.append(self._bot_from_token(token))

def remove_bot(self, token: str) -> None:
async def remove_bot(self, token: str) -> None:
if token not in self._tokens:
return
self._tokens.discard(token)
for i in self._bots:
if i.token == token:
self._bots.remove(i)
await i.session.close()

def next_bot(self) -> Bot:
return next(self)
@property
def tokens(self) -> frozenset[str]:
return frozenset(self._tokens)

def _bot_from_token(self, token: str) -> Bot:
return Bot(token=token, default=self._DEFAULT_BOT_PROPERTIES)
Expand Down