diff --git a/config.py b/config.py index 4e44602..2970904 100644 --- a/config.py +++ b/config.py @@ -13,6 +13,7 @@ PLEX_TOKEN = os.getenv("PLEX_TOKEN") TWITCH_CLIENT_ID = os.getenv("TWITCH_CLIENT_ID") TWITCH_CLIENT_SECRET = os.getenv("TWITCH_CLIENT_SECRET") TWITCH_CHANNEL = os.getenv("TWITCH_CHANNEL") +YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") # List of enabled modules ENABLED_MODULES = [ @@ -20,4 +21,5 @@ ENABLED_MODULES = [ "modules.user.birthday_module", "modules.money.currency_module", "modules.social.twitch_module", + "modules.social.youtube_module", ] diff --git a/modules/data/db.py b/modules/data/db.py index c313e16..5c362bb 100644 --- a/modules/data/db.py +++ b/modules/data/db.py @@ -29,6 +29,18 @@ def initialize_db(): discord_channel_id INTEGER ) ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS followed_youtube_channels ( + youtube_channel_id TEXT PRIMARY KEY, + discord_channel_id INTEGER + ) + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS youtube_status ( + youtube_channel_id TEXT PRIMARY KEY, + last_video_id TEXT + ) + ''') conn.commit() conn.close() diff --git a/modules/social/youtube_module.py b/modules/social/youtube_module.py new file mode 100644 index 0000000..99ccb1a --- /dev/null +++ b/modules/social/youtube_module.py @@ -0,0 +1,129 @@ +# modules/social/youtube_module.py +import discord +from discord import app_commands +from googleapiclient.discovery import build +import logging +import config +import asyncio +from modules.data.db import get_connection + +logger = logging.getLogger(__name__) + + +class YouTubeModule: + def __init__(self, bot): + self.bot = bot + self.youtube = build('youtube', 'v3', developerKey=config.YOUTUBE_API_KEY) + self.bot.loop.create_task(self.check_youtube_channels()) + self.add_commands() + + async def check_youtube_channels(self): + while True: + conn = get_connection() + c = conn.cursor() + c.execute('SELECT youtube_channel_id, discord_channel_id FROM followed_youtube_channels') + followed_channels = c.fetchall() + conn.close() + + for youtube_channel_id, discord_channel_id in followed_channels: + try: + request = self.youtube.channels().list( + part='contentDetails', + id=youtube_channel_id + ) + response = request.execute() + if 'items' in response and len(response['items']) > 0: + uploads_playlist_id = response['items'][0]['contentDetails']['relatedPlaylists']['uploads'] + + request = self.youtube.playlistItems().list( + part='snippet', + playlistId=uploads_playlist_id, + maxResults=1 + ) + response = request.execute() + if 'items' in response and len(response['items']) > 0: + latest_video = response['items'][0]['snippet'] + video_id = latest_video['resourceId']['videoId'] + title = latest_video['title'] + publish_time = latest_video['publishedAt'] + thumbnail_url = latest_video['thumbnails']['high']['url'] + + c.execute('SELECT last_video_id FROM youtube_status WHERE youtube_channel_id = ?', (youtube_channel_id,)) + row = c.fetchone() + last_video_id = row[0] if row else None + + if video_id != last_video_id: + channel = self.bot.get_channel(discord_channel_id) + if channel: + embed = discord.Embed( + title=f"New Video from {youtube_channel_id}", + description=f"{title}\n[Watch now](https://www.youtube.com/watch?v={video_id})", + color=discord.Color.green() + ) + embed.set_thumbnail(url=thumbnail_url) + await channel.send(embed=embed) + + c.execute('INSERT OR REPLACE INTO youtube_status (youtube_channel_id, last_video_id) VALUES (?, ?)', (youtube_channel_id, video_id)) + conn.commit() + except Exception as e: + logger.error(f"Error checking YouTube channel {youtube_channel_id}: {e}", exc_info=True) + + conn.close() + await asyncio.sleep(300) # Check every 5 minutes + + def add_commands(self): + @app_commands.command( + name="follow_youtube", + description="Follow a YouTube channel to get video updates" + ) + async def follow_youtube(interaction: discord.Interaction, youtube_channel_id: str, channel: discord.TextChannel = None): + channel = channel or interaction.channel + conn = get_connection() + c = conn.cursor() + c.execute( + 'INSERT OR REPLACE INTO followed_youtube_channels (youtube_channel_id, discord_channel_id) VALUES (?, ?)', + (youtube_channel_id, channel.id) + ) + conn.commit() + conn.close() + await interaction.response.send_message( + embed=discord.Embed( + title="Followed YouTube Channel", + description=f"Now following {youtube_channel_id}. Alerts will be sent to {channel.mention}.", + color=discord.Color.green() + ) + ) + logger.info(f"Now following {youtube_channel_id} for video updates in {channel.name}") + + @app_commands.command( + name="unfollow_youtube", + description="Unfollow a YouTube channel" + ) + async def unfollow_youtube(interaction: discord.Interaction, youtube_channel_id: str): + conn = get_connection() + c = conn.cursor() + c.execute( + 'DELETE FROM followed_youtube_channels WHERE youtube_channel_id = ?', + (youtube_channel_id,) + ) + c.execute( + 'DELETE FROM youtube_status WHERE youtube_channel_id = ?', + (youtube_channel_id,) + ) + conn.commit() + conn.close() + await interaction.response.send_message( + embed=discord.Embed( + title="Unfollowed YouTube Channel", + description=f"No longer following {youtube_channel_id}.", + color=discord.Color.red() + ) + ) + logger.info(f"No longer following {youtube_channel_id}") + + self.bot.tree.add_command(follow_youtube) + self.bot.tree.add_command(unfollow_youtube) + + +async def setup(bot): + YouTubeModule(bot)