From 8abfc48ef51a122b21117ea764a357e3e4cf831d Mon Sep 17 00:00:00 2001 From: Funky Waddle Date: Mon, 23 Jun 2025 07:56:09 -0500 Subject: [PATCH] Add Twitch Live Notifications. Clean up code a bit. --- __main__.py | 32 +++--- cogs/__pycache__/coder.cpython-313.pyc | Bin 4040 -> 0 bytes cogs/__pycache__/commands.cpython-313.pyc | Bin 2141 -> 0 bytes cogs/__pycache__/languages.cpython-313.pyc | Bin 7953 -> 0 bytes cogs/__pycache__/poll.cpython-313.pyc | Bin 976 -> 0 bytes cogs/roles.py | 8 +- cogs/twitch_notifications.py | 83 ++++++++++++++++ config.json | 26 +++++ data/TwitchLiveNotification.py | 0 embeds/TwitchGameNotificationEmbed.py | 27 ++++++ libs/Channels.py | 6 +- libs/Guilds.py | 5 +- libs/Twitch.py | 84 ++++++++++++++++ libs/__pycache__/BotLog.cpython-313.pyc | Bin 751 -> 0 bytes libs/__pycache__/Cog.cpython-313.pyc | Bin 1424 -> 0 bytes libs/__pycache__/Db.cpython-313.pyc | Bin 1733 -> 0 bytes libs/__pycache__/Role.cpython-313.pyc | Bin 1930 -> 0 bytes pyproject.toml | 2 + requirements.txt | 2 + uv.lock | 107 +++++++++++++++++++++ 20 files changed, 363 insertions(+), 19 deletions(-) delete mode 100644 cogs/__pycache__/coder.cpython-313.pyc delete mode 100644 cogs/__pycache__/commands.cpython-313.pyc delete mode 100644 cogs/__pycache__/languages.cpython-313.pyc delete mode 100644 cogs/__pycache__/poll.cpython-313.pyc create mode 100644 cogs/twitch_notifications.py create mode 100644 config.json create mode 100644 data/TwitchLiveNotification.py create mode 100644 embeds/TwitchGameNotificationEmbed.py create mode 100644 libs/Twitch.py delete mode 100644 libs/__pycache__/BotLog.cpython-313.pyc delete mode 100644 libs/__pycache__/Cog.cpython-313.pyc delete mode 100644 libs/__pycache__/Db.cpython-313.pyc delete mode 100644 libs/__pycache__/Role.cpython-313.pyc 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 ca19fb761c3393494d0cc15e24b04087456e21ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4040 zcmd^CO>7&-6`t82a!8S@M3HM*PTRGVMqarkZP#vL$4;fFO>9w>4X#wQv6>A0=>3Ci$d3)$SMHkt#j(hKo2&U7Ew-pZ+A&aq$Kyy zQ#;^3zL}l(cJ_VWym{M6CgTK}@|P>+BaD#0W2f8XQ0e>_lzT)cI?WJ{|3Ze$Q%+UX z#f&g7a?#hNj5IHE+1KSvWIoEHDj6daM30OUJ-Q;F6$77~>1>?vScz(}$2hd8Q7@6J z*NwbgtL3bM1AVqb7w7B}3`tqLYUTqUf$S~vDkbDCSmp}hR3}`}DHoT?8LA88q*gvpW*4bU7iy9^E|msE>f<%M6?l4p0f)ke+=9pd0|I%5JF9LU8y@(BAT$cq{*J_H0mbzm|;|`ifb6& zNX>MdT*-8df>|})+-lY25is*44!qDrPjbv^(Gyo~*NYiOzM69!!*Fn&s(RZyoi5uo zGhJ+0Kf9T(bNjlPcb)Xrn{L^*(y!y_Yqp!t+a(7CymhL6lOtz5#ScQNyAIqk&M^>Q z+$N7$@@}-n#y)=GQ})%(iI?w4pR%t%8Xf!S^*hoBu?|ckp6q+O^(HrK_6^ff-Lk3v zFz52hyWh&1#fD>Y^{Q!=8Wqb?@kP{1QN3w5)N<|y=mO$;#DC zZ>V58d0wfz72EQLDwb>VTpl~Sz{5@RK3tuDfS3e$?vZ7Z1--vzF?4-xnd`7@odJOgBboR8gu0>EkNl~6g=|wF&ApEI8!gr$=61~Xn8d9CBj+=8E&fYA&4-5G( zvy^6g=?0vKE!#^=-4S4yfc!cq?8B}pU{=&;mb}Qv!R*$=HfhwVZIxhi4^W4{2s76t zpE6tpontoP6DTG@?Bc@9FyJKvE`*Ww`LY1xeQfAL{@R8z*Jg7~Ha7tIQuFPC54qW9 zW{a6mMH2fyKyGeza1DUBf_TTJPmY1MF45V9v@S_^SjqR+5Wo0VFugh}NxFl_E2D{b*_;3}A9hb%fbm?HXL>VGm-Ah5Tlw=wNc z75za7kv$G(gUF^ZreTfJjjRS^&H*XnfKy?mS+4p3*4nJrVl_+=3jknki%)}BP2fpf z2A+4?Fji++c|dg*H;kHHXjD;87{<>VxoUW4*f5F}?zq*8Wm>jj_*E4b!GRYIi+nFy zs5p6>7rdd%+f|Ey5OU0b9O=vbH$4O;9h?@GypeOEh+1v9P!JkkGA#J=X0>V<8s*P} zg+oEi1cxftup9+m&La?C{DS;b66K?Cphia8@{zyFM>dnAo1^uovKWaxVWRvtz5h;o z>U?YJe1~AK6YJgBW%QNJ(KkXPK`zn*2J)WTKx z29&)h)>BZC`?&G%fM+jqWfco|kNJ}fG~oE%=0hFSPw?enx{W%Yf#FklD-8nA%FTH4 z!xMK;{L1`z?xX9!o%&tsH>uyx{dwUJ3$5wPO>O?piDo?0mNFYsX3N2n>%H@{68$5c z6{J5!K-1#>5(4H9g@jF+@46fV)SEDR*8BzV;*k@$?^T-sn=$*OG3I2DoFk>OW`QN_%XIKCL diff --git a/cogs/__pycache__/commands.cpython-313.pyc b/cogs/__pycache__/commands.cpython-313.pyc deleted file mode 100644 index 4706c46730aa364ae31d1e5568e43897537cb980..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2141 zcmZ`*U2qds6h3!3MPt=HsJ2K;pPrShd9eDHH-E8=)XR_y>d+yJ<=jS_< zR(E#@kltE)Z|-XW;BP+hNp1_RJt!%RdN;H^*2||@PBxFZPx1Vr_`=*tt5pwV$9>|e#;%P7dBnRcr`648QsUr!|ky)*3P)E_Ul3B7f&FQTejOB|4 z(+Wn}u=6uz1Gh=cD9<|LjAc7rnx>cYjA?_onW?wAcAsUZbgRgac_B?!Pf?yoXOr(?(tkn_)ocLSgER1_(!CU1QwA>f{iZzM z9NxYt-BMm?;o6h2;KBI}zt$J%ZwypSM+!`gLWXaBiZY!jJX+|3ccuVLF(+op>z@D= zaL=I)7{T>#@C)tH1&|Z{SesH#%5W#1B0tHdoD(u#Pc7n<7fep>>lA~3p9+T#18hLA zawOvx|C47k-A~T>EGuSup4H|A|4x5=7lAQ{X*@}m#VI+=Z9rzzIwwh{_gU@#-%ckR1HaY>$h@580P`?}c|ahcubr)rgb1%=vR(!wutPct z%Q8kQ=0&S0xVA-yq&^kZm({6+;7De^VmKQL2GeP&YL_e%l|u3rhvH>YcF z(J+mLDxGxVodWID?%uMc=gVv|+1bT|!$y$ZfirM3Hhp9NPbXIMSIGB)tAW*pE9}~< zKTiBGaV>Q#zVF8VhfKnqXA@(@A)!Qw^87gxhldj^8n%X#6ZQlTEKVR_tr}(l#~Naa zaya5lnzu@(6LE2BXl>j&!M2bHP~LYOQMVVIuwJ9oF!An6fuRa52zn8X;*{~!s4ltA zQx5CLaOt~^Z~%4*8Taw8{>_WwW?%oxo(p?cCcc_jOgDRa&(GiLQJc}tD@QLJZA3@b zq9dz%J^Iplq1nHA<@ANq_5RWGQgh?h#>Uth{$kU=?61eBf8RKLuM3_Vx*LL?-gEOy z^XIH3>tf-X(xuXsaDCf&J+iBz?7FG!YDRh+k)gH7&}!_vvCCuiNV1_M*OcTv5xRE& zp(G!&1itR9nhuh0-`t7(`XEV*()BIFT%I6lS-L(MK>25xAWsm=T}pKE?#Jr_yI)At zDpsLZ=5komPSo<{_DYYY&6X&$%O%q=ElqQ~mlvvFJ3>wrd!n7UOS+b~ZCaYC+1N%k zr@P%Tb)#I?G&CFGk>pr-!wWc1fC~_g3Ac}*5&?tf+D9{ zA_NBhOVxb< zI>oElb*c>^nHhGiN_V2VpD%4e#w({8>OMENH1^fpMfPQ-9*Q@lcwLG=WV~3`ZFp4$;2-FhOEm}w#3$_<)rCgd}$uyef7;ZUnp0lmU c?uvwv+o0Ztf%}0Tl3HZ<0f$zckP-Lvf7r6R6#xJL diff --git a/cogs/__pycache__/languages.cpython-313.pyc b/cogs/__pycache__/languages.cpython-313.pyc deleted file mode 100644 index a8fefba4035443970f3b16e60ac2647c6e5a72d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7953 zcmds6O>7&-72aJg$)QB8Y?-oUIj+}|ESWYcHnslcDp3SDiPTDl)3L8j~0;z!>h4vD+1=>?jEl{6&=!L8Tth7x|J^5yXX&d*{_m)4)6-hBZ z^k6NtquJSc-^{#kzM18ld`n9c1J`f=Hn;r8;|%jpd?;P+VkZB6$UI^sMq+0eivPJ8 zW}2m}hE6>mH({Adv^6nYWw3m8Cri1BXBu^(Jd2hJ~T?I@^B+|)HUyKd< zo?_8#kTw0`R7#GhtE=H;RD)+yk#|k0F(~5a)r1l$U=-4FCwBn)$I#7N3}q#TauQ2j z3(Np3ah=Qxq5LiPyuZ@0kcY0hBwlila+2qkcm7y)jR8*b0s=o+d_{eAk%;7LfYjtb zYO0IWEcqKCwK$NP>mmtKYXhWX4y5{hIxe*}Ksw<-^4INCKx%J*bkc#;QWxoz)X@Ox zv;#?~i_|HdX@K;a1F5wx(pl*n4Uo<`kdDOFa#cE;x|d>LT?@ zeGQNQ4y{}akx0(oxcvr1pA1&S$=tg zTVQ?!T3=uX*+L;KGVB7ADBIukmMWK&3UB#RS&RvCvp0fn)2*db3N?u=$CGhgmd&Z%f2 zN+;jn2&JgHqC|8pbbUi#R+FJ?So)T#hazfB3nfY;?@w(|T#L-MvNQTi{lVo}I}gcM z_nFU0%R_IDbo}zfXXNZ&SMLMC!j%<*tEs)_otB3^&z zx@h&AGx=nARWX~RiWZ^qlpa@;W^+8LD>NLz2Z!fz75xzI{0=1Dj0o%K0y7W!gV#Kc z`@prpO6*{fhj(F1ScW=l>?7B#+Yd+!?0lI8+ilH>`jphNvI>4k zJm#qY?VAMrC!^a;LKQ8gCN;(MYf3UI7bnMKj*yTX7$2C`&ePdIG|T{)r45JEX2l>JsTo6@t8 z5z+=p=Scdwi}4Nalk|%`PKXc0NrC+pJK4nl`aI;G@a&|Yf8zFF-p@j6x|1r-hHk2? zs%wgKLWuDtaRc1799~nzxc*ABPdmXKnGu_PF`*`73jMlsfSv*S68FsoZ&~!ybMS(S zn4rfUO!q*6*=jRw`T7*rs=;-4PUzm5`9u4zFl>#)dp8YX!*!Y-L*ombhPO@=Q-*am)`LLf|IH_3Zmo7U|+vv`j}? zPt)PGuw?CwM?h0e_GmJrHbb>_W!Dv7LR%rA#q`_STG6ni7EwiAC3b~zgN$d%cok9a zXV*6^qP7gOl_Oh7)YW~mRV0c}szBAHN=;St8TaWUrK{J$kC``Y)(+EEg&%5iS6f?G zkUPQV4*fff?EMgu1D`2HnZ33vOc-P$OD3w=yZww|?y_%jz#!yWr?~$xjBkp2UPyt; zH=c7?P-2s&7dJ9(W#P@k;}MV?kK@Q(v1zLFZYbdm#eX>@h#*ZmF-ngiIdC(Re?yM*9*nx^{pXbe;@lbPSWiZ&mo5 zz_uN`h}F)K+eO}iL4Vy{q`s$XivTxx=PrU?jiH+-O1=(qv<34RwF;iZC?*E^Ad1-} z%d2WMoxr?bmT#xSiQ=19Szd}$O;5y=N>Y_&EAmJoM8}xk;sJ~4jmEWzN~31;yLN!k zifvKrXBqykruex}@k^58Hwwi~kK!UrQN0xB3AKjM4$)CPtj8mASl4NMF|9*vRW@6S zvD1i>NXT-KrEuKDXe=ae7D8zl4yKskt^EX|Y+wD9`4{hUpMfBnr_FGm{=571UQ63v zpzpcclwzDwr!(rr_qVMz?)^GDQ+ib^p56wN6~1CD?4I}bBqa$E|!hf*-E2zyQOt!OW=sH zveCA~5%s$VW9ls%r5$5R+byM?EkXB2%0`=x?rqven^i{tk1_ol=GQh$1Epyh!MR$x zFns+X-1(0nDF@6eYZPnVM5S5R0#iD=xXG8w<~^mH{l5!C@2G(Tm&A^;Avn&g8z6UE z!EqNJrE>8oq@Gwho`9oNx9Nq?ML1fGS^A-d7{{L-Va U>^A#z*O!cy$jkF&2gI#XApigX diff --git a/cogs/__pycache__/poll.cpython-313.pyc b/cogs/__pycache__/poll.cpython-313.pyc deleted file mode 100644 index e47c356c8ca4681a21a3d3659d3f5b122db16d12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 976 zcmYjP&ubGw6rR~1$(kmuAf=_EuB|N%g#;H;8BnMH)4^>NI}p`Z_4)4gW!9+o6-mK=9@Qf-h1=CmsBbx0y1~+QTHG0zwj^= zGY83C0Q#O>T!3~>Cl4;@?Q^Ya1j3qsL%T1>+MR=h@tHon9 zHL6ruZrtlNBToR<@5dcC^UbuDAChGN`(%r7>JY9ulTA2Q8%OmAXkCtF-Hkhs!Xktcqo9t-3-a5ld>NhjX5Bay2AK9L zWAro7YblGdsM+%wQzgcFu{Q`2ml@k0G{gL6jInmWg$#qpk7C9+R;h~l*Er@-Xt+OwaqPnVGrF%g%re2&S)^g6?W2vV zrix9^V?c3QHBMhEmIAZb>1UKFd^zaz%U~ISB@iIj-%FLZ3vU+Qdmk3x1qUVj(6A2- z`&5+Se(&l${YdAGPlb6Ha#i#K;l|ukBRQz~J2Io!3WJuY!N7BDSjHS?NGZ*TK}wZ% qJL}pVehrRTo 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 e6854483fec8be38f446080df0e4b72f7bf00b93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 751 zcmYjP&ubGw6n?WmlI@QrQrZv{F^Y(b+C8YCP>XmdqNNCHPOT1`&1AE2vKwc1k@VD) zM9^dJUj0`**;^U({&3m zg~1^N5RjMff(X)t2Hb$axD0{0M;@AGoiJj{vrly55w*0HaP~Y*JDiEAEGThS4U@Z= zzeV0X5F`L$1VosD)q)2^Gxwc75msOdy9G~(sN4k4R+VR2zMaJ<7Pdylf2{gI6Hhh} zHpwCEUR=I5TJQ#~v`9c6lP1b;0l(-COw8tzAO_FcLGex(#i!wb?b}7(z4u6w51yrL zSqzy_4y8$&s!#pN{*fdgL^| z-u~eiGu5R znbP+~$Y(b-O1p`WIZslSW|Wqa*3eiU=1D}uJQqp3$QgQ7?#yAuq{#0yIAMc(4Zqu%RpeC^6 diff --git a/libs/__pycache__/Cog.cpython-313.pyc b/libs/__pycache__/Cog.cpython-313.pyc deleted file mode 100644 index b25c2a2b5be77b736a5bb2303ab62594cc16525c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1424 zcmZux-HRJl6hC)9lG$dG*tC@zyEbfF-Pk2r&_$6VY!?c(5GBJxp@GY6GMk+`nOX0h z6(c@q1+5^sJ}mm|Ti^T_>K`D6TC!a0gXqh?#ik;C>p3$?({6i`oclXpch1*joX;Nt zq?KRZ-};pS{7u4?%n2A@M6d@AIP3}tI<+h8Jxyp8I0*~j=w)z>me$Nn183)oo?@YQ zS=Brsp;2YZYDc}E8+K%S<_e%qgz;wx_MioVIUqEL3Ek1kAdHsTw4V$}akKcWZe^mP z+@@GqnB`pa__N+`YK~ck>xRfV8DV#`RZHn>QTM<=*Y-3k3L{JvjbA~w2UlMR@Jz{J z>kwcEQ|dxhYh1deDSab~RjKDm>2^KIJ6_<$?ndBMHDyRI*c5XZAnD#pMJ?C2qMldZ z+zCJI*SAG<-D}6P{y{(9io*IO(q4?>df;!!I;L0K?kkIPKlEeHl}(zdM3a!zmd|1T z*o)s<`wOq#H11lb$Ebq#<1A6262HXX_yV#sP{FFMJ+`ky^U3PbgQ$Re@P@WB@v1-< z-qJT2*8AyP#g`S~xt%_iXKo8Wj48v=?RlJ6naE+Zq_~QpIVvj(dE%aCKb2@B!UVkVl>l@`$Oy%$qo^ac-AB9g%il&s*^%H`$F02Oe zVOpFh+_wR)QB*?ftVz!}M6;ToZ*OJ~1)wUc3!@WY@{>B{(!=tP;75Dl8ruK> diff --git a/libs/__pycache__/Db.cpython-313.pyc b/libs/__pycache__/Db.cpython-313.pyc deleted file mode 100644 index 202728effac2dce6445d48fa78054a4ddc84e4cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1733 zcmZux&2JM&6rWx1uGfy^gnSdz#w4L+i-db58gM2HOnDUnuddtxuFcf;Ft z)f|vGAy=hpks8sSdWBQ}l|~@N8YSY;Q*Koq#I^73+HTT%w0iTK-^{*wU-R07gK37N zedSh7Nig;&ExJSLfcDo2_L#{`X^j;nkn)7(HMy98M1&J-N>K&1sDT!(m9=D12R+ZG z*eo;EX=ZARa;yzf)uf>x)54I%U>Ud_a*KOa$Ky!o!6P@^-Qj^Di{T0{yEfRN^9{F3 zcECj@;6dQ{Ufq7k0Z%J8y+*wpD~_ewS+u`Eu*bF-NG1c>lpqn=BBL#dEoBjXr2(p` zo60nMAc019LekVgM+;LzUMU@Gc;t+FDf$cJ^#;LN^;^oFD3qNOoe5OrGiNzkfrfmx z51&MSxDT%*KhlR!AwSlKPa{9khaW(Gst=zrWf-g`3`JyCN>^9x(B7al94GPYE4FTA zg>G4%UFVi1(w0^CD-D-$#~+yVU^e$epqmwQcyt8c6LRo=N(XH$iv_^il?kKGJP;I zyFW79OtyxnUSywVo9bz9X!6I$-#uP_iG}@%h1U}wH>a>~QG9FB`wz`}N+-PYJ30ij0z4G2t_952+?ba%aLLaR4|dClm%qcQ+6Wj>cyW{P8d!n zW2*YuyR4*Mi+`f?>qc6nu{+ytxyI|ev3uEniK__X=YE>>p&r#pTpS9=ejQsGnQ+!D rp)bd6-Af}^6hAjJ6G!uHH!*6&@w3KFH*u*S9|Q)%^<+v?NUTtxrA_T%{D>dhSeOvLbt7&YXMh`MT%)?zwQe zx&YVDNAIuy!vXxwHqFI1mf`$x^bA|g^YH@sz=Zb-0LkZ5lj z8nd<1-Neefh@*AX*d+U0TyTQQwRy9pxSLvYEdsat2|$|_*A|^5>}J51h|0ILQ+!K% zWh%k0eQnL+UDDWkgo)$Y5pGjg(XQV_YRd_2Z}8lToP!*Q5PyK*0E1STk8gJ_F{I?x zoU|>PtdfsOUjk=?!>KJ+M^i^&}PMmk@eyQdktNO?G zmQeO^{F~*vr~kLPpXQD|(Rv_M3yfC-<3H{k2c~M?sfu^XRC*NoKJuq?@XQ6yQ3P;s z{)HQS)0f&XX0|)BI7xmXi{t#S3!}(B>mrL2{4+<0@e>5;&A9#pZ7p0QcCV0)!p3?P zc2nHyUBm>rY22th;_HS%GmC!HeI~v-nlW1cSK*7H#>CA$;--_~hB6Of-ogNmEwH-p zq)M^0shE>Oqd4@SHFm5%JK8i1uQ^NFBtd5JArf<(TyL+{$hQ7rOCJRCS$Rjd7 zNRS>vJS-ZbEVC&q%SM+h7fac4f$>gR{=A$nG)_FSyq2e$UdXG8T9Rd}BKV4d4j?i7 zvaIQ8J)e=&x=!<}WnIxwYY@*a@B_0dly%D_jM3P``U=i%f)F})cL~zJK|zSov8GPg wW^`opTBby^mLYRy9a~Rn&dPP3zK#bBSXwudGma4Q7r6hk_mbE-ATe$J2dP$r-T(jq 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" }, +]