From 055e9579da660959cd281cb90ab19d69afc075a1 Mon Sep 17 00:00:00 2001 From: Bulat Kurbanov Date: Fri, 4 Oct 2024 18:48:32 +0200 Subject: [PATCH] Update --- .gitignore | 1 + configs/hafmc.toml | 29 +++++++++++++ configs/ssstano.toml | 18 ++++++++ docker/build.dockerfile | 1 + src/config.py | 48 +++++++++++++-------- src/services/discord.py | 17 +++++--- src/services/notification.py | 24 ++++++----- src/services/scheduler_sync/synchronizer.py | 10 ++--- src/services/twitch.py | 42 +++++++++--------- 9 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 configs/hafmc.toml create mode 100644 configs/ssstano.toml diff --git a/.gitignore b/.gitignore index 4940046..abc591b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv +.DS_Store diff --git a/configs/hafmc.toml b/configs/hafmc.toml new file mode 100644 index 0000000..7e6cc38 --- /dev/null +++ b/configs/hafmc.toml @@ -0,0 +1,29 @@ +[twitch] +id = 59900845 +name = "hafmc" + + +[notifications] +start_stream = ''' +HafMC сейчас стримит {title} ({category})! +Присоединяйся: https://twitch.tv/hafmc +''' + +change_category = ''' +HafMC начал играть в {category}! +Присоединяйся: https://twitch.tv/hafmc +''' + + +[integrations] + +[integrations.discord] +guild_id = 1198051900906549329 +notifications_channel_id = 1198296540964475002 + +[integrations.discord.games_list] +channel_id = 1201810638800691210 +message_id = 1239664178038313012 + +[integrations.telegram] +notifications_channel_id = -1001939021131 diff --git a/configs/ssstano.toml b/configs/ssstano.toml new file mode 100644 index 0000000..9b02419 --- /dev/null +++ b/configs/ssstano.toml @@ -0,0 +1,18 @@ +[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 bc67646..a7c2410 100644 --- a/docker/build.dockerfile +++ b/docker/build.dockerfile @@ -15,6 +15,7 @@ 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/config.py b/src/config.py index a6059d0..baf1ccd 100644 --- a/src/config.py +++ b/src/config.py @@ -1,29 +1,38 @@ -import json +import tomllib from pydantic import BaseModel, field_validator from pydantic_settings import BaseSettings +from pathlib import Path class TwitchConfig(BaseModel): - CHANNEL_ID: str - CHANNEL_NAME: str + 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 - CHANNEL_ID: int + guild_id: int + notifications_channel_id: int + games_list: GamesListConfig | None = None - GAME_LIST_CHANNEL_ID: int - GAME_LIST_MESSAGE_ID: int +class TelegramConfig(BaseModel): + notifications_channel_id: int +class IntegrationsConfig(BaseModel): + discord: DiscordConfig | None = None + telegram: TelegramConfig | None = None class StreamerConfig(BaseModel): - TWITCH: TwitchConfig - DISCORD: DiscordConfig | None = None - TELEGRAM_CHANNEL_ID: int | None = None - - START_STREAM_MESSAGE: str | None = None - CHANGE_CATEGORY_MESSAGE: str | None = None + twitch: TwitchConfig + notifications: NotificationsConfig + integrations: IntegrationsConfig class Config(BaseSettings): @@ -45,13 +54,16 @@ class Config(BaseSettings): SECRETS_FILE_PATH: str - @field_validator("STREAMERS", mode="before") def check_streamers(cls, value): - if isinstance(value, str): - return json.loads(value) - - return 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/services/discord.py b/src/services/discord.py index c5313eb..85623b3 100644 --- a/src/services/discord.py +++ b/src/services/discord.py @@ -17,13 +17,16 @@ def get_game_list_channel_to_message_map() -> dict[int, int]: result = {} for streamer in config.STREAMERS: - if streamer.DISCORD is None: + if (integration := streamer.integrations.discord) is None: continue - if streamer.DISCORD.GAME_LIST_CHANNEL_ID is None or streamer.DISCORD.GAME_LIST_MESSAGE_ID is None: + if (games_list := integration.games_list) is None: continue - result[streamer.DISCORD.GAME_LIST_CHANNEL_ID] = streamer.DISCORD.GAME_LIST_MESSAGE_ID + if games_list.channel_id is None or games_list.message_id is None: + continue + + result[games_list.channel_id] = games_list.message_id return result @@ -39,14 +42,14 @@ class DiscordClient(discord.Client): async def setup_hook(self): for streamer in config.STREAMERS: - if streamer.DISCORD is None: + if (integration := streamer.integrations.discord) is None: continue - if streamer.DISCORD.GAME_LIST_CHANNEL_ID is None or streamer.DISCORD.GAME_LIST_MESSAGE_ID is None: + if integration.games_list is None: continue - self.tree.copy_global_to(guild=Object(id=streamer.DISCORD.GUILD_ID)) - await self.tree.sync(guild=Object(id=streamer.DISCORD.GUILD_ID)) + self.tree.copy_global_to(guild=Object(id=integration.guild_id)) + await self.tree.sync(guild=Object(id=integration.guild_id)) async def on_ready(self): await self.change_presence( diff --git a/src/services/notification.py b/src/services/notification.py index 162a94c..91931d6 100644 --- a/src/services/notification.py +++ b/src/services/notification.py @@ -33,14 +33,18 @@ async def notify_discord(msg: str, channel_id: str): async def notify(msg: str, streamer_config: StreamerConfig): - if streamer_config.DISCORD is not None: - try: - await notify_discord(msg, str(streamer_config.DISCORD.CHANNEL_ID)) - except Exception as e: - logger.error("Failed to notify discord", exc_info=e) + integrations = streamer_config.integrations - if streamer_config.TELEGRAM_CHANNEL_ID is not None: - try: - await notify_telegram(msg, str(streamer_config.TELEGRAM_CHANNEL_ID)) - except Exception as e: - logger.error("Failed to notify telegram", exc_info=e) + if (discord := integrations.discord) is not None: + if discord.notifications_channel_id is not None: + try: + await notify_discord(msg, str(discord.notifications_channel_id)) + except Exception as e: + logger.error("Failed to notify discord", exc_info=e) + + if (telegram := integrations.telegram) is not None: + if telegram.notifications_channel_id is not None: + try: + await notify_telegram(msg, str(telegram.notifications_channel_id)) + except Exception as e: + logger.error("Failed to notify telegram", exc_info=e) diff --git a/src/services/scheduler_sync/synchronizer.py b/src/services/scheduler_sync/synchronizer.py index cc2180c..e6b630e 100644 --- a/src/services/scheduler_sync/synchronizer.py +++ b/src/services/scheduler_sync/synchronizer.py @@ -82,7 +82,7 @@ async def edit_events( async def syncronize(twitch: TwitchConfig, discord_guild_id: int): - twitch_events = await get_twitch_events(twitch.CHANNEL_ID) + twitch_events = await get_twitch_events(str(twitch.id)) discord_events = await get_discord_events(discord_guild_id) twitch_events_with_id = [(event.uid, event) for event in twitch_events] @@ -91,9 +91,9 @@ async def syncronize(twitch: TwitchConfig, discord_guild_id: int): for event in discord_events ] - await add_events(discord_guild_id, twitch.CHANNEL_NAME, twitch_events_with_id, discord_events_with_id) + await add_events(discord_guild_id, twitch.name, twitch_events_with_id, discord_events_with_id) await remove_events(discord_guild_id, twitch_events_with_id, discord_events_with_id) - await edit_events(discord_guild_id, twitch.CHANNEL_NAME, twitch_events_with_id, discord_events_with_id) + await edit_events(discord_guild_id, twitch.name, twitch_events_with_id, discord_events_with_id) async def start_synchronizer(): @@ -102,10 +102,10 @@ async def start_synchronizer(): while True: try: for streamer in config.STREAMERS: - if streamer.DISCORD is None: + if (integration := streamer.integrations.discord) is None: continue - await syncronize(streamer.TWITCH, streamer.DISCORD.GUILD_ID) + await syncronize(streamer.twitch, integration.guild_id) except Exception as e: logging.error(e) diff --git a/src/services/twitch.py b/src/services/twitch.py index 9fc4a08..9235447 100644 --- a/src/services/twitch.py +++ b/src/services/twitch.py @@ -60,7 +60,7 @@ class TwitchService: def __init__(self, twitch: Twitch): self.twitch = twitch - self.state: dict[str, State | None] = {} + self.state: dict[int, State | None] = {} @classmethod async def authorize(cls): @@ -78,31 +78,31 @@ class TwitchService: return twitch - def get_streamer_config(self, streamer_id: str) -> StreamerConfig: + def get_streamer_config(self, streamer_id: int) -> StreamerConfig: for streamer in config.STREAMERS: - if streamer.TWITCH.CHANNEL_ID == streamer_id: + 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: str): + 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) - if streamer.START_STREAM_MESSAGE is None: + if streamer.notifications.start_stream is None: return - msg = streamer.START_STREAM_MESSAGE.replace("\\n", "\n").format( + msg = streamer.notifications.start_stream.format( title=current_state.title, category=current_state.category ) await notify(msg, streamer) - async def notify_change_category(self, streamer_id: str): + async def notify_change_category(self, streamer_id: int): current_state = self.state.get(streamer_id) if current_state is None: @@ -113,20 +113,21 @@ class TwitchService: streamer = self.get_streamer_config(streamer_id) - if streamer.CHANGE_CATEGORY_MESSAGE is None: + if streamer.notifications.change_category is None: return - msg = streamer.CHANGE_CATEGORY_MESSAGE.replace("\\n", "\n").format( + msg = streamer.notifications.change_category.format( + title=current_state.title, category=current_state.category ) await notify(msg, streamer) - async def get_current_stream(self, streamer_id: str, retry_count: int = 5, delay: int = 5): + async def get_current_stream(self, streamer_id: int, retry_count: int = 5, delay: int = 5): remain_retry = retry_count while remain_retry > 0: - stream = await first(self.twitch.get_streams(user_id=[streamer_id])) + stream = await first(self.twitch.get_streams(user_id=[str(streamer_id)])) if stream is not None: return stream @@ -137,7 +138,7 @@ class TwitchService: return None async def on_channel_update(self, event: ChannelUpdateEvent): - brodcaster_id = event.event.broadcaster_user_id + brodcaster_id = int(event.event.broadcaster_user_id) stream = await self.get_current_stream(brodcaster_id) if stream is None: @@ -158,7 +159,7 @@ class TwitchService: if changed: await self.notify_change_category(brodcaster_id) - async def _on_stream_online(self, streamer_id: str): + async def _on_stream_online(self, streamer_id: int): current_stream = await self.get_current_stream(streamer_id) if current_stream is None: return @@ -180,7 +181,7 @@ class TwitchService: await self.notify_online(streamer_id) async def on_stream_online(self, event: StreamOnlineEvent): - await self._on_stream_online(event.event.broadcaster_user_id) + await self._on_stream_online(int(event.event.broadcaster_user_id)) async def run(self): eventsub = EventSubWebhook( @@ -191,15 +192,16 @@ class TwitchService: ) for streamer in config.STREAMERS: - current_stream = await self.get_current_stream(streamer.TWITCH.CHANNEL_ID) + current_stream = await self.get_current_stream(streamer.twitch.id) + if current_stream: - self.state[streamer.TWITCH.CHANNEL_ID] = State( + self.state[streamer.twitch.id] = State( title=current_stream.title, category=current_stream.game_name, last_live_at=datetime.now() ) else: - self.state[streamer.TWITCH.CHANNEL_ID] = None + self.state[streamer.twitch.id] = None try: await eventsub.unsubscribe_all() @@ -209,8 +211,8 @@ class TwitchService: logger.info("Subscribe to events...") for streamer in config.STREAMERS: - await eventsub.listen_channel_update_v2(streamer.TWITCH.CHANNEL_ID, self.on_channel_update) - await eventsub.listen_stream_online(streamer.TWITCH.CHANNEL_ID, self.on_stream_online) + 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) logger.info("Twitch service started") @@ -218,7 +220,7 @@ class TwitchService: await sleep(self.UPDATE_DELAY) for streamer in config.STREAMERS: - await self._on_stream_online(streamer.TWITCH.CHANNEL_ID) + await self._on_stream_online(streamer.twitch.id) finally: await eventsub.stop() await self.twitch.close()