diff --git a/__main__.py b/__main__.py index 834fbe2..1d5184d 100644 --- a/__main__.py +++ b/__main__.py @@ -1,11 +1,11 @@ import os import asyncio import discord -from discord import Client + from libs.Db import Db from libs.BotLog import BotLog -from libs.Channels import Channel from libs.Guilds import Guilds +from libs.Channels import Channels from discord.ext import commands from dotenv import load_dotenv @@ -14,6 +14,7 @@ token = os.getenv("DISCORD_TOKEN") prefix = os.getenv("COMMAND_PREFIX") guild_id = os.getenv("GUILD_ID") dev_mode = os.getenv("DEV_MODE") +db_conn_str = os.getenv("DB_CONN_STR") bot_intents = discord.Intents.default() bot_intents.message_content = True @@ -27,29 +28,36 @@ class FunkyBot(commands.Bot): intents=bot_intents ) self.log_handler = BotLog("bot.log") - self.db = Db('sqlite:///data/bot.db', True) + self.db = Db(db_conn_str, True) + self.guild = None self.guild_id = guild_id self.dev_mode = dev_mode == "1" async def on_ready(self): print(f"Logged in as {self.user.name}") - guild = discord.Object(id=guild_id) - synced = await self.tree.sync(guild=guild) - print(synced) + self.guild = await Guilds().get_guild(self) + await self.tree.sync(guild=self.guild) async def async_cleanup(self): - guild = await Guilds().get_guild(self) - channel = await Channel().get_channel(guild, "add-roles") - await channel.purge() - await channel.send("FunkyBot is currently sleeping. Please try again once he has awakened.") + channels_to_purge = { + "add-roles": "FunkyBot is currently sleeping. Please try again once he has awakened.", + "live-penguins": "The FunkyBot Twitch Live Notification System is currently offline. Live Alerts will resume later." + } + + for channel, message in channels_to_purge.items(): + the_channel = await Channels().get_channel(self.guild, channel) + await the_channel.purge() + if message is not None: + await the_channel.send(message) + ... async def close(self): await self.async_cleanup() await super().close() async def on_member_join(self, member): - channel = discord.utils.get(member.guild.channels, name="general") - role = discord.utils.get(await member.guild.fetch_roles(), name="Chinstrap Penguins") + channel = discord.utils.get(self.guild.channels, name="general") + role = discord.utils.get(await self.guild.fetch_roles(), name="Chinstrap Penguins") if role is not None: if role not in member.roles: await member.add_roles(role) diff --git a/cogs/__pycache__/coder.cpython-313.pyc b/cogs/__pycache__/coder.cpython-313.pyc deleted file mode 100644 index ca19fb7..0000000 Binary files a/cogs/__pycache__/coder.cpython-313.pyc and /dev/null differ diff --git a/cogs/__pycache__/commands.cpython-313.pyc b/cogs/__pycache__/commands.cpython-313.pyc deleted file mode 100644 index 4706c46..0000000 Binary files a/cogs/__pycache__/commands.cpython-313.pyc and /dev/null differ diff --git a/cogs/__pycache__/languages.cpython-313.pyc b/cogs/__pycache__/languages.cpython-313.pyc deleted file mode 100644 index a8fefba..0000000 Binary files a/cogs/__pycache__/languages.cpython-313.pyc and /dev/null differ diff --git a/cogs/__pycache__/poll.cpython-313.pyc b/cogs/__pycache__/poll.cpython-313.pyc deleted file mode 100644 index e47c356..0000000 Binary files a/cogs/__pycache__/poll.cpython-313.pyc and /dev/null differ diff --git a/cogs/roles.py b/cogs/roles.py index e35543c..8448984 100644 --- a/cogs/roles.py +++ b/cogs/roles.py @@ -1,7 +1,7 @@ import discord from discord import Embed from discord.ext import commands -from libs.Channels import Channel +from libs.Channels import Channels from libs.Guilds import Guilds from libs.Cog import Cog from views.MatrixButtons import MatrixButtons @@ -17,7 +17,7 @@ class RolesCog(Cog): @commands.Cog.listener() async def on_ready(self): guild = await Guilds().get_guild(self.bot) - channel = await Channel().get_channel(guild, "add-roles") + channel = await Channels().get_channel(guild, "add-roles") matrix_embed = Embed(title="Matrix Roles", color=discord.Color.purple(), description="Please select your choice between these two roles. " "\nClick to add. Click again to remove. " "\nAlso, clicking on one of them will remove the other, if you have it. " @@ -27,8 +27,8 @@ class RolesCog(Cog): if channel is not None: await channel.purge() - await channel.send(embed=matrix_embed, view=MatrixButtons()) - await channel.send(embed=language_embed, view=LanguageButtons()) + # await channel.send(embed=matrix_embed, view=MatrixButtons()) + # await channel.send(embed=language_embed, view=LanguageButtons()) async def setup(bot): diff --git a/cogs/twitch_notifications.py b/cogs/twitch_notifications.py new file mode 100644 index 0000000..57db973 --- /dev/null +++ b/cogs/twitch_notifications.py @@ -0,0 +1,83 @@ +import discord +import json +from libs.Cog import Cog +from libs.Twitch import Twitch +from libs.Channels import Channels +from libs.Guilds import Guilds +from embeds.TwitchGameNotificationEmbed import TwitchGameNotificationEmbed +from discord.ext import commands, tasks + + +class TwitchNotificationsCog(Cog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + self.guild = None + self.twitch = None + self.online_users = {} + self.config = {} + + @commands.Cog.listener() + async def on_ready(self): + await self.load_config() + self.guild = await Guilds().get_guild(self.bot) + self.twitch = Twitch(self.config[self.guild.name]["twitch"]["client_id"], self.config[self.guild.name]["twitch"]["client_secret"]) + await self.set_access_token_and_expires_date() + await self.set_notification_channel_and_purge_channel() + await self.write_config() + self.check_twitch_online_streamers.start() + self.check_twitch_access_token.start() + + async def load_config(self): + with open("config.json") as config_file: + self.config = json.load(config_file) + + async def write_config(self): + with open("config.json", "w") as config_file: + json.dump(self.config, config_file, indent=4) + + async def set_notification_channel_and_purge_channel(self): + channel = await Channels().get_channel(self.guild, self.config[self.guild.name]['twitch']['channel_name']) + if self.config[self.guild.name]['twitch']['channel_id'] == 'xxx': + self.config[self.guild.name]["twitch"]["channel_id"] = channel.id + await channel.purge() + + + async def set_access_token_and_expires_date(self): + access_token, access_token_expires = await self.twitch.get_access_token() + self.config[self.guild.name]["twitch"]["access_token"] = access_token + self.config[self.guild.name]["twitch"]["expire_date"] = access_token_expires + + @tasks.loop(seconds=60) + async def check_twitch_access_token(self): + print("Access token is checked") + access_token, access_token_expires = await self.twitch.check_access_token() + self.config[self.guild.name]["twitch"]["access_token"] = access_token + self.config[self.guild.name]["twitch"]["expire_date"] = access_token_expires + await self.write_config() + + + @tasks.loop(seconds=90) + async def check_twitch_online_streamers(self): + print("Online streamers are checked") + channel = await Channels().get_channel(self.guild, self.config[self.guild.name]["twitch"]["channel_name"]) + print(channel) + if not channel: + return + + online_notifications, offline_notifications = await self.twitch.get_notifications(self.config[self.guild.name]['twitch']['watchlist']) + print("online_notifications", online_notifications) + print("offline_notifications", offline_notifications) + + for username in offline_notifications: + message = await channel.fetch_message(self.online_users[username]) + await message.delete() + del self.online_users[username] + + for notification in online_notifications: + embed = TwitchGameNotificationEmbed(notification) + message = await channel.send(embed=embed) + self.online_users[notification["user_login"]] = message.id + +async def setup(bot): + await bot.add_cog(TwitchNotificationsCog(bot)) \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..9286fa5 --- /dev/null +++ b/config.json @@ -0,0 +1,26 @@ +{ + "servername": { + "twitch": { + "client_id": "", + "client_secret": "", + "access_token": "", + "channel_id": "xxx", + "channel_name": "xxx", + "expire_date": 1649873640, + "watchlist": [] + } + }, + "OgmaBotDev": { + "twitch": { + "client_id": "[redacted]", + "client_secret": "[redacted]", + "access_token": "[redacted]", + "channel_id": "[redacted]", + "channel_name": "live-penguins", + "expire_date": "[redacted]", + "watchlist": [ + "funkywaddle" + ] + } + } +} \ No newline at end of file diff --git a/data/TwitchLiveNotification.py b/data/TwitchLiveNotification.py new file mode 100644 index 0000000..e69de29 diff --git a/embeds/TwitchGameNotificationEmbed.py b/embeds/TwitchGameNotificationEmbed.py new file mode 100644 index 0000000..951b4aa --- /dev/null +++ b/embeds/TwitchGameNotificationEmbed.py @@ -0,0 +1,27 @@ +from discord import Embed + + +class TwitchGameNotificationEmbed(Embed): + + def __init__(self, stream_data): + super().__init__() + game = stream_data["game_name"] + self.title = stream_data["title"] + self.url = f"https://twitch.tv/{stream_data["user_login"]}" + self.description = f"[Watch](https://twitch.tv/{stream_data["user_login"]})" + self.color = 0x001eff + self.set_author(name=f"{stream_data["user_name"]} Stream is Live", + url=f"https://twitch.tv/{stream_data["user_login"]}") + self.set_image( + url=f"https://static-cdn.jtvnw.net/previews-ttv/live_user_{stream_data["user_login"]}-1920x1080.jpg") + + if game == "": + self.add_field(name="Game", value="404: Game not found", inline=True) + else: + self.add_field(name="Game", value=f"{stream_data["game_name"]}", inline=True) + self.add_field(name="Viewers", value=f"{stream_data["viewer_count"]}", inline=True) + tags = stream_data["tags"] + if len(tags) > 3: + tags = tags[:3] + tags.append('...') + self.add_field(name="Tags", value=f"{", ".join(tags)}") \ No newline at end of file diff --git a/libs/Channels.py b/libs/Channels.py index 27d88e5..222924c 100644 --- a/libs/Channels.py +++ b/libs/Channels.py @@ -1,8 +1,10 @@ -class Channel: +import discord + +class Channels: def __init__(self): pass - async def get_channel(self, guild, channel_name): + async def get_channel(self, guild: discord.Guild, channel_name): channel = None for c in guild.channels: if c.name == channel_name: diff --git a/libs/Guilds.py b/libs/Guilds.py index dd6d364..6e97c8e 100644 --- a/libs/Guilds.py +++ b/libs/Guilds.py @@ -1,8 +1,11 @@ +import discord +from discord.ext import commands + class Guilds: def __init__(self): pass - async def get_guild(self, bot): + async def get_guild(self, bot: commands.Bot) -> discord.Guild: guild = None for g in bot.guilds: if bot.dev_mode and g.name == 'OgmaBotDev': diff --git a/libs/Twitch.py b/libs/Twitch.py new file mode 100644 index 0000000..909dc35 --- /dev/null +++ b/libs/Twitch.py @@ -0,0 +1,84 @@ +import datetime +from datetime import timedelta +import json +import requests + + +class Twitch: + def __init__(self, client_id, client_secret): + self.access_token = None + self.access_token_expires = None + self.client_id = client_id + self.client_secret = client_secret + self.online_users = {} + self.config = {} + + async def get_unix_time(self): + now = datetime.datetime.now() + future = now + timedelta(weeks=3) + unx_time = future.timestamp() + print("unix time:", unx_time) + return int(unx_time) + + async def get_access_token(self): + params = { + "client_id": self.client_id, + "client_secret": self.client_secret, + "grant_type": "client_credentials" + } + + response = requests.post("https://id.twitch.tv/oauth2/token", params=params) + print("response:", response.json()) + self.access_token = response.json()["access_token"] + self.access_token_expires = await self.get_unix_time() + return self.access_token, self.access_token_expires + + + async def get_users(self, login_names): + params = {"login": login_names} + + headers = { + "Authorization": f"Bearer {self.access_token}", + "Client-Id": self.client_id + } + + response = requests.get("https://api.twitch.tv/helix/users", params=params, headers=headers) + return {entry["login"]: entry["id"] for entry in response.json()["data"]} + + async def get_notifications(self, watchlist): + users = await self.get_users(watchlist) + streams = await self.get_streams(users) + online_notifications = [] + offline_notifications = [] + + for user_name in watchlist: + if user_name not in self.online_users: + self.online_users[user_name] = None + if user_name not in streams: + if self.online_users[user_name] is not None: + offline_notifications.append(user_name) + self.online_users[user_name] = None + else: + if self.online_users[user_name] is None: + self.online_users[user_name] = datetime.datetime.now(datetime.UTC) + started_at = datetime.datetime.fromisoformat(streams[user_name]["started_at"]) + if started_at < self.online_users[user_name]: + online_notifications.append(streams[user_name]) + self.online_users[user_name] = started_at + return online_notifications, offline_notifications + + async def get_streams(self, users): + params = {"user_id": users.values()} + headers = { + "Authorization": f"Bearer {self.access_token}", + "Client-Id": self.client_id + } + + response = requests.get("https://api.twitch.tv/helix/streams", params=params, headers=headers) + return {entry["user_login"]: entry for entry in response.json()["data"]} + + async def check_access_token(self): + current_time = datetime.datetime.now().timestamp() + if int(current_time) >= self.access_token_expires: + await self.get_access_token() + return self.access_token, self.access_token_expires diff --git a/libs/__pycache__/BotLog.cpython-313.pyc b/libs/__pycache__/BotLog.cpython-313.pyc deleted file mode 100644 index e685448..0000000 Binary files a/libs/__pycache__/BotLog.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/Cog.cpython-313.pyc b/libs/__pycache__/Cog.cpython-313.pyc deleted file mode 100644 index b25c2a2..0000000 Binary files a/libs/__pycache__/Cog.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/Db.cpython-313.pyc b/libs/__pycache__/Db.cpython-313.pyc deleted file mode 100644 index 202728e..0000000 Binary files a/libs/__pycache__/Db.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/Role.cpython-313.pyc b/libs/__pycache__/Role.cpython-313.pyc deleted file mode 100644 index edb062f..0000000 Binary files a/libs/__pycache__/Role.cpython-313.pyc and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml index d493cb7..c7d674e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,9 @@ version = "0.1.0" description = "Add your description here" requires-python = ">=3.13" dependencies = [ + "datetime==5.5", "discord-py==2.5.2", "python-dotenv==1.1.0", + "requests==2.32.4", "sqlalchemy==2.0.41", ] diff --git a/requirements.txt b/requirements.txt index 02d3eaa..dae3fc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +datetime +requests discord.py python-dotenv~=1.1.0 SQLAlchemy~=2.0.41 \ No newline at end of file diff --git a/uv.lock b/uv.lock index af553e8..0d68afd 100644 --- a/uv.lock +++ b/uv.lock @@ -106,6 +106,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/35/be73b6015511aa0173ec595fc579133b797ad532996f2998fd6b8d1bbe6b/audioop_lts-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:78bfb3703388c780edf900be66e07de5a3d4105ca8e8720c5c4d67927e0b15d0", size = 23918, upload-time = "2024-08-04T21:14:42.803Z" }, ] +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "datetime" +version = "5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/66/e284b9978fede35185e5d18fb3ae855b8f573d8c90a56de5f6d03e8ef99e/DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3", size = 63671, upload-time = "2024-03-21T07:26:50.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/78/8e382b8cb4346119e2e04270b6eb4a01c5ee70b47a8a0244ecdb157204f7/DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120", size = 52649, upload-time = "2024-03-21T07:26:47.849Z" }, +] + [[package]] name = "discord-py" version = "2.5.2" @@ -167,15 +211,19 @@ name = "funkybot" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "datetime" }, { name = "discord-py" }, { name = "python-dotenv" }, + { name = "requests" }, { name = "sqlalchemy" }, ] [package.metadata] requires-dist = [ + { name = "datetime", specifier = "==5.5" }, { name = "discord-py", specifier = "==2.5.2" }, { name = "python-dotenv", specifier = "==1.1.0" }, + { name = "requests", specifier = "==2.32.4" }, { name = "sqlalchemy", specifier = "==2.0.41" }, ] @@ -305,6 +353,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.41" @@ -335,6 +416,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, ] +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + [[package]] name = "yarl" version = "1.20.1" @@ -382,3 +472,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] + +[[package]] +name = "zope-interface" +version = "7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, +]