Add Twitch Live Notifications. Clean up code a bit.
This commit is contained in:
parent
4418ccebfc
commit
8abfc48ef5
32
__main__.py
32
__main__.py
|
|
@ -1,11 +1,11 @@
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from discord import Client
|
|
||||||
from libs.Db import Db
|
from libs.Db import Db
|
||||||
from libs.BotLog import BotLog
|
from libs.BotLog import BotLog
|
||||||
from libs.Channels import Channel
|
|
||||||
from libs.Guilds import Guilds
|
from libs.Guilds import Guilds
|
||||||
|
from libs.Channels import Channels
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
@ -14,6 +14,7 @@ token = os.getenv("DISCORD_TOKEN")
|
||||||
prefix = os.getenv("COMMAND_PREFIX")
|
prefix = os.getenv("COMMAND_PREFIX")
|
||||||
guild_id = os.getenv("GUILD_ID")
|
guild_id = os.getenv("GUILD_ID")
|
||||||
dev_mode = os.getenv("DEV_MODE")
|
dev_mode = os.getenv("DEV_MODE")
|
||||||
|
db_conn_str = os.getenv("DB_CONN_STR")
|
||||||
|
|
||||||
bot_intents = discord.Intents.default()
|
bot_intents = discord.Intents.default()
|
||||||
bot_intents.message_content = True
|
bot_intents.message_content = True
|
||||||
|
|
@ -27,29 +28,36 @@ class FunkyBot(commands.Bot):
|
||||||
intents=bot_intents
|
intents=bot_intents
|
||||||
)
|
)
|
||||||
self.log_handler = BotLog("bot.log")
|
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.guild_id = guild_id
|
||||||
self.dev_mode = dev_mode == "1"
|
self.dev_mode = dev_mode == "1"
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
print(f"Logged in as {self.user.name}")
|
print(f"Logged in as {self.user.name}")
|
||||||
guild = discord.Object(id=guild_id)
|
self.guild = await Guilds().get_guild(self)
|
||||||
synced = await self.tree.sync(guild=guild)
|
await self.tree.sync(guild=self.guild)
|
||||||
print(synced)
|
|
||||||
|
|
||||||
async def async_cleanup(self):
|
async def async_cleanup(self):
|
||||||
guild = await Guilds().get_guild(self)
|
channels_to_purge = {
|
||||||
channel = await Channel().get_channel(guild, "add-roles")
|
"add-roles": "FunkyBot is currently sleeping. Please try again once he has awakened.",
|
||||||
await channel.purge()
|
"live-penguins": "The FunkyBot Twitch Live Notification System is currently offline. Live Alerts will resume later."
|
||||||
await channel.send("FunkyBot is currently sleeping. Please try again once he has awakened.")
|
}
|
||||||
|
|
||||||
|
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):
|
async def close(self):
|
||||||
await self.async_cleanup()
|
await self.async_cleanup()
|
||||||
await super().close()
|
await super().close()
|
||||||
|
|
||||||
async def on_member_join(self, member):
|
async def on_member_join(self, member):
|
||||||
channel = discord.utils.get(member.guild.channels, name="general")
|
channel = discord.utils.get(self.guild.channels, name="general")
|
||||||
role = discord.utils.get(await member.guild.fetch_roles(), name="Chinstrap Penguins")
|
role = discord.utils.get(await self.guild.fetch_roles(), name="Chinstrap Penguins")
|
||||||
if role is not None:
|
if role is not None:
|
||||||
if role not in member.roles:
|
if role not in member.roles:
|
||||||
await member.add_roles(role)
|
await member.add_roles(role)
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
||||||
import discord
|
import discord
|
||||||
from discord import Embed
|
from discord import Embed
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from libs.Channels import Channel
|
from libs.Channels import Channels
|
||||||
from libs.Guilds import Guilds
|
from libs.Guilds import Guilds
|
||||||
from libs.Cog import Cog
|
from libs.Cog import Cog
|
||||||
from views.MatrixButtons import MatrixButtons
|
from views.MatrixButtons import MatrixButtons
|
||||||
|
|
@ -17,7 +17,7 @@ class RolesCog(Cog):
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
guild = await Guilds().get_guild(self.bot)
|
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. "
|
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. "
|
"\nClick to add. Click again to remove. "
|
||||||
"\nAlso, clicking on one of them will remove the other, if you have it. "
|
"\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:
|
if channel is not None:
|
||||||
await channel.purge()
|
await channel.purge()
|
||||||
await channel.send(embed=matrix_embed, view=MatrixButtons())
|
# await channel.send(embed=matrix_embed, view=MatrixButtons())
|
||||||
await channel.send(embed=language_embed, view=LanguageButtons())
|
# await channel.send(embed=language_embed, view=LanguageButtons())
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
|
|
||||||
83
cogs/twitch_notifications.py
Normal file
83
cogs/twitch_notifications.py
Normal file
|
|
@ -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))
|
||||||
26
config.json
Normal file
26
config.json
Normal file
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
data/TwitchLiveNotification.py
Normal file
0
data/TwitchLiveNotification.py
Normal file
27
embeds/TwitchGameNotificationEmbed.py
Normal file
27
embeds/TwitchGameNotificationEmbed.py
Normal file
|
|
@ -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)}")
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
class Channel:
|
import discord
|
||||||
|
|
||||||
|
class Channels:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_channel(self, guild, channel_name):
|
async def get_channel(self, guild: discord.Guild, channel_name):
|
||||||
channel = None
|
channel = None
|
||||||
for c in guild.channels:
|
for c in guild.channels:
|
||||||
if c.name == channel_name:
|
if c.name == channel_name:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
class Guilds:
|
class Guilds:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_guild(self, bot):
|
async def get_guild(self, bot: commands.Bot) -> discord.Guild:
|
||||||
guild = None
|
guild = None
|
||||||
for g in bot.guilds:
|
for g in bot.guilds:
|
||||||
if bot.dev_mode and g.name == 'OgmaBotDev':
|
if bot.dev_mode and g.name == 'OgmaBotDev':
|
||||||
|
|
|
||||||
84
libs/Twitch.py
Normal file
84
libs/Twitch.py
Normal file
|
|
@ -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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -4,7 +4,9 @@ version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"datetime==5.5",
|
||||||
"discord-py==2.5.2",
|
"discord-py==2.5.2",
|
||||||
"python-dotenv==1.1.0",
|
"python-dotenv==1.1.0",
|
||||||
|
"requests==2.32.4",
|
||||||
"sqlalchemy==2.0.41",
|
"sqlalchemy==2.0.41",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
datetime
|
||||||
|
requests
|
||||||
discord.py
|
discord.py
|
||||||
python-dotenv~=1.1.0
|
python-dotenv~=1.1.0
|
||||||
SQLAlchemy~=2.0.41
|
SQLAlchemy~=2.0.41
|
||||||
107
uv.lock
107
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "discord-py"
|
name = "discord-py"
|
||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
|
|
@ -167,15 +211,19 @@ name = "funkybot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "datetime" },
|
||||||
{ name = "discord-py" },
|
{ name = "discord-py" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "requests" },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "datetime", specifier = "==5.5" },
|
||||||
{ name = "discord-py", specifier = "==2.5.2" },
|
{ name = "discord-py", specifier = "==2.5.2" },
|
||||||
{ name = "python-dotenv", specifier = "==1.1.0" },
|
{ name = "python-dotenv", specifier = "==1.1.0" },
|
||||||
|
{ name = "requests", specifier = "==2.32.4" },
|
||||||
{ name = "sqlalchemy", specifier = "==2.0.41" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "2.0.41"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "yarl"
|
name = "yarl"
|
||||||
version = "1.20.1"
|
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/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" },
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue