Compare commits

...

36 Commits

Author SHA1 Message Date
Dan
f7ffd77942 FIX: Fixed Twitch
FEAT: Added Tiktok, This new module should auto-embed tiktoks to have them be viewable
2024-07-10 12:10:30 -04:00
Dan
5219d5a6c8 Fix: Fixed the issue with Wordle hopefully 2024-07-05 22:57:00 -04:00
Dan
2674955d5c FEAT: added Wordle
REF: Requirements.txt got updated since we added an encrypted wordlist
2024-07-05 17:19:28 -04:00
Dan
0914cb30cc FIX: Changed Profiles to use your birthday to calculate your age 2024-07-04 17:16:27 -04:00
Dan
c9255143cb FEAT: Started adding profiles
FIX: Knucklebones now works as best as it can (need to stress test it)
2024-07-04 13:55:30 -04:00
Dan
2b621ae030 FIX: Fixed the score not updating when a dice was removed. should fix losing despite having a full board 2024-07-04 12:26:24 -04:00
Dan
77ae0cde5d FIX: Added Column Scores 2024-07-04 12:08:46 -04:00
Dan
b7617cbf0e FIX: Game works, declares a winner, just need to test the gambling/bet system 2024-07-04 11:24:14 -04:00
Dan
6346766583 REF: Changed Knucklebones to uses a gui instead of using a lot of commands. 2024-07-03 23:15:22 -04:00
Dan
d68cb1868d FEAT: Started working on Knucklebones
FIX: Corrected an issue with the database and XP (it was creating an extra table)
REF: Moved Destiny2 module to the social modules (makes sense since it's not fully interactable)
2024-07-03 22:37:05 -04:00
Dan
ed69bcf300 REF: Updated the twich module to hopefully fix alerts 2024-07-03 11:43:28 -04:00
Dan
b0ed598cb2 REF: Added a discord alert for github too 2024-07-01 22:44:21 -04:00
Dan
c6daeb425e FIX: XP finally works/tracks (won't be earned on the site bot, just development) 2024-07-01 22:30:34 -04:00
Dan
4f065d368b FIX: Fixed some typos as well as fixed up the update module 2024-06-30 17:55:41 -04:00
Dan
8e125c9758 FIX: Made a whoops with the enabling of the module for updates 2024-06-30 17:43:43 -04:00
Dan
7909594da8 FIX: Removed the branch 2024-06-30 17:41:21 -04:00
Dan
fce87e7924 FIX: Fixed an issue with the system update 2024-06-30 17:38:48 -04:00
Dan
8c58585af1 FEAT: added new policy requirements for Discord 2024-06-30 17:35:51 -04:00
Dan
11a15fda71 FIX: Attempt to fix the twitch bot not auto-alerting 2024-06-30 15:54:13 -04:00
Dan
cd2ea6f569 FIX: Missed some empty lines 2024-06-28 15:32:37 -04:00
Dan
e39833a98b FIX: Made the currency and xp guild-based now 2024-06-28 14:01:48 -04:00
Dan
55ede2d2cf DOC: Added a sample.env to allow deployment 2024-06-27 12:17:15 -04:00
Dan
d92810b84c FEAT: Added an update module to allow Selena to be deployed 2024-06-27 11:35:22 -04:00
Dan
c0dd278cd4 FEAT: Youtube Module is done as it's going to be 2024-06-27 09:57:58 -04:00
Dan
f7f1e14b4b REF: Fixed up the Twitch Module to make it look better. 2024-06-27 00:12:00 -04:00
Dan
91052663c6 FEAT: Twitch Module works correctly, checks users correctly
REF: Updated related files to allow for Twitch.
2024-06-26 22:00:44 -04:00
Dan
2fc26a3e6d FEAT: Added a youtube module that tracks live and/or video uploads
REF: Changed code needed for Youtube
2024-06-26 16:00:43 -04:00
Dan
fa6cd7b5ea REF: Changed up the Embed to show the thumbnail 2024-06-25 22:57:05 -04:00
Dan
7f5aeb04c3 FIX: Added Volume Control, Set to 25% by default 2024-06-25 22:36:18 -04:00
Dan
f75564760e FEAT: Added Music Player
DOC: Changed .gitingore to prevent tracking ffmpeg
REF: Changed code needed to active the new module
2024-06-25 22:23:15 -04:00
Dan
338752b31a FEAT: Added Destiny 2 Module
DOC: Changed things to ignore any __init__.py
DOC: Updated to support Destiny 2
2024-06-25 20:06:10 -04:00
Dan
8509bcc98f FEAT: added birthday module 2024-06-25 15:35:40 -04:00
Dan
6997d96dbd REF: Removed the Twitch Module for now 2024-06-25 12:55:25 -04:00
Dan
bd318a7815 FEAT: Added Twitch module (can't check if it's working yet due to a sync issue)
FIX: Added the needed changes to the code for the new module
2024-06-25 00:02:13 -04:00
Dan
49a7588699 FEAT: Added XP
REF: Fixed up currency so the cooldown is presented to the user.
2024-06-24 23:20:03 -04:00
Dan
fcc1489b74 FEAT: Added Currency
FIX: Added some files to the .gitignore
DOC: Added the config.py
2024-06-24 20:42:30 -04:00
23 changed files with 2376 additions and 0 deletions

15
.github/workflows/discord_sync.yml vendored Normal file
View File

@ -0,0 +1,15 @@
name: Discord Webhook
on: [push]
jobs:
git:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Discord Webhook
uses: johnnyhuy/actions-discord-git-webhook@main
with:
webhook_url: ${{ secrets.YOUR_DISCORD_WEBHOOK_URL }}

4
.gitignore vendored
View File

@ -160,3 +160,7 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/data/selena.db
/clear_commands.py
__init__.py
/ffmpeg

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run: Selena",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal"
}
]
}

32
config.py Normal file
View File

@ -0,0 +1,32 @@
from dotenv import load_dotenv
import os
load_dotenv()
config = {
'DISCORD_TOKEN': os.getenv('DISCORD_TOKEN'),
'GUILD_ID': int(os.getenv('DISCORD_GUILD_ID')),
'DISCORD_CHANNEL_ID': int(os.getenv('DISCORD_CHANNEL_ID')),
'YOUTUBE_API_KEY': os.getenv('YOUTUBE_API_KEY'),
'TWITCH_CLIENT_ID': os.getenv('TWITCH_CLIENT_ID'),
'TWITCH_CLIENT_SECRET': os.getenv('TWITCH_CLIENT_SECRET'),
'BUNGIE_API_KEY': os.getenv('BUNGIE_API_KEY'),
'OAUTH_URL': os.getenv('OAUTH_URL'),
'OAUTH_CLIENT_ID': os.getenv('OAUTH_CLIENT_ID'),
'modules': {
'currency': {'enabled': True},
'xp': {'enabled': True},
'birthday': {'enabled': True},
'destiny2': {'enabled': False},
'music': {'enabled': True},
'youtube': {'enabled': True},
'twitch': {'enabled': True},
'update': {'enabled': True},
'data_privacy': {'enabled': True},
'terms_privacy': {'enabled': True},
'knucklebones': {'enabled': True},
'wordle': {'enabled': True},
'profiles': {'enabled': True},
'tiktok': {'enabled': True}
}
}

View File

@ -0,0 +1,142 @@
gAAAAABmiGBez3P25GGn0jWx1deDsb1oIpEV7IU4ZVgOOL24S5r6WfidvQ6g4Yrq84E9gQTSkdcwn_lnm_2zpjSbe9X_qhontQ==
gAAAAABmiGBejvP841lVXZ3ZCA_95MlnRk3UMtJCnzCFMa3QSN5ViaSg4UD3SSqQq9gIE-fc1GL8Gb6m8iOEqhxfmR5HeNyu1Q==
gAAAAABmiGBeRhhg4lJY8Wyz0fUkmZELYKr_twUFba2ZbxoUTKGGGdVUt7yrkG4-UQxqg-IP4ufHZUu-k4w-mJ2gWA25sKrp9A==
gAAAAABmiGBevngRUaVoomllodc0IpcvKWhm-AjzNUaTpjyns_rh5DHbYuWTj5pgUjJXERZEhDa1BrQ3OqLAk2wayONLNnH3eQ==
gAAAAABmiGBeUp0Wyr0XNACS_tY_55WTFf84Jdmg2JCTR9_9bkAfhqZv22PTSN-xdxz9VADh264BDucz2YRoHOVmdbqXMoz0eQ==
gAAAAABmiGBe5vAqONV8OfOznlcLYMh9KcyPxI8IcyIOUDdxSmMOoxDVGpxRPwyowJDokgzZGmq6tvXLlnBcwbWleoECopUO-Q==
gAAAAABmiGBeAXmUWejjs0rIvDlVsx-U3d2MdS_rjNLbSgq6-ukcuFhDbr0nWIOD83DMcO2CjAGJWN-a2Mw8Vdql287v7M_AEA==
gAAAAABmiGBeA1jW_FbcAo3JDFC9iX2abGFIR7EW8MOH5zcWfZMhPCGnsvbu69fZP35SudeqbMNhLy7drASV-Z4KtXnwyZD7qQ==
gAAAAABmiGBeYf819SSXTwW_9MbUtfE8xAjtqHkFCbXY7Vgma36Tchl6LqV62jlodDqvHhbQ-fStSEsjG2bPdO5OI3_PmCb3TA==
gAAAAABmiGBeztzZr4I-jyrB76dZjjDkvK5b5kaY06Vol6lGjoqz10qzIWBkbpvmvvdAT31Q01vPyN4iU8vpWNSNTPkV1vlfAg==
gAAAAABmiGBekhbLv-48kdCUF6j4hU2JrTXnu5hnezTz2UEXqD15PZeDITuyRUqa7hNbo3FkBzqGYQsXz4gDi_f-0ViYCsCQJQ==
gAAAAABmiGBeja_0vafWUUN87JOvFh-aQci_XzIqTrCqr629Y_xHO9cBvrSa0tmK_DHMcGISN_Ls8iVFOWt1U82sTy2DiDU-pw==
gAAAAABmiGBeR9ovJk2ONg0Pee3LXsJQoj5ddXdGIln9yakJ4ZztJUmjHMjFKU1S8tZb_xpHEHp6pbG10RFqk1yRRR-T5Y_WVg==
gAAAAABmiGBegcizm8mJR5V7yHpp7Xp0Fs0uC2mxf-4NzeIyQK_zEmPOOHQj8s8htTdk-x-fUwSD9qpFSJt11zSHdyONRkHvDw==
gAAAAABmiGBeJIUMxf6HdcWpsLLeKEJDwt6QwbC709xUEPdfmQVAA1N7Nl78MtJqSJxQ9bJC7wcJgg87IRC0koqViN3M1YWjKQ==
gAAAAABmiGBeL9xj9HLcmegi45KteAbupTac77kfC484qPi7MJA_xHMcHGY-YOrIFEo37Xb7w7aivNt9YaVOi5IkVNan1OnVFw==
gAAAAABmiGBe87S8w710hzzQlEdplEsJxAygJb9ZBvcd8_9l3IMzWLoI9lwJrlZtbG9a1mpO37EbTLcKdN0trhqZCtnItsWa2g==
gAAAAABmiGBetXiT4f2aeTQmpibgYQtgreQC3mDYuTqYFcuz5Im_b9EF3X4cP6Xg_TWNzz1x4GtduktHrRZ169Ro8CvBxcuWfQ==
gAAAAABmiGBeVIXUxhIGbdpqjiJ5pcZ2mbbr_YaG0M98u9iysVJqxsgTKtxhrI8BDux2PxF71vHS9VY7aiM27UMXBnoQTJqniA==
gAAAAABmiGBeFZ_eKtFb9anrmyns5cMMDfvhH6ZeIjUUt2QDgZLBRhE_ObgyWXFj4MSk32hl60p4ZkBzd9TivlUv6pfZmN0HUw==
gAAAAABmiGBeGltFO5nGfyvZB48Y86Jb2RTJEUWlYe_0uAkAeqagJhQkM0L41Wa5ipFhtdhvaYUzIw2WEWcpPMh4Rbrc4k-Ugg==
gAAAAABmiGBetjFqcCEB-85G3iMm_ITyZH78ECDtWvgEzFHsfWeKkt2LW5dhw1NCG-_A4xkrUbtp98X9_-AHwOOVDA_Qnvgt0Q==
gAAAAABmiGBeA22zlQbVE_cFLQIaFKR_Dd01G2Y9MG1zM1iP_W2YEYS5kCeV4FmR2w--0tdajPxljhQnZoFSqh-xAkR8SfhGvg==
gAAAAABmiGBezdUKsSIPdOruL-MByI4F3vXvEHLQHauzifJnqXIrAlo_8uoICLOb8lH2tXBe2oMfVGTXuCt4v2v8cvLfyJCvtg==
gAAAAABmiGBeXCtGzc-NksPzpLZHGXkY9mgQb_jVu8rW-jR8NZrvESY5HpkRb49RdmOloo4npmvc2IqdPJLhnMtUYWl4x-MciQ==
gAAAAABmiGBe3Ly8SPsWI-kQyWKouMHRlyqJP8j9GlW5eHZlZxaB0uz1WLJgfqG0hXebQZmVwLZRKRMbn7-hNDXhAIxuyf6rqA==
gAAAAABmiGBeVUxmJrqpAA0yEwRUrFnZuPv-aZRO33xkDZ3LHK7jTaUbG-7GWEduFnth0O65Y7xpx20ehxeWWeHg9B18eeZLEA==
gAAAAABmiGBebS6ZFcceVeM1FdE1DcykOAHrH6q6lK9jQ8Ys3na2VwwymC8ut6muZgWYVjRuposve-62LJlpZ7G4rm1czdtDsQ==
gAAAAABmiGBek_OcTkRg3v5xwKvx_-FSBTHGFkzBrGVw1RQAR8AekT5aluuyroxP46uVGlnH-8fSsCNDTIZ7A45EwMIpLt-5JQ==
gAAAAABmiGBedkkvrywKcZv9ncnXDzkjLlt02Dj9cn1_pxcZLub0oWlsuQS2gqGyZlaW3TySBK40FJhWEYGFrGxz0eOXG4kocQ==
gAAAAABmiGBe4kaJLtMhIVtkzXkLFkWeQI1fEoJ4fNJCQas9xnEi-n672yrrS8wr7ZJd193gTp6t7FOhrrIM6KlnXCax0PBXXQ==
gAAAAABmiGBeO8kOd-oG3AJ-hRBM3xYHI-paZGLzHlPUFnAWl3iftg0hLvbDEACyn32JwLH-53FEIV5D3IDw88XubwCMboHAzw==
gAAAAABmiGBe9QPlqsPdu-FtddreMB0L19_frNSz0qWKuelo9Gv5rheGpBSuOI7iKFrBXQFziZJQpF9LrzYBHNAsqzPEOqxvEg==
gAAAAABmiGBeP5LsqXg_NFMY7vzs0aGsVK9n5Nay_i_E_ym6hD4KG_bix4u39AqUjb9Xqo-coHgCaKui8V9oCFVXEMIFxxnz3A==
gAAAAABmiGBexoCMcMLDoVJkHzcld4iUE2Tw8SO97GYkrDsf3Nv5mWyuiP4tvfBLDvok1Prz7IDcNu6ioy_IP0v4169THgh0kA==
gAAAAABmiGBetT6P2YhqftrXd8qawxZejTP20_L5IB0MI_gFhCO0dZt_GBZDV0L-45ob60QiVuXAQFDjFT5a7QOPVH8Q5eiyUQ==
gAAAAABmiGBeyxX-J0HZpxGfWxIvBhBNCRdeVdRwc_idW-p4qaejNJRNfkCMvZ-V5ou0bslAuWJu51iQJgMmnfGrWdmgWenPxA==
gAAAAABmiGBeXyiN7FNM5ol6WlQCz8aCwFN5mkY37u9mgX1nt3tlYtht9lovrcBOSQxytOCHjk2TQowrKqyUitu0VK-Byq0p6w==
gAAAAABmiGBeTqYfz3oXMKzLMETkmA-Ua8IcliNFih8TtsfQUiaqyiTWUzmiqJMRVbp_DNWwCs1QVWB5E9xNu_OpdJPZ3xCSvg==
gAAAAABmiGBe8Gdq0EE1p5StrJFxLunocKPcGKV-CJknJ0vOeG8Ma5eJTLhuIMxYxbBzochyJUY9Awipl5C8yU9bY9fjgVIVXA==
gAAAAABmiGBe-GdlZxJalBfeRTRsk2QEKNrom9s4vkn0cX2OpDc3O9vWQ4sZsF-prQzLJpd-_QucJz87wYloPOkDcCyIlUfghQ==
gAAAAABmiGBeJM-M6EdiJFP_tGFFT2Jfi1gAn6zzi81sIMPo94EuVg3MacLdi7M1jMSSeXigmTcZJffP0cHZrmAWKiY73v_--Q==
gAAAAABmiGBe439eGaTd-xNK242LyNdpCb62lh4ycdwkCORZX3C6-Gc48Gav31eh1r4-SH2b5R5ianSSWHeXZTn8GkK9JXPlbw==
gAAAAABmiGBeYR2bhkF5E_JQ2aP64S9Rk3MLUop5l7ePI2KQg55tRZzd45HuwwedvfSbQ6bQnE8YGMbL9F6H_s84Ad2XzBqraw==
gAAAAABmiGBemMMKI3IxikqSbq0PC2mcTnhDWMkYaq8BTxoFPZvcuIviqkXax94vNBnmvW2cb1i8TyWNEjuri_zYBLK9RmdBBQ==
gAAAAABmiGBehBFXbc5a4ZGmVf2QSWmjt9d46sk8MRLwDEW0XZGsBpBhqXFsd-OI4cKcvhIIq_3ekLhVbykueQq6zwhPbL2mxw==
gAAAAABmiGBeqw2AIQ-va8eclQqvMTDOFQrcfvab7zr-KLyDJ3QDt4NUeqZsvS8Sb-nOxjvcSoSxYs6yOlBi6KjMnggmJIjA5Q==
gAAAAABmiGBexrW0Y8F12ZOv_Q36tye3jHauY8ZEdivO-PthNOwbGdsH9hDmUHWbfJNzJjHzL2kcJrKgupY88R9WEjGXn8ZBuw==
gAAAAABmiGBemR2jD_liGj5LcKczsKQi-0uuMmOnWIih0-7umAZoYy3Nzl13FC-Nd0Gj6c4ElqO3dNGl7soM1VqNWXvNxLpwdQ==
gAAAAABmiGBePuRqnyLDCnZTqPBMrDbqRneWhtxZAVqd4npd9bIMv_7KSFq1k_nZG_S3wT11NiWbQUqmucxOvWlblFmYxHpxcQ==
gAAAAABmiGBe9EnAWtWzYrhM1Wqwz2BiijQ07PC8qOHUL5CZkpz4akFl0gb7r9Y-i6v_uuIAg9BATG-rnNSeQtRfYAhVv9qLBw==
gAAAAABmiGBeE0yy6A1e4tYMp2Dz8RbRPKJ3iDt5n-PgzPcMQz63anVMgJmbNXcxvdCn7eKFAJw-qMRhvBtgF_MEkpGnz66l-A==
gAAAAABmiGBexBqB8-W-TStm_-Eb45Tj1tmtwACtdImH9fuffUUePUvYpyK7DynmSLK6s4Hcl27O2muCEp_bPvSPFy0Tb9bpzA==
gAAAAABmiGBeM_fbbOguy8O4iYH6ktnkLp82ZyV9B4lZILhf5QHC4n6g5Ah81cCKPjoHSRqgm77DUAu7EI78fs9YIHL9XHiFtg==
gAAAAABmiGBe9JwFSkg6QhnwWcH1YrN8dnMYg53Z90B_VI91KAA4jmeqZCAUvtxmYvCeFMUuJ350XCqv4wcj_BtJ3SKuPceL-w==
gAAAAABmiGBeIsuGLeD0D2hL5HwWYANwpRzr-IYIE2oRajniXrpf8qZyhI91mlhkXlDLjeFr0TxzXgFwy9EOheXbOvGL3Xs_Dg==
gAAAAABmiGBevIc9u4DzZc-gCqXX0f4EJXBMVYzBg69Q0bxEWKyAzkpdTCHqTypJ7nE5EKGXqfUhXsQYWacrdcRWkBmc3ZD6_w==
gAAAAABmiGBeBuJ6KHF1qrF05mnpd77lwL4tmmk6FR3ahXTdZuiHr7xhCccjiFDTffvfHJZc-2ccFm0Kdas5AVKGkDWI4L9bXw==
gAAAAABmiGBeaj50_Ea0jqe-E5kz1T2ywI2UJ__pcMkA5ggEmMtV_X_g1zIL_MVLkHaNPuBbCS5zUErusOQziazQYO51lC9zSw==
gAAAAABmiGBeJ8zpbvRNQMSha8XovkGg4O7Nh1BrtzvuPDrU4j3g9OfrIRRZY6gs9B5Y7X3snvYOFUhv69Vvo5SxtK-eN3n1ug==
gAAAAABmiGBega5Bct8hU5YwFaV-sln_C0Nkqk874oeExWvLOA63bHUS-fMWaYf20KssRlR5F43QVCV8p37QMOzAW2SBzTEgVw==
gAAAAABmiGBeKh6N_zSAYIjlMi0sYPiaeiLOTMLlE4VdDCVYiaNiA9tW5gdnVkXJRS7bEGf-EFQ86qed0B_lAixe-Q30kEyDsw==
gAAAAABmiGBeOLbCos9_j0Vd9TIaK-f4YZch5yxafMWWKyuHTH3tLT3oqac9n4khN1AVs-Osusv5EVrYl3l4C7TVHFoAeqANWg==
gAAAAABmiGBedjUiInJnLly6cylrkP2HlRA2vj6VboTeucpZ_U9sFtygbee5oglUGS66x8hj2FW7qDWA9UZty--aOKP3LixWng==
gAAAAABmiGBe1XwlvfvBn9eM9zXfHD-vlTblMIRvDkFSizj-pYh9cU_-672zFjr_zdLPYo0K9bB0k8-Lz9fwf3liiYhLx1OTnA==
gAAAAABmiGBe9oO3mQhwHiTQAep6U5DLQULXoD1KLg0fMWc1z7iaXQ_87RcUZ9Vg10IgA7CR5aVRXFmwugOi2ROO4Umj59bt9Q==
gAAAAABmiGBemOYRBnd9PaLchxYZehMr__NL4WMG7c8dTiWxCQBWTUXsPBYVkR-pthLGXySVNK5JWa_ECPSoUk4Su0yOuuGAHg==
gAAAAABmiGBeJs7d4P240rE71BVNsOCJQMM5nnw9u5CP2t6wh3mq79W0xsj0SDQS0OWvmJaPo9JWD53dp_2vkF5uBx0IWhQr7Q==
gAAAAABmiGBekT7VZL9zsgvDyYqM4gMfvLGBvhi7tTIojtD7Sf0crCh4A1y-VMqHPkt-lhrXnt0-z97zbPj4YzyTehVrJbTOdA==
gAAAAABmiGBeXuC2QEOzaY6Rh4UPKdU5Uai4rgJPdJxwecWnzjMyQ75DzZFZnR63fRJzZDzEX0MDB2X0qnMc0P_6poPcS4CgdA==
gAAAAABmiGBehbwKXfjeBurCrBGAW-pFLJvAIjcjR4uFs3XUsxz94FgSRhcP3EM3wF0L8UF9BaKjxxqmDgF0XaH7dJG1LEx_AA==
gAAAAABmiGBeHTV3hklgTW2zdzuUIhhptx5hFFQkOuSJGDDFrqa-EvQM9m85QOtvdaapAZ7T9VDBugZk5OGM_MQr3PgH1uxtoA==
gAAAAABmiGBekcytpYfddImZACFJ2Gdmodr1JBQ5-hWAQwfpHMe0p0kdnoulEZh_BtWiV43qBomFfo13Xi6YPt-57jpoS_fXXQ==
gAAAAABmiGBe9DeqZFC2ImX4vpBuoA43oftxH3372G98Na9V0u0q-Rfmmfvkf3S8osgBfwQmPQh_VkTASttBlYgZR19A_ijJVQ==
gAAAAABmiGBe12el2Q7LMDwXDiSv-1zEaWJurFTIm511H7fXVmPR5q8H_wJjHdDxYv2x8N3JsalGuh7g4_OQZVntQVuZRt9Dgg==
gAAAAABmiGBexaQmgCKkVpP4oeZbGgBwz-4QdbR9VpIg2rMbDMlxxgD1s61Ps8bUGFb9CSy0fKaN7YOZfsfVc5kWmKAXM-ZQrg==
gAAAAABmiGBeIShjv9ZgAMXJzEIgZG_k0E4P63HB4OosbAEXnsZglYp3BL6Lfp_gsCvDOibipowoLE9bRLDCM9a6bnS1FHPe-w==
gAAAAABmiGBemNkvc_zd-Ignvo0hbp65JWctJTae0yxRe-jTGAnFfEHuf3ne3GrDijyjE6yOYShdu0HJwiFZ7SGwN6RnzEQLSg==
gAAAAABmiGBe_m93N4cuKOfYjH_FEsjeD0WEsRIzfe16r_w0pi2M1dYzfkJlfu-QCSfZA4CllE5rsgMS4AK-wXEvRD3c9aa60Q==
gAAAAABmiGBea7Z0Mn4HoT0u0jdzcfsi-ZSuLqvuvsQGokkHrFN9xTKUOGKy3mt56PigeDW3QO9v7kXX-piZMxbB7-3lZjR12A==
gAAAAABmiGBeUc86QwIoOQior8bSDbqq5bOcldHwM42_Mn1kylO9tWwTsTD5Vyb9KbT-LMlwXVs_JdzERE-QPSr9NOk63kcGfw==
gAAAAABmiGBey4wXPm_zRlPJMko6DZFbf0prCGhT8ZlPtvOJ_OqWRbWRdi9su3uBF-DKB2xqnA2CYXuRQLedb3GcoRlM9FmRug==
gAAAAABmiGBeNEbOmK0ZjCRn1lKViedDxY1DLQue9dMshgO3w6N-MHIHJqSeSby5XMIl52hM1PF2EeiIU-oCytoKJnmysvEMUA==
gAAAAABmiGBebsZIk75_eNbPhtgEyqWUQqozxZzOLtEx4rY_Ts6x8Hy6OXWpwQTfDaHMgGvzI0zjG7JD5D8xkwEGk9TrbILbGg==
gAAAAABmiGBed3P9LP2XUyQmMqOsfgd1aTdpqQfvSOZGO_rnKqidO79HdRHWHcqCbKXLv8asOvwKYtCTFGhkKOce64eBVDiowg==
gAAAAABmiGBexl4iai6_Le3Scd84J7nv4iGCCaXPSTAugbA5ReGNqbaWBt5iqeRJt2wKns76dCFvygSdVr48erFCP1GtJfzefg==
gAAAAABmiGBeHbOUjc0VBNxxaWVnPnfwQl4tYosP4ZadVRCU3pTofOQrRDl4Yw8blCZGDZC5N7_mu4SMQ9PqyC7jrE3mF8B00A==
gAAAAABmiGBendqbvTJ6PMRvFxwLyoOQNrdaAz3GQzPAUFFlpe3XVofMP6SCR143NR60dms6XGuBAI6NfXFsE-9ApI_rLz007w==
gAAAAABmiGBej6aGyN5xZ7HMiQ166J2UVM4dWxNg9ZNdaGcE9if0QO-EO_4PQ47oHPhcev9YAfsFs7nGrtB4lDmnRxgTFykXlg==
gAAAAABmiGBe9mwN-NwDVLWwCHR4Pk6sKvLssrs8eMCWdLumKmsgs8HpYkTI1pdxf-T_WFMKEVWOSa80_i7_C3TCdBAeGqXINw==
gAAAAABmiGBeKdG87FUw9nCDno_LzkYE1_npOPsKr-GnOKw9Wl6FFTAPHUi_UforcPRy3c9jZ9GsukkMLTSBrlQ-2qWPMriihA==
gAAAAABmiGBe_YC7bNDSAM2VShglash712cXq0gyLI_SOCmL8GmsGsN6htksjutOKrWko51f_rPiPquwWCLPiZMy1TBYM_o3wA==
gAAAAABmiGBeux4zYQaVPwSukoXiZ306ctICDzFy6TrswbpJ_eoM5RsmC8rLb1uSS6BqHfB2xvTQp86Alv3HFUjhqNhW7txX7A==
gAAAAABmiGBe8SRs2qtWFOzfg_fsxuzpMGcmynQBf5ewERp7RMaxh5dLMeNHzg_c1enoVO_B8yC9eABQgzhnNjhVur3OF7a-ag==
gAAAAABmiGBeRrBfV6UPNHvI16AAPQ5qD3TCXGZqN4hpeJOMffTuifSFoO2FnqD2sW1itDr9yYYbwAeg6D43XLadR0E5vGQ5rA==
gAAAAABmiGBezlGR-RIigHXyxIvh0LMqqRslQVkAT3zOhtd92Ic0_Sn4YJNpBT2iIOBealiYr9aLjLOXrupg-ctzgZxdYbxEYQ==
gAAAAABmiGBeok3ptnUxj4jT2iNN6pN1pHarHAFbYONJfq3dJXyNS-V04mpy-ASpETJhVqvd4yXqIMMIyOOMT7z5sOZTAfQCxg==
gAAAAABmiGBe4pIVucPnDujegjvAE6hSGwQmH5K5waTHsH4TT-Pzlgh3hZR7T-4bn6ZasPa49QazOjTGB1-LQpSWwjOQZzbIRg==
gAAAAABmiGBeRLJFrM1lpFPGSjmKgPFLvwYzWqHo7VOX4SBtmCj9vjSfUQ2YldwPUw9BU4NcKopX8fBglgbAt7mbQIZJJUzgGQ==
gAAAAABmiGBeyiwBCgCxOIX4jFoMtAVRpk4YiDvPDmym8Yt0wrY3xLBmOXoJpR38EUe050Ue_TuvXpCrv3rIqdSxyAGlcBT9xQ==
gAAAAABmiGBez4uDP-C7_eHS2FBCJcELHB2jmS0YdkBDcC-5yyv6qMLEGAvq5Fwu24Jl1Ses6GJ06rvIsaKPex-f9GR1Ok688g==
gAAAAABmiGBe2IhQNbEtzGThVQqDyYmLbSzYvjluDXEqDZ1cJ-W8W0FvDy8WYDcdOEckXA2qcaqgW891lVt1a-Ag9y-x9cOv9Q==
gAAAAABmiGBeNue5LnAzFuUJJqWpH9b3eDQ_-OHKzP9SRWBq1ziQnkRDZIczfMcgiUDC-FtEZd4KtKx2q1NUzmFMEMFGINxoew==
gAAAAABmiGBeRwxsQvki0wchQrFSiQYfx1EEWyeQRY1_QLve7SVrrTAHJNsDYc_Ukd3gYaOwHik8cDo1P3uW3QocveKhh4tqqw==
gAAAAABmiGBeYAt6Rm1sBhdjvVpbINFenM9ibxbJCt7G66Nuh4KOskYNqFj1rOmMFOgx2NJKnhST284E85sMaDyoPsY1X2KkDA==
gAAAAABmiGBer2fAvPOL8pvD_6qqtfxALjikN_Phz7z_QkKSn72N6i5giFgBz-8G7jWninRuKK8YarXVD63bSpmO6tUI3Fqvrg==
gAAAAABmiGBensdSs1E6eq1jvYJCTiuIn8fozM76xHnVZnSvOdjnodRR4mg9EcxThtb8msIjGYXQO3nBL65gNL3OhV4vPsvjbg==
gAAAAABmiGBeLToU73rGQEpBYuxGuR7qbKTV8cOkPocp6yuHL1a_e2tk5Ctm6AA2_QUklkZ44rk8-w5D4lrMvPB6p1F9TefRIg==
gAAAAABmiGBerm0abfRe96MpADs8SlNetoe2Tns3fbRvtsZ7-4_L549zmdjX3jpQgrg7Ky72366CTNJJRLFdYrBd1E50n0nT_g==
gAAAAABmiGBeGVZEll9cc03GgDn-lXdOdqtov1dNu4XeIaQCTWz4yV2FV00eg_nUzxRSpCnczly5KyVRX8lvOuc7p0V9jnnh5g==
gAAAAABmiGBel-7ZD14sHhTFcJJtixgmuR5LryvT_iTffEBNER2pjI8MTeu09WahuvD4XK0OXPgTAkgI2aYkg5Z0GtHeS3gZuw==
gAAAAABmiGBeAqockNltibw6NEWFfCZTpAObwthtoEXleSt5bTWp82MzpDQkeubvELY58vEjPXBPPAyciFdVClmGK_c_hF5cmQ==
gAAAAABmiGBeIg-HT8CHoUCK6DZQYNFQ8bDNcAkE1i0K1MBQJewgKBtiqMLmms_DQ6g9rPY1HOnC3chBzxaPNub696nEZgXmjg==
gAAAAABmiGBef4RIdvDHRkfc9rrm8Z-ca_IEqV1e5DSe7fL4ueMNt_D8XzXZRD0K96Q9mUCd3VDUH08n6rXviq-24CoKYjRdbA==
gAAAAABmiGBeIDs9L2Q0_fGbDyJaYchZD0ft32rgins-USDicuXUhHsULgc_ObD3atLOS3QCKnr30SNmWl6vFBjGr7TasrzHfw==
gAAAAABmiGBeKaVFgl44tcBwmeQO6C58EEj0OmiDfAhVLz4Lzqouo8bwPJT2TbxdNbnm5dyJVbreakpgVhXtsASFETkRNaRpvw==
gAAAAABmiGBeEFQsoQwYC-FgmPWCtUIytNXsvZ_pjGAzRsqqCzPnZwvXJ2LHmXD4XPNxgRjX8xPs2Hu6NZvVds2Y0c6zHUqltw==
gAAAAABmiGBeu_-GAe-GsS2NcvlT7kO0ObMgzJkvv8sKh7uZ4bMYiXG9COrs9WtpDxcEk3Dc_gwteOkmUjbH0hKXipEwmLi_EA==
gAAAAABmiGBewlW_6gVFFKi65HywAhJ7bl5EMbrlWxy_dtbZx-agn2aPJs9embZzEDBV-VGFo3t43nvOpirFkk8Epu1EmM9d5w==
gAAAAABmiGBedHCr6QdKngIVcMY3JNF5T-YRfDnvqoBOPAa2uM_Ra8hI0qdhJqMnWT4zPW-SbWXTzuvyk_HAlOfP4QHuvQ0DCA==
gAAAAABmiGBeAudgnlYElvlD4i77a-Nencbne2SV0MAPamLVGhjZJypXFV3Bp8qYkAf6drG1oK0lyRShYcIzIi5OHvcjF3YkIw==
gAAAAABmiGBejhSKOZUDpun9YcfN8RaGDuAs0bb5EnZ4hNx07cSVzMa0L4sa0Dr8sI_RkweMcqkyu5SpzOpHQz7pzSWU133KdQ==
gAAAAABmiGBekuxD8Um9QUlUJbDfDnD0LrdP2FDjhGYfzMA_bZk1B9zPiUSXWZ1MGYiGGifeZi1Bi05GLQVqd1RrKghaxGT67Q==
gAAAAABmiGBeVPBAOwK5atC0XcigtA1tDDlyYzB7t4jaH2EMcegy38tsIYvig4FXncne1Im6r4iuQ0xlqycp-3lvQ5TbC0ofuA==
gAAAAABmiGBehzZovJnfqU27lfXtkK0J_Mr58-HgMcv_jZIWX7MD4YulvoOcA0nMzFoC2AG9UNV35ioHAnJOyRhZeUxzeKc_mA==
gAAAAABmiGBe_MAXBrQrL4-8lwsCcQWBnLoOZhzvmPET8HenppfgPqMCjoA8iH9oelsxsRAKnuu531CsjuWAN3lrk3kML6AjQQ==
gAAAAABmiGBe7SclAu6-Ej_QKAqNI1j076rDrtTxyPoodvdUNc6wFkmiQMgB8Wnep-jh9USmVTNf3xLDtTkESWvaKi389LMz7g==
gAAAAABmiGBeAexOEyaxQkPfhio7Ku3hWrsUYtq3h10kzIgqNRFPe7Hf5qx5JwIP6Iw20oiRihGmpAbCrtj5DnH58yMUYyARDg==
gAAAAABmiGBeA01RT2Gv4wunnAdcEXgjEdGfPml8L1chMKVteh4ETLG__1vmMjlylRJd_duASyymcPAVRKGbliCdCT6bNsQNng==
gAAAAABmiGBeQwxwXIwe92jm_ti8E_Q-hneMkHXK0cyJTirp7DHEkEshkxM4HacICVaGRJ0teQ4BRCVmNrNLDTwP9KiAFSYtlg==
gAAAAABmiGBejTB8C55VArGvejJzipT6NT1c8DutiDICTlV1P8JAwiyz3T1IBlutirXL39d216ZcyMtUYxrpOxVEbk92N_WeWA==
gAAAAABmiGBeLtS8VgP_PIBqT2exmtET6jj_lhpdL-qzolFtJ3pWUEXR7WLfcXu9-vsXOnrwgYxXjHsFB_pFgAUkYOFWbgN5wQ==
gAAAAABmiGBe06x5AZsswb3e-gORZ4_I8wzL8IlI90hJylk-fs2IlBiKRqv0UI8RxvncQELiBcseTLTxpAwxeHb3tT75TcCqMA==
gAAAAABmiGBeT2ClmE02iROcOXC_YrQOya_2OZu04nIV0q4nLnUCtE1_QD88vudAUV4wqjKq211x0_moGz81yGhH0HUHzQOniw==
gAAAAABmiGBecAOu9n3rX7TAaoSvkNS24lxumeebT-92yH-TQLBScy2Qv--5MrhI8ohe5Az4okdY9NbQnYBI2nJDVU7l-2ccvw==
gAAAAABmiGBeBu1wJLSnvBhZDpS6iMEHC9oDjSyItY4NEuDzwOYyhVerY26fjXOuRtGED2_NXGRiD_t31QT8evNZOCC4cXXDqw==
gAAAAABmiGBekmNfazsSiK8pXdjbWttSd9IGiFc-VAAwP4p2AN_j8QkqoamZuUVzxP8VrVRDd8RI038xH5xTjUtv3fFWZocUxQ==
gAAAAABmiGBeVzEtbmNqI7CDg8K0A7aT9U3-9ly32_c-vHPdw_U5SFC_30DQ3huH3kf_D7jQCLDoFJzFTlSTBw_2-82MWemSlQ==
gAAAAABmiGBejbeqrR0FrkyQkpsU9pDw4expVHaTuE4VFOUbykMj3KIZ7SBx30BqffPMMZqAhwpuNbIHeVZpGr_0RgHpTrEBog==
gAAAAABmiGBeMw-OE-AS7nQDxEixUHu0he4WZ7W4Lp8iLYALiPEh5ELQnRHK9blGTkkQ3zljZcB8wBap8fF7CAFU9dH9LNpo2A==
gAAAAABmiGBebVn-mEieiBnk6-qHtMH3Zv85jPXG4zRUWhYEVMQLizHziBRW4gBYo_sEZUbfWrFKvydDASxyMIaDrHHRrZHQyw==
gAAAAABmiGBeIrd8LgnGqKHuVL7URqRa8bKgO1uTg4N8D7bZ7BxDMXN7pDTseka2qDtJ9TiChWE6_qtxYj8uNwa2ksTiZeLRxQ==

1
data/wordlist.key Normal file
View File

@ -0,0 +1 @@
noJII7Djv1uQXw1hxEoHxe0LJ2P21uQJ8WryKMPPNb8=

133
main.py Normal file
View File

@ -0,0 +1,133 @@
import discord
from config import config
import logging
logging.basicConfig(level=logging.INFO)
TOKEN = config['DISCORD_TOKEN']
GUILD_ID = config['GUILD_ID']
intents = discord.Intents.default()
intents.message_content = True
class Selena(discord.Client):
def __init__(self):
super().__init__(intents=intents)
self.tree = discord.app_commands.CommandTree(self)
self.xp_module = None # Initialize as None
self.load_modules()
async def setup_hook(self):
logging.info("Setting up modules...")
self.tree.copy_global_to(guild=discord.Object(id=GUILD_ID))
await self.tree.sync(guild=discord.Object(id=GUILD_ID))
logging.info("Modules setup and commands synchronized")
# Call setup_hook for xp_module here
if self.xp_module:
await self.xp_module.setup_hook()
def load_modules(self):
if config['modules']['currency']['enabled']:
from modules.user.currency import Currency
currency = Currency(self)
currency.setup(self.tree)
logging.info("Currency module loaded")
if config['modules']['xp']['enabled']:
from modules.user.xp import XP
xp = XP(self)
xp.setup(self.tree)
self.xp_module = xp # Set the xp_module attribute
logging.info("XP module loaded")
if config['modules']['birthday']['enabled']:
from modules.user.birthday import Birthday
birthday = Birthday(self)
birthday.setup(self.tree)
logging.info("Birthday module loaded")
if config['modules']['destiny2']['enabled']:
from modules.social.destiny2 import Destiny2
destiny2 = Destiny2(self)
destiny2.setup(self.tree)
logging.info("Destiny 2 module loaded")
if config['modules']['music']['enabled']:
from modules.music.music import Music
music = Music(self)
music.setup(self.tree)
logging.info("Music module loaded")
if config['modules']['youtube']['enabled']:
from modules.social.youtube import YouTube
youtube = YouTube(self)
youtube.setup(self.tree)
logging.info("YouTube module loaded")
if config['modules']['twitch']['enabled']:
from modules.social.twitch import Twitch
twitch = Twitch(self)
twitch.setup(self.tree)
self.twitch_module = twitch
logging.info("Twitch module loaded")
if config['modules']['update']['enabled']:
from modules.admin.update import Update
update = Update(self)
update.setup(self.tree)
logging.info("Update module loaded")
if config['modules']['data_privacy']['enabled']:
from modules.admin.data_privacy import DataPrivacy
data_privacy = DataPrivacy(self)
data_privacy.setup(self.tree)
logging.info("Data Privacy module loaded")
if config['modules']['terms_privacy']['enabled']:
from modules.admin.terms_privacy import TermsPrivacy
terms_privacy = TermsPrivacy(self)
terms_privacy.setup(self.tree)
logging.info("Terms and Privacy module loaded")
if config['modules']['knucklebones']['enabled']:
from modules.games.knucklebones import setup as knucklebones_setup
knucklebones_setup(self)
logging.info("Knucklebones module loaded")
if config['modules']['wordle']['enabled']:
from modules.games.wordle import setup as wordle_setup
wordle_setup(self)
logging.info("Wordle module loaded")
if config['modules']['profiles']['enabled']:
from modules.user.profiles import Profiles
profiles = Profiles(self)
profiles.setup(self.tree)
self.profiles = profiles # Properly set the profiles attribute
logging.info("Profiles module loaded")
if config['modules']['tiktok']['enabled']:
from modules.social.tiktok import TikTok
tiktok = TikTok(self)
tiktok.setup(self.tree)
logging.info("TikTok module loaded")
bot = Selena()
@bot.event
async def on_ready():
logging.info(f'{bot.user.name} has connected to Discord!')
@bot.event
async def on_message(message):
logging.debug(f"Message from {message.author}: {message.content}")
if message.author == bot.user:
return
if bot.xp_module:
await bot.xp_module.handle_message(message)
bot.run(TOKEN)

View File

@ -0,0 +1,51 @@
import discord
from discord import app_commands
import sqlite3
class DataPrivacy:
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
async def fetch_user_data(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM user_data WHERE user_id = ?", (user_id,))
data = cursor.fetchall()
conn.close()
return data
async def delete_user_data(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM user_data WHERE user_id = ?", (user_id,))
conn.commit()
conn.close()
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="request_data", description="Request your stored data")
async def request_data_command(interaction: discord.Interaction):
user_id = interaction.user.id
data = await self.fetch_user_data(user_id)
if data:
await interaction.response.send_message(f"Your data: {data}", ephemeral=True)
else:
await interaction.response.send_message("No data found for your user.", ephemeral=True)
@tree.command(name="delete_data", description="Request deletion of your stored data")
async def delete_data_command(interaction: discord.Interaction):
user_id = interaction.user.id
await self.delete_user_data(user_id)
await interaction.response.send_message("Your data has been deleted.", ephemeral=True)
if not tree.get_command("request_data"):
tree.add_command(request_data_command)
if not tree.get_command("delete_data"):
tree.add_command(delete_data_command)
def setup(bot):
data_privacy = DataPrivacy(bot)
data_privacy.setup(bot.tree)

View File

@ -0,0 +1,73 @@
import discord
from discord import app_commands
import sqlite3
class TermsPrivacy:
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
self.privacy_policy_url = "https://advtech92.github.io/selena-website/privacy_policy.html"
self.terms_of_service_url = "https://advtech92.github.io/selena-website/terms_of_service.html"
async def user_opt_out(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("INSERT INTO opt_out_users (user_id) VALUES (?)", (user_id,))
conn.commit()
conn.close()
async def user_opt_in(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM opt_out_users WHERE user_id = ?", (user_id,))
conn.commit()
conn.close()
async def is_user_opted_out(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT 1 FROM opt_out_users WHERE user_id = ?", (user_id,))
result = cursor.fetchone()
conn.close()
return result is not None
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="privacy_policy", description="Show the privacy policy")
async def privacy_policy_command(interaction: discord.Interaction):
embed = discord.Embed(title="Privacy Policy", url=self.privacy_policy_url, description="Read our privacy policy.", color=discord.Color.blue())
await interaction.response.send_message(embed=embed, ephemeral=True)
@tree.command(name="terms_of_service", description="Show the terms of service")
async def terms_of_service_command(interaction: discord.Interaction):
embed = discord.Embed(title="Terms of Service", url=self.terms_of_service_url, description="Read our terms of service.", color=discord.Color.blue())
await interaction.response.send_message(embed=embed, ephemeral=True)
@tree.command(name="opt_out", description="Opt out of using the bot")
async def opt_out_command(interaction: discord.Interaction):
user_id = interaction.user.id
await self.user_opt_out(user_id)
await interaction.response.send_message("You have opted out of using the bot.", ephemeral=True)
@tree.command(name="opt_in", description="Opt back in to using the bot")
async def opt_in_command(interaction: discord.Interaction):
user_id = interaction.user.id
await self.user_opt_in(user_id)
await interaction.response.send_message("You have opted back in to using the bot.", ephemeral=True)
if not tree.get_command("privacy_policy"):
tree.add_command(privacy_policy_command)
if not tree.get_command("terms_of_service"):
tree.add_command(terms_of_service_command)
if not tree.get_command("opt_out"):
tree.add_command(opt_out_command)
if not tree.get_command("opt_in"):
tree.add_command(opt_in_command)
def setup(bot):
terms_privacy = TermsPrivacy(bot)
terms_privacy.setup(bot.tree)

42
modules/admin/update.py Normal file
View File

@ -0,0 +1,42 @@
import discord
from discord import app_commands
import os
import subprocess
import logging
import sys
class Update:
def __init__(self, bot):
self.bot = bot
self.logger = logging.getLogger('Update')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
async def update_bot(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True)
await interaction.followup.send(embed=discord.Embed(description="Updating Selena...", color=discord.Color.green()))
self.logger.info('Starting update process...')
try:
subprocess.run(["git", "pull"], check=True)
self.logger.info('Successfully pulled updates from git.')
await interaction.followup.send(embed=discord.Embed(description="Update complete. Restarting...", color=discord.Color.green()))
os.execv(sys.executable, ['python'] + sys.argv)
except subprocess.CalledProcessError as e:
self.logger.error(f'Error during update: {e}')
await interaction.followup.send(embed=discord.Embed(description=f"Update failed: {e}", color=discord.Color.red()))
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="update", description="Update the bot from the repository")
async def update_command(interaction: discord.Interaction):
await self.update_bot(interaction)
if not tree.get_command("update"):
tree.add_command(update_command)
def setup(bot):
update = Update(bot)
update.setup(bot.tree)

View File

@ -0,0 +1,253 @@
import discord
from discord import app_commands
import random
import logging
import sqlite3
class KnucklebonesGame:
def __init__(self, player1, player2, bet=0):
self.players = [player1, player2]
self.turn = 0
self.columns = {player1: [[], [], []], player2: [[], [], []]}
self.scores = {player1: 0, player2: 0}
self.bet = bet
self.current_dice = None
def roll_dice(self):
self.current_dice = random.randint(1, 6)
return self.current_dice
def place_dice(self, player, dice, column):
column -= 1 # Adjust for 1-based index
self.columns[player][column].insert(0, dice) # Place at the top of the column
self.clear_matching_dice(player, dice, column)
self.calculate_score()
def clear_matching_dice(self, player, dice, column):
opponent = self.other_player()
opponent_column = self.columns[opponent][column]
self.columns[opponent][column] = [d for d in opponent_column if d != dice]
self.calculate_score() # Update score after clearing dice
def calculate_score(self):
for player in self.players:
total_score = 0
for column in self.columns[player]:
if column:
column_score = sum(column) * len(column)
total_score += column_score
self.scores[player] = total_score
def next_turn(self):
self.turn = (self.turn + 1) % 2
def current_player(self):
return self.players[self.turn]
def other_player(self):
return self.players[(self.turn + 1) % 2]
def is_game_over(self):
return all(len(col) == 3 for col in self.columns[self.players[0]]) or all(len(col) == 3 for col in self.columns[self.players[1]])
def winner(self):
if self.scores[self.players[0]] > self.scores[self.players[1]]:
return self.players[0]
elif self.scores[self.players[1]] > self.scores[self.players[0]]:
return self.players[1]
else:
return None # It's a tie
def render_board(self):
board_str = "```\n"
board_str += f"{self.players[1].display_name}'s Board\n"
board_str += self.render_player_board(self.players[1], True)
board_str += "---------\n" # Separator between boards
board_str += f"{self.players[0].display_name}'s Board\n"
board_str += self.render_player_board(self.players[0], False)
board_str += "```"
return board_str
def render_player_board(self, player, is_opponent):
board_str = ""
board_str += " ".join([f"{sum(col)}" for col in self.columns[player]]) + "\n" # Column totals
for row in range(3):
for col in range(3):
if is_opponent:
dice = self.columns[player][col]
else:
dice = list(reversed(self.columns[player][col]))
if len(dice) > row:
board_str += f"| {dice[row]} "
else:
board_str += "| "
board_str += "|\n"
board_str += f"Score: {self.scores[player]}\n" # Player's total score
return board_str
class Knucklebones:
def __init__(self, bot):
self.bot = bot
self.games = {}
self.logger = logging.getLogger('Knucklebones')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.db_path = 'data/selena.db'
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="start_knucklebones", description="Start a game of Knucklebones")
async def start_knucklebones_command(interaction: discord.Interaction, opponent: discord.User = None, bet: int = 0):
player1 = interaction.user
player2 = opponent or self.bot.user
if player1 == player2:
await interaction.response.send_message("You cannot play against yourself!", ephemeral=True)
return
if bet > 0 and not await self.has_enough_kibble(player1.id, bet):
await interaction.response.send_message("You do not have enough Kibble to place this bet.", ephemeral=True)
return
game = KnucklebonesGame(player1, player2, bet)
thread = await interaction.channel.create_thread(name=f"Knucklebones: {player1.display_name} vs {player2.display_name}", type=discord.ChannelType.public_thread)
self.games[thread.id] = game
if bet > 0:
await self.deduct_kibble(player1.id, bet)
if player2 != self.bot.user:
await self.deduct_kibble(player2.id, bet)
initial_message = await thread.send(f"{player1.mention} has started a game of Knucklebones against {player2.mention}!\n{player1.mention}, it's your turn to roll the dice.", view=RollDiceView(self.bot))
self.games[thread.id].message = initial_message
await interaction.response.send_message("Game started in a new thread!", ephemeral=True)
@tree.command(name="check_score", description="Check the current score in Knucklebones")
async def check_score_command(interaction: discord.Interaction):
game = self.games.get(interaction.channel_id)
if not game:
await interaction.response.send_message("There is no game in progress in this thread.", ephemeral=True)
return
scores = [f"{player.mention}: {score}" for player, score in game.scores.items()]
await interaction.response.send_message("Current scores:\n" + "\n".join(scores))
if not tree.get_command("start_knucklebones"):
tree.add_command(start_knucklebones_command)
if not tree.get_command("check_score"):
tree.add_command(check_score_command)
async def update_game_message(self, game, interaction, content, view=None):
try:
await game.message.edit(content=content, view=view)
except discord.NotFound:
game.message = await interaction.channel.send(content=content, view=view)
async def play_bot_turn(self, channel, game):
dice = game.roll_dice()
column = random.randint(1, 3)
game.place_dice(self.bot.user, dice, column)
if game.is_game_over():
await self.end_game(channel, game)
else:
game.next_turn()
await self.update_game_message(game, channel, f"{self.bot.user.mention} rolled a {dice} and placed it in column {column}.\nIt's now {game.current_player().mention}'s turn!\n{game.render_board()}", view=RollDiceView(self.bot))
async def end_game(self, channel, game):
winner = game.winner()
if winner:
await self.award_kibble(winner.id, game.bet * 2)
await self.bot.profiles.record_win(winner.id, "Knucklebones")
loser = game.other_player()
await self.bot.profiles.record_loss(loser.id, "Knucklebones")
await self.update_game_message(game, channel, f"{winner.mention} wins the game and {game.bet * 2} Kibble!\n{game.render_board()}")
else:
await self.update_game_message(game, channel, f"The game is a tie!\n{game.render_board()}")
del self.games[channel.id]
await self.schedule_thread_deletion(channel)
async def schedule_thread_deletion(self, channel):
await discord.utils.sleep_until(discord.utils.utcnow() + discord.timedelta(minutes=2))
await channel.delete()
async def has_enough_kibble(self, user_id, amount):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT balance FROM guild_currency WHERE user_id = ?", (user_id,))
row = cursor.fetchone()
conn.close()
return row and row[0] >= amount
async def deduct_kibble(self, user_id, amount):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("UPDATE guild_currency SET balance = balance - ? WHERE user_id = ?", (amount, user_id))
conn.commit()
conn.close()
async def award_kibble(self, user_id, amount):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("UPDATE guild_currency SET balance = balance + ? WHERE user_id = ?", (amount, user_id))
conn.commit()
conn.close()
class RollDiceView(discord.ui.View):
def __init__(self, bot):
super().__init__(timeout=None)
self.bot = bot
@discord.ui.button(label="Roll Dice", style=discord.ButtonStyle.primary)
async def roll_dice_button(self, interaction: discord.Interaction, button: discord.ui.Button):
game = self.bot.knucklebones_module.games.get(interaction.channel_id)
if not game:
await interaction.response.send_message("There is no game in progress in this thread.", ephemeral=True)
return
if interaction.user != game.current_player():
await interaction.response.send_message("It's not your turn.", ephemeral=True)
return
dice = game.roll_dice()
await interaction.response.edit_message(content=f"{interaction.user.mention} rolled a {dice}! Choose a column to place it in.\n{game.render_board()}", view=PlaceDiceView(self.bot, dice))
class PlaceDiceView(discord.ui.View):
def __init__(self, bot, dice):
super().__init__(timeout=None)
self.bot = bot
self.dice = dice
@discord.ui.button(label="Column 1", style=discord.ButtonStyle.secondary)
async def column_1_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.place_dice(interaction, 1)
@discord.ui.button(label="Column 2", style=discord.ButtonStyle.secondary)
async def column_2_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.place_dice(interaction, 2)
@discord.ui.button(label="Column 3", style=discord.ButtonStyle.secondary)
async def column_3_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.place_dice(interaction, 3)
async def place_dice(self, interaction, column):
game = self.bot.knucklebones_module.games.get(interaction.channel_id)
if not game:
await interaction.response.send_message("There is no game in progress in this thread.", ephemeral=True)
return
if interaction.user != game.current_player():
await interaction.response.send_message("It's not your turn.", ephemeral=True)
return
game.place_dice(interaction.user, self.dice, column)
if game.is_game_over():
await self.bot.knucklebones_module.end_game(interaction.channel, game)
else:
game.next_turn()
await self.bot.knucklebones_module.update_game_message(game, interaction, f"{interaction.user.mention} placed {self.dice} in column {column}.\nIt's now {game.current_player().mention}'s turn!\n{game.render_board()}", view=RollDiceView(self.bot))
if game.current_player() == self.bot.user:
await self.bot.knucklebones_module.play_bot_turn(interaction.channel, game)
else:
await interaction.channel.send(f"{game.current_player().mention}, it's your turn to roll the dice.", view=RollDiceView(self.bot))
def setup(bot):
knucklebones = Knucklebones(bot)
knucklebones.setup(bot.tree)
bot.knucklebones_module = knucklebones

192
modules/games/wordle.py Normal file
View File

@ -0,0 +1,192 @@
import discord
from discord import app_commands
import random
import logging
import sqlite3
from cryptography.fernet import Fernet
import datetime
import asyncio
class WordleGame:
def __init__(self, user, word, date):
self.user = user
self.word = word
self.date = date
self.guesses = []
def guess_word(self, guess):
self.guesses.append(guess)
return self.check_guess(guess)
def check_guess(self, guess):
feedback = [''] * 5
for i, letter in enumerate(guess):
if letter == self.word[i]:
feedback[i] = '🟩'
elif letter in self.word:
feedback[i] = '🟨'
return ''.join(feedback)
def is_complete(self):
return self.guesses and self.guesses[-1] == self.word
def render_board(self):
return "\n".join([f"Guess {i+1}: {guess} -> {self.check_guess(guess)}" for i, guess in enumerate(self.guesses)])
class Wordle:
def __init__(self, bot):
self.bot = bot
self.games = {}
self.logger = logging.getLogger('Wordle')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.db_path = 'data/selena.db'
self.key = self.load_key()
self.cipher_suite = Fernet(self.key)
self.words = self.load_words()
self.ensure_table_exists()
def load_key(self):
with open('data/wordlist.key', 'rb') as key_file:
return key_file.read()
def load_words(self):
with open('data/encrypted_word_list.bin', 'rb') as f:
encrypted_words = f.read().splitlines()
return [self.cipher_suite.decrypt(word).decode() for word in encrypted_words]
def ensure_table_exists(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS wordle (
user_id TEXT NOT NULL,
date TEXT NOT NULL,
word TEXT NOT NULL,
guesses TEXT,
PRIMARY KEY (user_id, date)
);
""")
conn.commit()
conn.close()
def get_today_word(self):
today = datetime.date.today()
word_index = (today - datetime.date(2022, 1, 1)).days % len(self.words)
return self.words[word_index]
async def start_game(self, interaction):
user_id = str(interaction.user.id)
today = datetime.date.today().isoformat()
word = self.get_today_word()
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM wordle WHERE user_id = ? AND date = ?", (user_id, today))
row = cursor.fetchone()
if row:
await interaction.response.send_message("You have already played today's Wordle.", ephemeral=True)
return
else:
cursor.execute("INSERT INTO wordle (user_id, date, word) VALUES (?, ?, ?)", (user_id, today, word))
conn.commit()
conn.close()
thread = await interaction.channel.create_thread(name=f"Wordle: {interaction.user.display_name}", type=discord.ChannelType.public_thread)
self.games[thread.id] = WordleGame(interaction.user, word, today)
initial_message = await thread.send(f"{interaction.user.mention}, welcome to today's Wordle! Start guessing the 5-letter word.", view=WordleView(self.bot))
self.games[thread.id].message = initial_message
await interaction.response.send_message("Game started in a new thread!", ephemeral=True)
async def guess_word(self, interaction, guess):
game = self.games.get(interaction.channel_id)
if not game:
await interaction.response.send_message("There is no game in progress in this thread.", ephemeral=True)
return
if len(guess) != 5:
await interaction.response.send_message("Your guess must be a 5-letter word.", ephemeral=True)
return
feedback = game.guess_word(guess.lower())
if game.is_complete():
await self.update_game_message(game, interaction, f"{interaction.user.mention} guessed the word! The word was **{game.word}**.\n{game.render_board()}")
del self.games[interaction.channel_id]
await self.record_win(interaction.user.id)
await interaction.channel.send("This thread will be archived in 2 minutes.")
await asyncio.sleep(120)
await interaction.channel.archive()
else:
await self.update_game_message(game, interaction, f"{interaction.user.mention} guessed **{guess}**. Feedback: {feedback}\n{game.render_board()}")
async def update_game_message(self, game, interaction, content, view=None):
try:
await game.message.edit(content=content, view=view)
except discord.NotFound:
game.message = await interaction.channel.send(content=content, view=view)
async def record_win(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO wordle_stats (user_id, wins)
VALUES (?, 1)
ON CONFLICT(user_id)
DO UPDATE SET wins = wins + 1
""", (user_id,))
conn.commit()
conn.close()
async def record_loss(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO wordle_stats (user_id, losses)
VALUES (?, 1)
ON CONFLICT(user_id)
DO UPDATE SET losses = losses + 1
""", (user_id,))
conn.commit()
conn.close()
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="start_wordle", description="Start a game of Wordle")
async def start_wordle_command(interaction: discord.Interaction):
await self.start_game(interaction)
if not tree.get_command("start_wordle"):
tree.add_command(start_wordle_command)
class WordleView(discord.ui.View):
def __init__(self, bot):
super().__init__(timeout=None)
self.bot = bot
@discord.ui.button(label="Guess Word", style=discord.ButtonStyle.primary)
async def guess_word_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_modal(GuessInput(self.bot))
class GuessInput(discord.ui.Modal, title="Wordle Guess"):
guess = discord.ui.TextInput(label="Your Guess", placeholder="Enter a 5-letter word...", max_length=5)
def __init__(self, bot):
super().__init__()
self.bot = bot
async def on_submit(self, interaction: discord.Interaction):
await self.bot.wordle_module.guess_word(interaction, self.guess.value)
def setup(bot):
wordle = Wordle(bot)
wordle.setup(bot.tree)
bot.wordle_module = wordle

187
modules/music/music.py Normal file
View File

@ -0,0 +1,187 @@
import discord
from discord import app_commands
import yt_dlp as youtube_dl
import logging
import asyncio
class Music:
def __init__(self, bot):
self.bot = bot
self.logger = logging.getLogger('Music')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'quiet': True
}
self.volume = 0.25 # Default volume (25%)
async def search_youtube(self, query):
with youtube_dl.YoutubeDL(self.ydl_opts) as ydl:
try:
requests = ydl.extract_info(f"ytsearch:{query}", download=False)
return requests['entries'][0]
except Exception as e:
self.logger.error(f'Error searching YouTube: {e}')
return None
async def join(self, interaction: discord.Interaction):
self.logger.debug(f'User {interaction.user} is attempting to join a voice channel')
if interaction.guild.voice_client:
await interaction.followup.send(embed=discord.Embed(description="Already connected to a voice channel.", color=discord.Color.red()))
return
if interaction.user.voice:
channel = interaction.user.voice.channel
try:
voice_client = await channel.connect()
await voice_client.guild.change_voice_state(channel=channel, self_deaf=True)
await interaction.followup.send(embed=discord.Embed(description=f"Joined {channel.name}", color=discord.Color.green()))
self.logger.info(f"Successfully connected to {channel.name}")
except discord.ClientException as e:
self.logger.error(f'Error joining voice channel: {e}')
await interaction.followup.send(embed=discord.Embed(description=f"Error joining voice channel: {e}", color=discord.Color.red()))
except asyncio.TimeoutError:
self.logger.error('Timeout error while trying to connect to voice channel')
await interaction.followup.send(embed=discord.Embed(description='Timeout error while trying to connect to voice channel', color=discord.Color.red()))
except Exception as e:
self.logger.error(f'Unexpected error: {e}')
await interaction.followup.send(embed=discord.Embed(description=f'Unexpected error: {e}', color=discord.Color.red()))
else:
await interaction.followup.send(embed=discord.Embed(description="You're not in a voice channel.", color=discord.Color.red()))
async def leave(self, interaction: discord.Interaction):
self.logger.debug(f'User {interaction.user} is attempting to leave the voice channel')
if interaction.guild.voice_client:
await interaction.guild.voice_client.disconnect()
await interaction.followup.send(embed=discord.Embed(description="Left the voice channel.", color=discord.Color.green()))
self.logger.info(f"Disconnected from the voice channel in {interaction.guild.name}")
else:
await interaction.followup.send(embed=discord.Embed(description="I'm not in a voice channel.", color=discord.Color.red()))
async def play(self, interaction: discord.Interaction, search: str):
self.logger.debug(f'User {interaction.user} is attempting to play: {search}')
if not interaction.guild.voice_client:
await self.join(interaction)
if not interaction.guild.voice_client:
return
info = await self.search_youtube(search)
if info:
url = info['url']
title = info.get('title')
uploader = info.get('uploader', 'Unknown')
duration = info.get('duration', 0)
thumbnail = info.get('thumbnail', '')
self.logger.debug(f'Playing URL: {url}')
try:
ffmpeg_options = {
'options': f'-vn -filter:a "volume={self.volume}"'
}
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **ffmpeg_options), volume=self.volume)
interaction.guild.voice_client.play(source)
embed = discord.Embed(title=title, description=f"Uploader: {uploader}\nDuration: {duration // 60}:{duration % 60:02d}", color=discord.Color.green())
embed.set_thumbnail(url=thumbnail)
await interaction.followup.send(embed=embed)
self.logger.info(f'Now playing: {title}')
except Exception as e:
self.logger.error(f'Error playing audio: {e}')
await interaction.followup.send(embed=discord.Embed(description='Error playing the audio.', color=discord.Color.red()))
else:
await interaction.followup.send(embed=discord.Embed(description='Could not find any results.', color=discord.Color.red()))
self.logger.error('Could not find any results for the search query')
async def pause(self, interaction: discord.Interaction):
self.logger.debug(f'User {interaction.user} is attempting to pause the music')
if interaction.guild.voice_client and interaction.guild.voice_client.is_playing():
interaction.guild.voice_client.pause()
await interaction.followup.send(embed=discord.Embed(description="Paused the current song.", color=discord.Color.green()))
self.logger.info('Paused the current song')
else:
await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))
async def resume(self, interaction: discord.Interaction):
self.logger.debug(f'User {interaction.user} is attempting to resume the music')
if interaction.guild.voice_client and interaction.guild.voice_client.is_paused():
interaction.guild.voice_client.resume()
await interaction.followup.send(embed=discord.Embed(description="Resumed the paused song.", color=discord.Color.green()))
self.logger.info('Resumed the paused song')
else:
await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))
async def stop(self, interaction: discord.Interaction):
self.logger.debug(f'User {interaction.user} is attempting to stop the music')
if interaction.guild.voice_client and interaction.guild.voice_client.is_playing():
interaction.guild.voice_client.stop()
await interaction.followup.send(embed=discord.Embed(description="Stopped the current song.", color=discord.Color.green()))
self.logger.info('Stopped the current song')
else:
await interaction.followup.send(embed=discord.Embed(description="I'm not playing anything right now.", color=discord.Color.red()))
async def set_volume(self, interaction: discord.Interaction, volume: float):
self.logger.debug(f'User {interaction.user} is attempting to set volume to {volume}')
if 0 <= volume <= 1:
self.volume = volume
if interaction.guild.voice_client and interaction.guild.voice_client.source:
interaction.guild.voice_client.source.volume = volume
await interaction.followup.send(embed=discord.Embed(description=f"Volume set to {volume*100:.0f}%", color=discord.Color.green()))
self.logger.info(f'Volume set to {volume*100:.0f}%')
else:
await interaction.followup.send(embed=discord.Embed(description="Volume must be between 0 and 1.", color=discord.Color.red()))
self.logger.error('Invalid volume level attempted')
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="join", description="Join the voice channel")
async def join_command(interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
await self.join(interaction)
@tree.command(name="leave", description="Leave the voice channel")
async def leave_command(interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
await self.leave(interaction)
@tree.command(name="play", description="Play a song from YouTube")
async def play_command(interaction: discord.Interaction, search: str):
await interaction.response.defer() # Defer the interaction response
await self.play(interaction, search)
@tree.command(name="pause", description="Pause the current song")
async def pause_command(interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
await self.pause(interaction)
@tree.command(name="resume", description="Resume the paused song")
async def resume_command(interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
await self.resume(interaction)
@tree.command(name="stop", description="Stop the current song")
async def stop_command(interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
await self.stop(interaction)
@tree.command(name="volume", description="Set the volume (0 to 1)")
async def volume_command(interaction: discord.Interaction, volume: float):
await interaction.response.defer() # Defer the interaction response
await self.set_volume(interaction, volume)
def setup(bot):
music = Music(bot)
music.setup(bot.tree)

228
modules/social/destiny2.py Normal file
View File

@ -0,0 +1,228 @@
import discord
from discord import app_commands
import requests
from datetime import datetime, timedelta
import asyncio
import logging
from config import config
class Destiny2:
def __init__(self, bot):
self.bot = bot
self.api_key = config['BUNGIE_API_KEY']
self.logger = logging.getLogger('Destiny2')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='destiny2.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
self.logger.addHandler(handler)
async def fetch_item_details(self, item_hash):
headers = {
"X-API-Key": self.api_key
}
url = f"https://www.bungie.net/Platform/Destiny2/Manifest/DestinyInventoryItemDefinition/{item_hash}/"
response = requests.get(url, headers=headers)
if response.status_code == 200:
item_data = response.json()
return item_data['Response']
else:
self.logger.error(f'Error fetching item details for {item_hash}: {response.status_code} - {response.text}')
return None
async def fetch_vendors(self, interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
headers = {
"X-API-Key": self.api_key
}
url = "https://www.bungie.net/Platform/Destiny2/Vendors/"
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
self.logger.debug(f'Vendors data: {data}')
try:
vendors_info = data['Response']['vendors']
embeds = []
for vendor_hash, vendor_data in vendors_info.items():
vendor_name = vendor_data['vendorName']
vendor_icon = vendor_data['vendorIcon']
embed = discord.Embed(title=f"{vendor_name}'s Inventory", color=discord.Color.blue())
embed.set_thumbnail(url=f"https://www.bungie.net{vendor_icon}")
field_count = 0
for item in vendor_data['items']:
if field_count >= 25:
embeds.append(embed)
embed = discord.Embed(title=f"{vendor_name}'s Inventory (cont.)", color=discord.Color.blue())
embed.set_thumbnail(url=f"https://www.bungie.net{vendor_icon}")
field_count = 0
item_details = await self.fetch_item_details(item['itemHash'])
if item_details:
item_name = item_details['displayProperties']['name']
item_icon = item_details['displayProperties']['icon']
item_icon_url = f"https://www.bungie.net{item_icon}"
embed.add_field(
name=item_name,
value=f"Quantity: {item['quantity']}\n[Icon]({item_icon_url})",
inline=False
)
field_count += 1
embeds.append(embed)
for embed in embeds:
await interaction.followup.send(embed=embed)
except KeyError as e:
self.logger.error(f'Error processing vendors data: {e}')
await interaction.followup.send("Error processing vendor data.")
else:
self.logger.error(f'Error fetching vendors data: {response.status_code} - {response.text}')
await interaction.followup.send("Error fetching vendor data.")
async def fetch_xur(self, interaction: discord.Interaction):
await interaction.response.defer() # Defer the interaction response
headers = {
"X-API-Key": self.api_key
}
url = "https://www.bungie.net/Platform/Destiny2/Vendors/?components=402"
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
self.logger.debug(f'Xur data: {data}')
try:
xur_info = data['Response']['sales']['data']['2190858386']
embed = discord.Embed(title="Xur's Inventory", color=discord.Color.purple())
embed.set_thumbnail(url="https://www.bungie.net/img/misc/xur.png")
field_count = 0
for item in xur_info['saleItems'].values():
if field_count >= 25:
await interaction.followup.send(embed=embed)
embed = discord.Embed(title="Xur's Inventory (cont.)", color=discord.Color.purple())
embed.set_thumbnail(url="https://www.bungie.net/img/misc/xur.png")
field_count = 0
item_details = await self.fetch_item_details(item['itemHash'])
if item_details:
item_name = item_details['displayProperties']['name']
item_icon = item_details['displayProperties']['icon']
item_icon_url = f"https://www.bungie.net{item_icon}"
embed.add_field(
name=item_name,
value=f"Quantity: {item['quantity']}\n[Icon]({item_icon_url})",
inline=False
)
field_count += 1
await interaction.followup.send(embed=embed)
except KeyError as e:
self.logger.error(f'Error processing Xur data: {e}')
await interaction.followup.send("Error processing Xur data.")
else:
self.logger.error(f'Error fetching Xur data: {response.status_code} - {response.text}')
await interaction.followup.send("Error fetching Xur data.")
def setup(self, tree):
@app_commands.command(name='fetch_vendors', description='Fetch the current vendors')
async def fetch_vendors_command(interaction: discord.Interaction):
await self.fetch_vendors(interaction)
@app_commands.command(name='fetch_xur', description='Fetch Xur\'s items')
async def fetch_xur_command(interaction: discord.Interaction):
await self.fetch_xur(interaction)
tree.add_command(fetch_vendors_command)
tree.add_command(fetch_xur_command)
async def setup_hook(self):
await self.bot.wait_until_ready()
while not self.bot.is_closed():
if datetime.utcnow().weekday() == 1: # Check vendors every Tuesday
await self.check_vendors()
if datetime.utcnow().weekday() == 4: # Check Xur every Friday
await self.check_xur()
await asyncio.sleep(86400) # Check once every 24 hours
async def check_vendors(self):
headers = {
"X-API-Key": self.api_key
}
url = "https://www.bungie.net/Platform/Destiny2/Vendors/"
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
self.logger.debug(f'Vendors data: {data}')
try:
vendors_info = data['Response']['vendors']
embeds = []
for vendor_hash, vendor_data in vendors_info.items():
vendor_name = vendor_data['vendorName']
vendor_icon = vendor_data['vendorIcon']
embed = discord.Embed(title=f"{vendor_name}'s Inventory", color=discord.Color.blue())
embed.set_thumbnail(url=f"https://www.bungie.net{vendor_icon}")
field_count = 0
for item in vendor_data['items']:
if field_count >= 25:
embeds.append(embed)
embed = discord.Embed(title=f"{vendor_name}'s Inventory (cont.)", color=discord.Color.blue())
embed.set_thumbnail(url=f"https://www.bungie.net{vendor_icon}")
field_count = 0
item_details = await self.fetch_item_details(item['itemHash'])
if item_details:
item_name = item_details['displayProperties']['name']
item_icon = item_details['displayProperties']['icon']
item_icon_url = f"https://www.bungie.net{item_icon}"
embed.add_field(
name=item_name,
value=f"Quantity: {item['quantity']}\n[Icon]({item_icon_url})",
inline=False
)
field_count += 1
embeds.append(embed)
channel = self.bot.get_channel(config['CHANNEL_ID'])
for embed in embeds:
await channel.send(embed=embed)
except KeyError as e:
self.logger.error(f'Error processing vendors data: {e}')
else:
self.logger.error(f'Error fetching vendors data: {response.status_code} - {response.text}')
async def check_xur(self):
headers = {
"X-API-Key": self.api_key
}
url = "https://www.bungie.net/Platform/Destiny2/Vendors/?components=402"
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
self.logger.debug(f'Xur data: {data}')
try:
xur_info = data['Response']['sales']['data']['2190858386']
embed = discord.Embed(title="Xur's Inventory", color=discord.Color.purple())
embed.set_thumbnail(url="https://www.bungie.net/img/misc/xur.png")
field_count = 0
for item in xur_info['saleItems'].values():
if field_count >= 25:
await channel.send(embed=embed)
embed = discord.Embed(title="Xur's Inventory (cont.)", color=discord.Color.purple())
embed.set_thumbnail(url="https://www.bungie.net/img/misc/xur.png")
field_count = 0
item_details = await self.fetch_item_details(item['itemHash'])
if item_details:
item_name = item_details['displayProperties']['name']
item_icon = item_details['displayProperties']['icon']
item_icon_url = f"https://www.bungie.net{item_icon}"
embed.add_field(
name=item_name,
value=f"Quantity: {item['quantity']}\n[Icon]({item_icon_url})",
inline=False
)
field_count += 1
channel = self.bot.get_channel(config['CHANNEL_ID'])
await channel.send(embed=embed)
except KeyError as e:
self.logger.error(f'Error processing Xur data: {e}')
else:
self.logger.error(f'Error fetching Xur data: {response.status_code} - {response.text}')
def setup(bot):
destiny2 = Destiny2(bot)
bot.add_cog(destiny2)
bot.setup_hook = destiny2.setup_hook

47
modules/social/tiktok.py Normal file
View File

@ -0,0 +1,47 @@
import discord
from discord import app_commands
import logging
import re
class TikTok:
def __init__(self, bot):
self.bot = bot
self.logger = logging.getLogger('TikTok')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
async def on_message(self, message):
if message.author.bot:
return
tiktok_url = self.extract_tiktok_url(message.content)
if tiktok_url:
embed = self.create_tiktok_embed(tiktok_url)
await message.channel.send(embed=embed)
await message.delete()
await message.author.send("Your TikTok video has been properly embedded.")
def extract_tiktok_url(self, content):
tiktok_regex = re.compile(
r'(https?://(?:www\.)?tiktok\.com/[^ ]+|https?://vm\.tiktok\.com/[^ ]+)')
match = tiktok_regex.search(content)
return match.group(0) if match else None
def create_tiktok_embed(self, url):
embed = discord.Embed(title="TikTok Video", description="Here is the TikTok video:", url=url)
embed.set_author(name="TikTok", icon_url="https://upload.wikimedia.org/wikipedia/en/a/a9/TikTok_logo.svg")
embed.add_field(name="Link", value=f"[Watch on TikTok]({url})")
embed.set_footer(text="TikTok Embed Module")
return embed
def setup(self, tree: app_commands.CommandTree):
pass
def setup(bot):
tiktok = TikTok(bot)
bot.add_listener(tiktok.on_message, "on_message")
bot.tiktok_module = tiktok

188
modules/social/twitch.py Normal file
View File

@ -0,0 +1,188 @@
import discord
from discord import app_commands
import requests
import logging
import asyncio
import sqlite3
from config import config
class Twitch:
def __init__(self, bot):
self.bot = bot
self.client_id = config['TWITCH_CLIENT_ID']
self.client_secret = config['TWITCH_CLIENT_SECRET']
self.logger = logging.getLogger('Twitch')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.db_path = 'data/selena.db'
self.token = None
self.token_expiry = None
self.channel_alerts = {}
self.ensure_table_exists()
def ensure_table_exists(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS twitch_channels (
channel_name TEXT PRIMARY KEY,
alert_channel_id TEXT
);
""")
conn.commit()
conn.close()
self.logger.info('Twitch channels table ensured in database')
async def get_token(self):
url = "https://id.twitch.tv/oauth2/token"
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'client_credentials'
}
response = requests.post(url, params=params)
if response.status_code == 200:
data = response.json()
self.token = data['access_token']
self.token_expiry = asyncio.get_event_loop().time() + data['expires_in']
self.logger.info('Successfully obtained Twitch token')
else:
self.logger.error(f'Failed to obtain Twitch token: {response.status_code} - {response.text}')
async def ensure_token(self):
if not self.token or asyncio.get_event_loop().time() >= self.token_expiry:
await self.get_token()
async def fetch_channel_info(self, channel_name):
await self.ensure_token()
url = "https://api.twitch.tv/helix/streams"
headers = {
'Authorization': f'Bearer {self.token}',
'Client-Id': self.client_id
}
params = {
'user_login': channel_name
}
response = requests.get(url, headers=headers, params=params)
self.logger.debug(f'Response status code: {response.status_code}')
self.logger.debug(f'Response content: {response.content}')
if response.status_code == 200:
data = response.json()
if data['data']:
return data['data'][0] # Return the first stream (should only be one)
return None
async def fetch_user_info(self, user_id):
await self.ensure_token()
url = f"https://api.twitch.tv/helix/users?id={user_id}"
headers = {
'Authorization': f'Bearer {self.token}',
'Client-Id': self.client_id
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
if data['data']:
return data['data'][0]
return None
async def check_channels(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT channel_name, alert_channel_id FROM twitch_channels")
channels = cursor.fetchall()
for channel_name, alert_channel_id in channels:
channel_info = await self.fetch_channel_info(channel_name)
if channel_info and not self.channel_alerts.get(channel_name):
await self.send_alert(alert_channel_id, channel_info)
self.channel_alerts[channel_name] = True
elif not channel_info and self.channel_alerts.get(channel_name):
self.channel_alerts[channel_name] = False
conn.close()
async def send_alert(self, alert_channel_id, channel_info):
user_info = await self.fetch_user_info(channel_info['user_id'])
channel = self.bot.get_channel(alert_channel_id)
if channel:
title = channel_info['title']
url = f"https://www.twitch.tv/{channel_info['user_login']}"
thumbnail = channel_info['thumbnail_url'].replace('{width}', '320').replace('{height}', '180')
logo = user_info['profile_image_url'] if user_info else None
embed = discord.Embed(title=title, url=url, color=discord.Color.purple())
embed.set_thumbnail(url=logo if logo else thumbnail)
embed.add_field(name="Channel", value=channel_info['user_name'], inline=True)
embed.add_field(name="Game", value=channel_info['game_name'], inline=True)
await channel.send(embed=embed)
async def is_channel_followed(self, channel_name):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT 1 FROM twitch_channels WHERE channel_name = ?", (channel_name,))
result = cursor.fetchone()
conn.close()
return result is not None
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="add_twitch_channel", description="Add a Twitch channel to monitor")
async def add_twitch_channel_command(interaction: discord.Interaction, channel_name: str, alert_channel: discord.TextChannel):
if await self.is_channel_followed(channel_name):
await interaction.response.send_message(embed=discord.Embed(description=f"Twitch channel {channel_name} is already being monitored.", color=discord.Color.orange()))
return
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("INSERT INTO twitch_channels (channel_name, alert_channel_id) VALUES (?, ?)", (channel_name, alert_channel.id))
conn.commit()
conn.close()
await interaction.response.send_message(embed=discord.Embed(description=f"Added Twitch channel {channel_name} to monitor.", color=discord.Color.green()))
@tree.command(name="remove_twitch_channel", description="Remove a Twitch channel from monitoring")
async def remove_twitch_channel_command(interaction: discord.Interaction, channel_name: str):
if not await self.is_channel_followed(channel_name):
await interaction.response.send_message(embed=discord.Embed(description=f"Twitch channel {channel_name} is not being monitored.", color=discord.Color.red()))
return
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM twitch_channels WHERE channel_name = ?", (channel_name,))
conn.commit()
conn.close()
await interaction.response.send_message(embed=discord.Embed(description=f"Removed Twitch channel {channel_name} from monitoring.", color=discord.Color.green()))
@tree.command(name="check_twitch_channel", description="Check if a Twitch channel is live")
async def check_twitch_channel_command(interaction: discord.Interaction, channel_name: str):
channel_info = await self.fetch_channel_info(channel_name)
user_info = await self.fetch_user_info(channel_info['user_id']) if channel_info else None
if channel_info:
thumbnail = channel_info['thumbnail_url'].replace('{width}', '320').replace('{height}', '180')
logo = user_info['profile_image_url'] if user_info else None
embed = discord.Embed(title=f"{channel_info['user_name']} is live!", url=f"https://www.twitch.tv/{channel_info['user_login']}", color=discord.Color.purple())
embed.set_thumbnail(url=logo if logo else thumbnail)
embed.add_field(name="Title", value=channel_info['title'], inline=False)
embed.add_field(name="Game", value=channel_info['game_name'], inline=False)
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message(embed=discord.Embed(description=f"{channel_name} is not live.", color=discord.Color.red()))
if not tree.get_command("add_twitch_channel"):
tree.add_command(add_twitch_channel_command)
if not tree.get_command("remove_twitch_channel"):
tree.add_command(remove_twitch_channel_command)
if not tree.get_command("check_twitch_channel"):
tree.add_command(check_twitch_channel_command)
async def setup_hook(self):
await self.bot.wait_until_ready()
await self.get_token()
while not self.bot.is_closed():
await self.check_channels()
await asyncio.sleep(300) # Check every 5 minutes
def setup(bot):
twitch = Twitch(bot)
twitch.setup(bot.tree)
bot.loop.create_task(twitch.setup_hook())

188
modules/social/youtube.py Normal file
View File

@ -0,0 +1,188 @@
import discord
from discord import app_commands
import requests
import logging
import asyncio
import sqlite3
from config import config
class YouTube:
def __init__(self, bot):
self.bot = bot
self.api_key = config['YOUTUBE_API_KEY']
self.logger = logging.getLogger('YouTube')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.db_path = 'data/selena.db'
self.channel_alerts = {}
async def fetch_channel_id(self, identifier):
if identifier.startswith('@'):
url = f'https://www.googleapis.com/youtube/v3/search?part=snippet&q={identifier[1:]}&type=channel&key={self.api_key}'
else:
url = f'https://www.googleapis.com/youtube/v3/channels?part=id&id={identifier}&key={self.api_key}'
self.logger.debug(f'Fetching channel ID with URL: {url}')
response = requests.get(url)
self.logger.debug(f'Channel ID response status code: {response.status_code}')
self.logger.debug(f'Channel ID response content: {response.content}')
if response.status_code == 200:
data = response.json()
if data.get('items'):
if identifier.startswith('@'):
for item in data['items']:
if item['snippet']['title'].lower() == identifier[1:].lower():
self.logger.debug(f'Found channel ID: {item["id"]["channelId"]} for {identifier}')
return item['id']['channelId']
else:
return data['items'][0]['id']
else:
self.logger.error(f'Failed to fetch channel ID: {response.status_code} - {response.text}')
return None
async def fetch_latest_video(self, channel_id):
url = f'https://www.googleapis.com/youtube/v3/search?part=snippet&channelId={channel_id}&order=date&maxResults=1&type=video&key={self.api_key}'
self.logger.debug(f'Fetching latest video with URL: {url}')
response = requests.get(url)
self.logger.debug(f'Latest video response status code: {response.status_code}')
self.logger.debug(f'Latest video response content: {response.content}')
if response.status_code == 200:
data = response.json()
if data.get('items'):
return data['items'][0]
else:
self.logger.error(f'Failed to fetch latest video: {response.status_code} - {response.text}')
return None
async def fetch_channel_info(self, channel_id):
url = f'https://www.googleapis.com/youtube/v3/channels?part=snippet&id={channel_id}&key={self.api_key}'
self.logger.debug(f'Fetching channel info with URL: {url}')
response = requests.get(url)
self.logger.debug(f'Channel info response status code: {response.status_code}')
self.logger.debug(f'Channel info response content: {response.content}')
if response.status_code == 200:
data = response.json()
if data.get('items'):
return data['items'][0]
else:
self.logger.error(f'Failed to fetch channel info: {response.status_code} - {response.text}')
return None
async def check_channels(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT channel_id, last_video_id, alert_channel_id FROM youtube_channels")
channels = cursor.fetchall()
for channel_id, last_video_id, alert_channel_id in channels:
latest_video = await self.fetch_latest_video(channel_id)
if latest_video and latest_video['id']['videoId'] != last_video_id:
await self.send_alert(alert_channel_id, latest_video)
cursor.execute("UPDATE youtube_channels SET last_video_id = ? WHERE channel_id = ?", (latest_video['id']['videoId'], channel_id))
conn.commit()
await self.check_if_live(channel_id, alert_channel_id)
conn.close()
async def send_alert(self, alert_channel_id, video):
channel = self.bot.get_channel(alert_channel_id)
if channel:
title = video['snippet']['title']
description = video['snippet']['description']
url = f"https://www.youtube.com/watch?v={video['id']['videoId']}"
thumbnail = video['snippet']['thumbnails']['high']['url']
embed = discord.Embed(title=title, description=description, url=url, color=discord.Color.red())
embed.set_thumbnail(url=thumbnail)
await channel.send(embed=embed)
async def check_if_live(self, channel_id, alert_channel_id):
url = f'https://www.googleapis.com/youtube/v3/search?part=snippet&channelId={channel_id}&type=video&eventType=live&key={self.api_key}'
self.logger.debug(f'Checking live status with URL: {url}')
response = requests.get(url)
self.logger.debug(f'Live status response status code: {response.status_code}')
self.logger.debug(f'Live status response content: {response.content}')
if response.status_code == 200:
data = response.json()
if data.get('items'):
for live_video in data['items']:
if live_video['id']['kind'] == 'youtube#video':
await self.send_live_alert(alert_channel_id, live_video)
return True # Indicate that the channel is live
elif response.status_code == 400:
# Handle the specific error case with additional logging
self.logger.error(f'Error checking live status: {response.status_code} - {response.text}')
return False # Indicate that the channel is not live
async def send_live_alert(self, alert_channel_id, live_video):
channel = self.bot.get_channel(alert_channel_id)
if channel:
title = live_video['snippet']['title']
description = live_video['snippet']['description']
url = f"https://www.youtube.com/watch?v={live_video['id']['videoId']}"
thumbnail = live_video['snippet']['thumbnails']['high']['url']
embed = discord.Embed(title=title, description=description, url=url, color=discord.Color.red())
embed.set_thumbnail(url=thumbnail)
embed.add_field(name="Status", value="Live", inline=True)
await channel.send(embed=embed)
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="add_youtube_channel", description="Add a YouTube channel to monitor")
async def add_youtube_channel_command(interaction: discord.Interaction, identifier: str, alert_channel: discord.TextChannel):
channel_id = await self.fetch_channel_id(identifier)
if channel_id:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("INSERT INTO youtube_channels (channel_id, last_video_id, alert_channel_id) VALUES (?, ?, ?)", (channel_id, '', alert_channel.id))
conn.commit()
conn.close()
await interaction.response.send_message(embed=discord.Embed(description=f"Added YouTube channel {identifier} to monitor.", color=discord.Color.green()))
else:
await interaction.response.send_message(embed=discord.Embed(description=f"Failed to find YouTube channel {identifier}.", color=discord.Color.red()))
@tree.command(name="remove_youtube_channel", description="Remove a YouTube channel from monitoring")
async def remove_youtube_channel_command(interaction: discord.Interaction, channel_id: str):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM youtube_channels WHERE channel_id = ?", (channel_id,))
conn.commit()
conn.close()
await interaction.response.send_message(embed=discord.Embed(description=f"Removed YouTube channel {channel_id} from monitoring.", color=discord.Color.green()))
@tree.command(name="check_youtube_channel", description="Check if a YouTube channel is live")
async def check_youtube_channel_command(interaction: discord.Interaction, identifier: str):
channel_id = await self.fetch_channel_id(identifier)
if not channel_id:
await interaction.response.send_message(embed=discord.Embed(description=f"Failed to find YouTube channel {identifier}.", color=discord.Color.red()))
return
is_live = await self.check_if_live(channel_id, interaction.channel_id)
if is_live:
await interaction.response.send_message(embed=discord.Embed(description=f"{identifier} is live!", color=discord.Color.green()))
else:
channel_info = await self.fetch_channel_info(channel_id)
if channel_info:
embed = discord.Embed(description=f"{channel_info['snippet']['title']} is not live.", color=discord.Color.red())
embed.set_thumbnail(url=channel_info['snippet']['thumbnails']['high']['url'])
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message(embed=discord.Embed(description=f"{identifier} is not live.", color=discord.Color.red()))
if not tree.get_command("add_youtube_channel"):
tree.add_command(add_youtube_channel_command)
if not tree.get_command("remove_youtube_channel"):
tree.add_command(remove_youtube_channel_command)
if not tree.get_command("check_youtube_channel"):
tree.add_command(check_youtube_channel_command)
async def setup_hook(self):
await self.bot.wait_until_ready()
while not self.bot.is_closed():
await self.check_channels()
await asyncio.sleep(3600) # Check every hour
def setup(bot):
youtube = YouTube(bot)
youtube.setup(bot.tree)
bot.loop.create_task(youtube.setup_hook())

119
modules/user/birthday.py Normal file
View File

@ -0,0 +1,119 @@
import discord
from discord.ext import tasks
import sqlite3
import logging
import datetime
class Birthday:
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
self.logger = logging.getLogger('Birthday')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.ensure_table_exists()
def ensure_table_exists(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS birthdays (
user_id TEXT,
guild_id TEXT,
birthday TEXT,
PRIMARY KEY (user_id, guild_id)
);
""")
conn.commit()
conn.close()
self.logger.info('Birthday table ensured in database')
async def set_birthday(self, user_id, guild_id, birthday):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO birthdays (user_id, guild_id, birthday)
VALUES (?, ?, ?)
ON CONFLICT(user_id, guild_id)
DO UPDATE SET birthday = ?
""", (user_id, guild_id, birthday, birthday))
conn.commit()
conn.close()
self.logger.info(f'Set birthday for user {user_id} in guild {guild_id}')
async def get_birthday(self, user_id, guild_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT birthday FROM birthdays WHERE user_id = ? AND guild_id = ?", (user_id, guild_id))
row = cursor.fetchone()
conn.close()
return row[0] if row else None
@tasks.loop(hours=24)
async def check_birthdays(self):
today = datetime.datetime.today().strftime('%Y-%m-%d')
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT user_id, guild_id FROM birthdays WHERE birthday = ?", (today,))
rows = cursor.fetchall()
conn.close()
for user_id, guild_id in rows:
guild = self.bot.get_guild(int(guild_id))
if guild:
user = guild.get_member(int(user_id))
if user:
channel = guild.system_channel or next((ch for ch in guild.text_channels if ch.permissions_for(guild.me).send_messages), None)
if channel:
await channel.send(f"Happy Birthday, {user.mention}! 🎉🎂")
async def setup_hook(self):
self.check_birthdays.start()
self.logger.info('Started birthday check loop')
def setup(self, tree: discord.app_commands.CommandTree):
@tree.command(name="set_birthday", description="Set your birthday")
async def set_birthday_command(interaction: discord.Interaction):
await interaction.response.send_modal(BirthdayModal(self))
@tree.command(name="check_birthday", description="Check your birthday")
async def check_birthday_command(interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
birthday = await self.get_birthday(user_id, guild_id)
if birthday:
await interaction.response.send_message(f"Your birthday is set to {birthday}.", ephemeral=True)
else:
await interaction.response.send_message("You have not set your birthday yet. Use /set_birthday to set it.", ephemeral=True)
if not tree.get_command("set_birthday"):
tree.add_command(set_birthday_command)
if not tree.get_command("check_birthday"):
tree.add_command(check_birthday_command)
class BirthdayModal(discord.ui.Modal, title="Set Birthday"):
birthday = discord.ui.TextInput(label="Your Birthday", placeholder="Enter your birthday (YYYY-MM-DD)...", required=True)
def __init__(self, birthday_module):
super().__init__()
self.birthday_module = birthday_module
async def on_submit(self, interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
try:
birthday = self.birthday.value
await self.birthday_module.set_birthday(user_id, guild_id, birthday)
await interaction.response.send_message("Your birthday has been set.", ephemeral=True)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
def setup(bot):
birthday_module = Birthday(bot)
bot.add_cog(birthday_module)
bot.birthday_module = birthday_module

59
modules/user/currency.py Normal file
View File

@ -0,0 +1,59 @@
# currency.py
import discord
from discord import app_commands
import sqlite3
import random
class Currency:
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
async def earn_kibble(self, interaction: discord.Interaction):
guild_id = str(interaction.guild_id)
user_id = str(interaction.user.id)
earned = random.randint(1, 10)
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO guild_currency (guild_id, user_id, balance)
VALUES (?, ?, ?)
ON CONFLICT(guild_id, user_id)
DO UPDATE SET balance = balance + ?
""", (guild_id, user_id, earned, earned))
conn.commit()
cursor.execute("SELECT balance FROM guild_currency WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
balance = cursor.fetchone()[0]
conn.close()
await interaction.response.send_message(f"You earned {earned} Kibble! You now have {balance} Kibble.", ephemeral=False)
async def balance(self, interaction: discord.Interaction):
guild_id = str(interaction.guild_id)
user_id = str(interaction.user.id)
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT balance FROM guild_currency WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
result = cursor.fetchone()
conn.close()
balance = result[0] if result else 0
await interaction.response.send_message(f"You have {balance} Kibble.", ephemeral=False)
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="earn_kibble", description="Earn Kibble currency")
async def earn_kibble_command(interaction: discord.Interaction):
await self.earn_kibble(interaction)
@tree.command(name="balance", description="Check your Kibble balance")
async def balance_command(interaction: discord.Interaction):
await self.balance(interaction)
if not tree.get_command("earn_kibble"):
tree.add_command(earn_kibble_command)
if not tree.get_command("balance"):
tree.add_command(balance_command)

209
modules/user/profiles.py Normal file
View File

@ -0,0 +1,209 @@
import discord
import sqlite3
import logging
from datetime import datetime
class Profiles:
ALLOWED_PRONOUNS = ["he/him", "she/her", "they/them", "ask me"]
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
self.logger = logging.getLogger('Profiles')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.ensure_tables_exist()
def ensure_tables_exist(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS profiles (
user_id TEXT,
guild_id TEXT,
pronouns TEXT,
birthday TEXT,
age INTEGER,
xp INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
is_global BOOLEAN DEFAULT 0,
PRIMARY KEY (user_id, guild_id)
);
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS game_stats (
user_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
game TEXT NOT NULL,
wins INTEGER DEFAULT 0,
losses INTEGER DEFAULT 0,
PRIMARY KEY (user_id, guild_id, game)
);
""")
# Ensure the guild_id column exists in the game_stats table
cursor.execute("PRAGMA table_info(game_stats);")
columns = [info[1] for info in cursor.fetchall()]
if 'guild_id' not in columns:
cursor.execute("ALTER TABLE game_stats ADD COLUMN guild_id TEXT;")
conn.commit()
conn.close()
self.logger.info('Profile and game stats tables ensured in database')
async def record_win(self, user_id, guild_id, game):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO game_stats (user_id, guild_id, game, wins)
VALUES (?, ?, ?, 1)
ON CONFLICT(user_id, guild_id, game)
DO UPDATE SET wins = wins + 1
""", (user_id, guild_id, game))
conn.commit()
conn.close()
self.logger.info(f'Recorded win for user {user_id} in game {game} in guild {guild_id}')
async def record_loss(self, user_id, guild_id, game):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO game_stats (user_id, guild_id, game, losses)
VALUES (?, ?, ?, 1)
ON CONFLICT(user_id, guild_id, game)
DO UPDATE SET losses = losses + 1
""", (user_id, guild_id, game))
conn.commit()
conn.close()
self.logger.info(f'Recorded loss for user {user_id} in game {game} in guild {guild_id}')
async def update_profile(self, user_id, guild_id, pronouns=None, birthday=None, is_global=False):
if pronouns and pronouns not in self.ALLOWED_PRONOUNS:
raise ValueError("Invalid pronouns. Allowed values are: " + ", ".join(self.ALLOWED_PRONOUNS))
age = self.calculate_age(birthday) if birthday else None
if age is not None and age < 13:
raise ValueError("You must be at least 13 years old to use Selena.")
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO profiles (user_id, guild_id, pronouns, birthday, age, is_global)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, guild_id)
DO UPDATE SET pronouns = COALESCE(?, pronouns), birthday = COALESCE(?, birthday), age = COALESCE(?, age), is_global = ?
""", (user_id, guild_id, pronouns, birthday, age, is_global, pronouns, birthday, age, is_global))
conn.commit()
conn.close()
self.logger.info(f'Updated profile for user {user_id} in guild {guild_id}')
async def get_profile(self, user_id, guild_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
SELECT pronouns, birthday, age, xp, level, is_global FROM profiles
WHERE user_id = ? AND (guild_id = ? OR is_global = 1)
""", (user_id, guild_id))
row = cursor.fetchone()
conn.close()
return row
async def get_game_stats(self, user_id, guild_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT game, wins, losses FROM game_stats WHERE user_id = ? AND guild_id = ?", (user_id, guild_id))
rows = cursor.fetchall()
conn.close()
return rows
def calculate_age(self, birthday):
if not birthday:
return None
today = datetime.today()
birthdate = datetime.strptime(birthday, "%Y-%m-%d")
age = today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day))
return age
def setup(self, tree: discord.app_commands.CommandTree):
@tree.command(name="set_profile", description="Set your profile information")
async def set_profile_command(interaction: discord.Interaction):
await interaction.response.send_message("Please select your pronouns and set your birthday:", view=ProfileView(self))
@tree.command(name="profile", description="View your profile")
async def profile_command(interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
profile = await self.get_profile(user_id, guild_id)
if profile:
pronouns, birthday, age, xp, level, is_global = profile
game_stats = await self.get_game_stats(user_id, guild_id)
games_info = "\n".join([f"{game}: {wins}W/{losses}L" for game, wins, losses in game_stats])
embed = discord.Embed(title=f"{interaction.user.display_name}'s Profile")
embed.add_field(name="Pronouns", value=pronouns or "Not set", inline=True)
embed.add_field(name="Birthday", value=birthday or "Not set", inline=True)
embed.add_field(name="Age", value=age or "Not set", inline=True)
embed.add_field(name="XP", value=xp, inline=True)
embed.add_field(name="Level", value=level, inline=True)
embed.add_field(name="Global Profile", value="Yes" if is_global else "No", inline=True)
embed.add_field(name="Games", value=games_info or "No games played", inline=False)
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message("Profile not found. Please set your profile using /set_profile.", ephemeral=True)
if not tree.get_command("set_profile"):
tree.add_command(set_profile_command)
if not tree.get_command("profile"):
tree.add_command(profile_command)
class ProfileView(discord.ui.View):
def __init__(self, profiles):
super().__init__()
self.profiles = profiles
self.add_item(PronounSelect(profiles))
@discord.ui.button(label="Set Birthday", style=discord.ButtonStyle.primary)
async def set_birthday_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_modal(BirthdayModal(self.profiles))
class PronounSelect(discord.ui.Select):
def __init__(self, profiles):
self.profiles = profiles
options = [discord.SelectOption(label=pronoun, value=pronoun) for pronoun in Profiles.ALLOWED_PRONOUNS]
super().__init__(placeholder="Select your pronouns...", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
try:
await self.profiles.update_profile(user_id, guild_id, pronouns=self.values[0])
await interaction.response.send_message(f"Your pronouns have been set to {self.values[0]}.", ephemeral=True)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
class BirthdayModal(discord.ui.Modal, title="Set Birthday"):
birthday = discord.ui.TextInput(label="Your Birthday", placeholder="Enter your birthday (YYYY-MM-DD)...", required=True)
def __init__(self, profiles):
super().__init__()
self.profiles = profiles
async def on_submit(self, interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
try:
birthday = self.birthday.value
await self.profiles.update_profile(user_id, guild_id, birthday=birthday)
await interaction.response.send_message("Your birthday has been set.", ephemeral=True)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
def setup(bot):
profiles = Profiles(bot)
profiles.setup(bot.tree)
bot.profiles = profiles

118
modules/user/xp.py Normal file
View File

@ -0,0 +1,118 @@
import discord
import sqlite3
import random
import logging
import asyncio
class XP:
def __init__(self, bot):
self.bot = bot
self.db_path = 'data/selena.db'
self.logger = logging.getLogger('XP')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.cooldown = 10 # Cooldown time in seconds
self.xp_range = (5, 15) # Range of XP that can be earned per message
self.user_cooldowns = {}
self.ensure_table_exists()
def ensure_table_exists(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS guild_xp (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
xp INTEGER NOT NULL,
level INTEGER NOT NULL,
PRIMARY KEY (guild_id, user_id)
);
""")
conn.commit()
conn.close()
self.logger.info('guild_xp table ensured in database')
async def add_xp(self, guild_id, user_id, xp):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO guild_xp (guild_id, user_id, xp, level)
VALUES (?, ?, ?, ?)
ON CONFLICT(guild_id, user_id)
DO UPDATE SET xp = xp + excluded.xp
""", (guild_id, user_id, xp, 0))
cursor.execute("SELECT xp, level FROM guild_xp WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
row = cursor.fetchone()
conn.commit()
conn.close()
if row:
total_xp = row[0]
level = row[1]
new_level = self.calculate_level(total_xp)
if new_level > level:
await self.update_level(guild_id, user_id, new_level)
async def update_level(self, guild_id, user_id, level):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("UPDATE guild_xp SET level = ? WHERE guild_id = ? AND user_id = ?", (level, guild_id, user_id))
conn.commit()
conn.close()
self.logger.info(f'User {user_id} in guild {guild_id} leveled up to {level}')
def calculate_level(self, xp):
return int(xp ** 0.5) # Simple leveling formula, can be adjusted
async def get_xp(self, guild_id, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT xp, level FROM guild_xp WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
row = cursor.fetchone()
conn.close()
return row if row else (0, 0)
async def handle_message(self, message):
self.logger.debug(f'Received message from user {message.author.id} in guild {message.guild.id}')
if message.author.bot:
self.logger.debug('Message author is a bot, ignoring.')
return
guild_id = str(message.guild.id)
user_id = str(message.author.id)
if user_id in self.user_cooldowns and self.user_cooldowns[user_id] > asyncio.get_event_loop().time():
self.logger.debug(f'User {user_id} is on cooldown')
return
xp = random.randint(*self.xp_range)
await self.add_xp(guild_id, user_id, xp)
self.user_cooldowns[user_id] = asyncio.get_event_loop().time() + self.cooldown
self.logger.info(f'Added {xp} XP to user {user_id} in guild {guild_id}')
def setup(self, tree: discord.app_commands.CommandTree):
@tree.command(name="check_xp", description="Check your XP and level")
async def check_xp_command(interaction: discord.Interaction):
user_id = str(interaction.user.id)
guild_id = str(interaction.guild.id)
xp, level = await self.get_xp(guild_id, user_id)
await interaction.response.send_message(embed=discord.Embed(description=f"You have {xp} XP and are at level {level}.", color=discord.Color.green()))
if not tree.get_command("check_xp"):
tree.add_command(check_xp_command)
async def setup_hook(self):
self.bot.event(self.handle_message)
self.logger.info('XP module setup complete and listener added')
def setup(bot):
xp = XP(bot)
xp.setup(bot.tree)
bot.loop.create_task(xp.setup_hook())
bot.xp_module = xp

65
requirements.txt Normal file
View File

@ -0,0 +1,65 @@
aiohttp==3.9.5
aiosignal==1.3.1
async-timeout==4.0.3
attrs==23.2.0
autoflake==2.3.1
black==24.4.2
Brotli==1.1.0
cachetools==5.3.3
certifi==2024.6.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
com2ann==0.3.0
cryptography==42.0.8
discord.py==2.3.2
ffmpeg-python==0.2.0
frozenlist==1.4.1
future==1.0.0
google-api-core==2.19.0
google-api-python-client==2.134.0
google-auth==2.30.0
google-auth-httplib2==0.2.0
googleapis-common-protos==1.63.1
httplib2==0.22.0
idna==3.7
iso8601==2.1.0
libcst==1.4.0
multidict==6.0.5
mutagen==1.47.0
mypy-extensions==1.0.0
packaging==24.1
pathspec==0.12.1
platformdirs==4.2.2
PlexAPI==4.15.13
proto-plus==1.24.0
protobuf==4.25.3
pyasn1==0.6.0
pyasn1_modules==0.4.0
pycparser==2.22
pycryptodomex==3.20.0
pyflakes==3.2.0
PyNaCl==1.5.0
pyparsing==3.1.2
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
pyupgrade==3.16.0
PyYAML==6.0.1
redis==5.0.6
requests==2.32.3
rsa==4.9
ruff==0.4.10
shed==2024.3.1
six==1.16.0
spotipy==2.24.0
tokenize-rt==5.2.0
tomli==2.0.1
twitchAPI==4.2.0
twitchio==2.9.1
typing_extensions==4.12.2
uritemplate==4.1.1
urllib3==2.2.2
websockets==12.0
yarl==1.9.4
yt-dlp==2024.5.27

15
sample.env Normal file
View File

@ -0,0 +1,15 @@
DISCORD_TOKEN = "YOUR DISCORD TOKEN HERE"
DISCORD_GUILD_ID = "YOUR DISCORD GUILD ID HERE"
DISCORD_CHANNEL_ID = "YOUR DISCORD CHANNEL ID HERE"
BUNGIE_API_KEY = "YOUR BUNGIE API KEY HERE"
OAUTH_URL = 'https://www.bungie.net/en/OAuth/Authorize'
OAUTH_CLIENT_ID = "YOUR BUNGIE CLIENT ID HERE"
SPOTIPY_CLIENT_ID = "YOUR SPOTIFY CLIENT ID HERE"
SPOTIPY_CLIENT_SECRET = "YOUR SPOTIFY CLIENT SECRET HERE"
SPOTIPY_REDIRECT_URI = 'https://127.0.0.1:9090' # Change this to match your redirect URI
PLEX_URL= "YOUR PLEX SERVER HERE"
PLEX_TOKEN = "YOUR PLEX TOKEN HERE"
TWITCH_CLIENT_ID = "YOUR TWITCH CLIENT ID HERE"
TWITCH_CLIENT_SECRET = "YOUR TWITCH CLIENT SECRET HERE"
TWITCH_CHANNEL = "YOUR TWITCH CHANNEL HERE"
YOUTUBE_API_KEY = "YOUR YOUTUBE API KEY HERE"