mirror of
https://github.com/kurbezz/discord-bot.git
synced 2025-12-06 15:15:37 +01:00
Refactor streamers configs
This commit is contained in:
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
RUN apt update && apt install -y --no-install-recommends netcat-traditional wkhtmltopdf && apt clean
|
||||||
|
|
||||||
COPY ./src/ /app
|
COPY ./src/ /app
|
||||||
COPY ./configs/ /app/configs
|
|
||||||
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
ENV VENV_PATH=/opt/venv
|
ENV VENV_PATH=/opt/venv
|
||||||
|
|||||||
@@ -1,39 +1,4 @@
|
|||||||
import tomllib
|
|
||||||
|
|
||||||
from pydantic import BaseModel, field_validator
|
|
||||||
from pydantic_settings import BaseSettings
|
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):
|
class Config(BaseSettings):
|
||||||
@@ -51,22 +16,9 @@ class Config(BaseSettings):
|
|||||||
TWITCH_CALLBACK_URL: str
|
TWITCH_CALLBACK_URL: str
|
||||||
TWITCH_CALLBACK_PORT: int = 80
|
TWITCH_CALLBACK_PORT: int = 80
|
||||||
|
|
||||||
STREAMERS: list[StreamerConfig] = []
|
|
||||||
|
|
||||||
MONGODB_URI: str
|
MONGODB_URI: str
|
||||||
|
|
||||||
SECRETS_FILE_PATH: 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
|
config = Config() # type: ignore
|
||||||
|
|||||||
31
src/domain/streamers.py
Normal file
31
src/domain/streamers.py
Normal file
@@ -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
|
||||||
@@ -8,15 +8,18 @@ from discord import app_commands
|
|||||||
from modules.games_list.games_list import GameList, GameItem
|
from modules.games_list.games_list import GameList, GameItem
|
||||||
|
|
||||||
from core.config import config
|
from core.config import config
|
||||||
|
from repositories.streamers import StreamerConfigRepository
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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 = {}
|
result = {}
|
||||||
|
|
||||||
for streamer in config.STREAMERS:
|
streamers = await StreamerConfigRepository.all()
|
||||||
|
|
||||||
|
for streamer in streamers:
|
||||||
if (integration := streamer.integrations.discord) is None:
|
if (integration := streamer.integrations.discord) is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -41,7 +44,9 @@ class DiscordClient(discord.Client):
|
|||||||
self.tree = app_commands.CommandTree(self)
|
self.tree = app_commands.CommandTree(self)
|
||||||
|
|
||||||
async def setup_hook(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:
|
if (integration := streamer.integrations.discord) is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ async def add(
|
|||||||
game: str,
|
game: str,
|
||||||
date: str | None = None
|
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:
|
if interaction.channel is None:
|
||||||
await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True)
|
await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True)
|
||||||
@@ -114,7 +119,7 @@ async def game_list_autocomplete(
|
|||||||
if not isinstance(interaction.channel, Messageable):
|
if not isinstance(interaction.channel, Messageable):
|
||||||
return []
|
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)
|
message_id = channel_to_message.get(interaction.channel.id)
|
||||||
if message_id is None:
|
if message_id is None:
|
||||||
return []
|
return []
|
||||||
@@ -130,7 +135,7 @@ async def game_list_autocomplete(
|
|||||||
@app_commands.describe(game="Игра")
|
@app_commands.describe(game="Игра")
|
||||||
@app_commands.autocomplete(game=game_list_autocomplete)
|
@app_commands.autocomplete(game=game_list_autocomplete)
|
||||||
async def delete(interaction: discord.Interaction, game: str):
|
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:
|
if interaction.channel is None:
|
||||||
await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True)
|
await interaction.response.send_message("Команда не доступна в этом канале (#1)", ephemeral=True)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from asyncio import sleep
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
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 .twitch_events import get_twitch_events, TwitchEvent
|
||||||
from .discord_events import (
|
from .discord_events import (
|
||||||
@@ -101,7 +104,9 @@ async def start_synchronizer():
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
for streamer in config.STREAMERS:
|
streamers = await StreamerConfigRepository().all()
|
||||||
|
|
||||||
|
for streamer in streamers:
|
||||||
if (integration := streamer.integrations.discord) is None:
|
if (integration := streamer.integrations.discord) is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -109,4 +114,4 @@ async def start_synchronizer():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
|
|
||||||
await sleep(5 * 30)
|
await sleep(5 * 60)
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ from twitchAPI.twitch import Twitch
|
|||||||
from twitchAPI.type import AuthScope
|
from twitchAPI.type import AuthScope
|
||||||
from twitchAPI.object.eventsub import StreamOnlineEvent, ChannelUpdateEvent
|
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 modules.stream_notifications.notification import notify
|
||||||
|
from repositories.streamers import StreamerConfigRepository
|
||||||
|
|
||||||
from .state import State
|
from .state import State
|
||||||
from .token_storage import TokenStorage
|
from .token_storage import TokenStorage
|
||||||
@@ -50,19 +51,12 @@ class TwitchService:
|
|||||||
|
|
||||||
return twitch
|
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):
|
async def notify_online(self, streamer_id: int):
|
||||||
current_state = self.state.get(streamer_id)
|
current_state = self.state.get(streamer_id)
|
||||||
if current_state is None:
|
if current_state is None:
|
||||||
raise RuntimeError("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:
|
if streamer.notifications.start_stream is None:
|
||||||
return
|
return
|
||||||
@@ -78,7 +72,7 @@ class TwitchService:
|
|||||||
if (datetime.now() - current_state.last_live_at).seconds >= self.ONLINE_NOTIFICATION_DELAY:
|
if (datetime.now() - current_state.last_live_at).seconds >= self.ONLINE_NOTIFICATION_DELAY:
|
||||||
return
|
return
|
||||||
|
|
||||||
streamer = self.get_streamer_config(streamer_id)
|
streamer = await StreamerConfigRepository.get_by_twitch_id(streamer_id)
|
||||||
|
|
||||||
if streamer.notifications.change_category is None:
|
if streamer.notifications.change_category is None:
|
||||||
return
|
return
|
||||||
@@ -154,7 +148,9 @@ class TwitchService:
|
|||||||
message_deduplication_history_length=50
|
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)
|
current_stream = await self.get_current_stream(streamer.twitch.id)
|
||||||
|
|
||||||
if current_stream:
|
if current_stream:
|
||||||
@@ -173,7 +169,7 @@ class TwitchService:
|
|||||||
|
|
||||||
logger.info("Subscribe to events...")
|
logger.info("Subscribe to events...")
|
||||||
|
|
||||||
for streamer in config.STREAMERS:
|
for streamer in streamers:
|
||||||
logger.info(f"Subscribe to events for {streamer.twitch.name}")
|
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_channel_update_v2(str(streamer.twitch.id), self.on_channel_update)
|
||||||
await eventsub.listen_stream_online(str(streamer.twitch.id), self.on_stream_online)
|
await eventsub.listen_stream_online(str(streamer.twitch.id), self.on_stream_online)
|
||||||
@@ -184,7 +180,7 @@ class TwitchService:
|
|||||||
while True:
|
while True:
|
||||||
await sleep(self.UPDATE_DELAY)
|
await sleep(self.UPDATE_DELAY)
|
||||||
|
|
||||||
for streamer in config.STREAMERS:
|
for streamer in streamers:
|
||||||
await self._on_stream_online(streamer.twitch.id)
|
await self._on_stream_online(streamer.twitch.id)
|
||||||
finally:
|
finally:
|
||||||
await eventsub.stop()
|
await eventsub.stop()
|
||||||
|
|||||||
28
src/repositories/streamers.py
Normal file
28
src/repositories/streamers.py
Normal file
@@ -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]
|
||||||
Reference in New Issue
Block a user