diff --git a/configs/hafmc.toml b/configs/hafmc.toml deleted file mode 100644 index daf79d7..0000000 --- a/configs/hafmc.toml +++ /dev/null @@ -1,43 +0,0 @@ -[twitch] -id = 59900845 -name = "hafmc" - -[notifications] -start_stream = ''' -HafMC стримит {title} ({category})! -Присоединяйся: https://twitch.tv/hafmc - -{role} -''' - -change_category = ''' -HafMC играет в {category} ({title})! -Присоединяйся: https://twitch.tv/hafmc - -{role} -''' - - -[integrations] - -[integrations.discord] -guild_id = 1198051900906549329 -notifications_channel_id = 1198296540964475002 - -[integrations.discord.games_list] -channel_id = 1201810638800691210 -message_id = 1239664178038313012 - -[integrations.discord.roles] -"Counter-Strike" = 1198372462476398823 -"SIGame" = 1198372523046346833 -"Battlefield 1" = 1198377604533735454 -"Battlefield V" = 1198377604533735454 -"Among Us" = 1198377604533735454 -"BattleBit Remastered" = 1198381867343286352 -"Lethal Company" = 1198383849785270302 -"HELLDIVERS 2" = 1230292196805054488 -"Satisfactory" = 1292091826802528308 - -[integrations.telegram] -notifications_channel_id = -1001939021131 diff --git a/configs/ssstano.toml b/configs/ssstano.toml deleted file mode 100644 index 9b02419..0000000 --- a/configs/ssstano.toml +++ /dev/null @@ -1,18 +0,0 @@ -[twitch] -id = 188615689 -name = "ssstano" - - -[notifications] -start_stream = ''' -ssstano ебашит LIVE стрим для everyone - -https://www.twitch.tv/ssstano -https://www.twitch.tv/ssstano -https://www.twitch.tv/ssstano -''' - -[integrations] - -[integrations.telegram] -notifications_channel_id = -1002152372995 diff --git a/docker/build.dockerfile b/docker/build.dockerfile index a7c2410..bc67646 100644 --- a/docker/build.dockerfile +++ b/docker/build.dockerfile @@ -15,7 +15,6 @@ FROM python:3.12-slim AS runtime RUN apt update && apt install -y --no-install-recommends netcat-traditional wkhtmltopdf && apt clean COPY ./src/ /app -COPY ./configs/ /app/configs ENV PATH="/opt/venv/bin:$PATH" ENV VENV_PATH=/opt/venv diff --git a/src/core/config.py b/src/core/config.py index c4ea511..9b2559e 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -1,39 +1,4 @@ -import tomllib - -from pydantic import BaseModel, field_validator from pydantic_settings import BaseSettings -from pathlib import Path - - -class TwitchConfig(BaseModel): - id: int - name: str - -class NotificationsConfig(BaseModel): - start_stream: str - change_category: str | None = None - -class GamesListConfig(BaseModel): - channel_id: int - message_id: int - -class DiscordConfig(BaseModel): - guild_id: int - notifications_channel_id: int - games_list: GamesListConfig | None = None - roles: dict[str, int] | None = None - -class TelegramConfig(BaseModel): - notifications_channel_id: int - -class IntegrationsConfig(BaseModel): - discord: DiscordConfig | None = None - telegram: TelegramConfig | None = None - -class StreamerConfig(BaseModel): - twitch: TwitchConfig - notifications: NotificationsConfig - integrations: IntegrationsConfig class Config(BaseSettings): @@ -51,22 +16,9 @@ class Config(BaseSettings): TWITCH_CALLBACK_URL: str TWITCH_CALLBACK_PORT: int = 80 - STREAMERS: list[StreamerConfig] = [] - MONGODB_URI: str SECRETS_FILE_PATH: str - @field_validator("STREAMERS", mode="before") - def check_streamers(cls, value): - config_dir = Path("/app/configs") - streamers = [] - for toml_file in config_dir.glob("*.toml"): - if toml_file.is_file(): - with open(toml_file, "rb") as f: - streamer_config = tomllib.load(f) - streamers.append(StreamerConfig(**streamer_config)) - return streamers if streamers else value - config = Config() # type: ignore diff --git a/src/domain/streamers.py b/src/domain/streamers.py new file mode 100644 index 0000000..dbfb09f --- /dev/null +++ b/src/domain/streamers.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel + +class TwitchConfig(BaseModel): + id: int + name: str + +class NotificationsConfig(BaseModel): + start_stream: str + change_category: str | None = None + +class GamesListConfig(BaseModel): + channel_id: int + message_id: int + +class DiscordConfig(BaseModel): + guild_id: int + notifications_channel_id: int + games_list: GamesListConfig | None = None + roles: dict[str, int] | None = None + +class TelegramConfig(BaseModel): + notifications_channel_id: int + +class IntegrationsConfig(BaseModel): + discord: DiscordConfig | None = None + telegram: TelegramConfig | None = None + +class StreamerConfig(BaseModel): + twitch: TwitchConfig + notifications: NotificationsConfig + integrations: IntegrationsConfig diff --git a/src/modules/games_list/discord.py b/src/modules/games_list/discord.py index 145283b..861200c 100644 --- a/src/modules/games_list/discord.py +++ b/src/modules/games_list/discord.py @@ -8,15 +8,18 @@ from discord import app_commands from modules.games_list.games_list import GameList, GameItem from core.config import config +from repositories.streamers import StreamerConfigRepository logger = logging.getLogger(__name__) -def get_game_list_channel_to_message_map() -> dict[int, int]: +async def get_game_list_channel_to_message_map() -> dict[int, int]: result = {} - for streamer in config.STREAMERS: + streamers = await StreamerConfigRepository.all() + + for streamer in streamers: if (integration := streamer.integrations.discord) is None: continue @@ -41,7 +44,9 @@ class DiscordClient(discord.Client): self.tree = app_commands.CommandTree(self) async def setup_hook(self): - for streamer in config.STREAMERS: + streamers = await StreamerConfigRepository.all() + + for streamer in streamers: if (integration := streamer.integrations.discord) is None: continue @@ -82,7 +87,7 @@ async def add( game: str, date: str | None = None ): - channel_to_message = get_game_list_channel_to_message_map() + channel_to_message = await get_game_list_channel_to_message_map() if interaction.channel is None: await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True) @@ -114,7 +119,7 @@ async def game_list_autocomplete( if not isinstance(interaction.channel, Messageable): return [] - channel_to_message = get_game_list_channel_to_message_map() + channel_to_message = await get_game_list_channel_to_message_map() message_id = channel_to_message.get(interaction.channel.id) if message_id is None: return [] @@ -130,7 +135,7 @@ async def game_list_autocomplete( @app_commands.describe(game="Игра") @app_commands.autocomplete(game=game_list_autocomplete) async def delete(interaction: discord.Interaction, game: str): - channel_to_message = get_game_list_channel_to_message_map() + channel_to_message = await get_game_list_channel_to_message_map() if interaction.channel is None: await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True) diff --git a/src/modules/scheduler_sync/synchronizer.py b/src/modules/scheduler_sync/synchronizer.py index e1909c3..281e9a0 100644 --- a/src/modules/scheduler_sync/synchronizer.py +++ b/src/modules/scheduler_sync/synchronizer.py @@ -2,7 +2,10 @@ from asyncio import sleep import logging from datetime import datetime -from core.config import config, TwitchConfig +from core.config import config + +from domain.streamers import TwitchConfig +from repositories.streamers import StreamerConfigRepository from .twitch_events import get_twitch_events, TwitchEvent from .discord_events import ( @@ -101,7 +104,9 @@ async def start_synchronizer(): while True: try: - for streamer in config.STREAMERS: + streamers = await StreamerConfigRepository().all() + + for streamer in streamers: if (integration := streamer.integrations.discord) is None: continue @@ -109,4 +114,4 @@ async def start_synchronizer(): except Exception as e: logging.error(e) - await sleep(5 * 30) + await sleep(5 * 60) diff --git a/src/modules/stream_notifications/twitch/twitch.py b/src/modules/stream_notifications/twitch/twitch.py index 0961474..4b3cee8 100644 --- a/src/modules/stream_notifications/twitch/twitch.py +++ b/src/modules/stream_notifications/twitch/twitch.py @@ -8,8 +8,9 @@ from twitchAPI.twitch import Twitch from twitchAPI.type import AuthScope from twitchAPI.object.eventsub import StreamOnlineEvent, ChannelUpdateEvent -from core.config import config, StreamerConfig +from core.config import config from modules.stream_notifications.notification import notify +from repositories.streamers import StreamerConfigRepository from .state import State from .token_storage import TokenStorage @@ -50,19 +51,12 @@ class TwitchService: return twitch - def get_streamer_config(self, streamer_id: int) -> StreamerConfig: - for streamer in config.STREAMERS: - if streamer.twitch.id == streamer_id: - return streamer - - raise ValueError(f"Streamer with id {streamer_id} not found") - async def notify_online(self, streamer_id: int): current_state = self.state.get(streamer_id) if current_state is None: raise RuntimeError("State is None") - streamer = self.get_streamer_config(streamer_id) + streamer = await StreamerConfigRepository.get_by_twitch_id(streamer_id) if streamer.notifications.start_stream is None: return @@ -78,7 +72,7 @@ class TwitchService: if (datetime.now() - current_state.last_live_at).seconds >= self.ONLINE_NOTIFICATION_DELAY: return - streamer = self.get_streamer_config(streamer_id) + streamer = await StreamerConfigRepository.get_by_twitch_id(streamer_id) if streamer.notifications.change_category is None: return @@ -154,7 +148,9 @@ class TwitchService: message_deduplication_history_length=50 ) - for streamer in config.STREAMERS: + streamers = await StreamerConfigRepository.all() + + for streamer in streamers: current_stream = await self.get_current_stream(streamer.twitch.id) if current_stream: @@ -173,7 +169,7 @@ class TwitchService: logger.info("Subscribe to events...") - for streamer in config.STREAMERS: + for streamer in streamers: logger.info(f"Subscribe to events for {streamer.twitch.name}") await eventsub.listen_channel_update_v2(str(streamer.twitch.id), self.on_channel_update) await eventsub.listen_stream_online(str(streamer.twitch.id), self.on_stream_online) @@ -184,7 +180,7 @@ class TwitchService: while True: await sleep(self.UPDATE_DELAY) - for streamer in config.STREAMERS: + for streamer in streamers: await self._on_stream_online(streamer.twitch.id) finally: await eventsub.stop() diff --git a/src/repositories/streamers.py b/src/repositories/streamers.py new file mode 100644 index 0000000..9d5f851 --- /dev/null +++ b/src/repositories/streamers.py @@ -0,0 +1,28 @@ +from domain.streamers import StreamerConfig + +from core.mongo import mongo_manager + + +class StreamerConfigRepository: + COLLECTION_NAME = "streamers" + + @classmethod + async def get_by_twitch_id(cls, twitch_id: int) -> StreamerConfig: + async with mongo_manager.connect() as client: + db = client.get_default_database() + collection = db[cls.COLLECTION_NAME] + + doc = await collection.find_one({"twitch.id": twitch_id}) + if doc is None: + raise ValueError(f"Streamer with twitch id {twitch_id} not found") + + return StreamerConfig(**doc) + + @classmethod + async def all(cls) -> list[StreamerConfig]: + async with mongo_manager.connect() as client: + db = client.get_default_database() + collection = db[cls.COLLECTION_NAME] + + cursor = await collection.find() + return [StreamerConfig(**doc) async for doc in cursor]