Compare commits
No commits in common. "dev-rework" and "main" have entirely different histories.
dev-rework
...
main
15
.github/workflows/discord_sync.yml
vendored
15
.github/workflows/discord_sync.yml
vendored
@ -1,15 +0,0 @@
|
||||
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
4
.gitignore
vendored
@ -160,7 +160,3 @@ 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
15
.vscode/launch.json
vendored
@ -1,15 +0,0 @@
|
||||
{
|
||||
// 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
32
config.py
@ -1,32 +0,0 @@
|
||||
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}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
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 +0,0 @@
|
||||
noJII7Djv1uQXw1hxEoHxe0LJ2P21uQJ8WryKMPPNb8=
|
133
main.py
133
main.py
@ -1,133 +0,0 @@
|
||||
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)
|
@ -1,51 +0,0 @@
|
||||
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)
|
@ -1,73 +0,0 @@
|
||||
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)
|
@ -1,42 +0,0 @@
|
||||
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)
|
@ -1,253 +0,0 @@
|
||||
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
|
@ -1,192 +0,0 @@
|
||||
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
|
@ -1,187 +0,0 @@
|
||||
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)
|
@ -1,228 +0,0 @@
|
||||
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
|
@ -1,47 +0,0 @@
|
||||
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
|
@ -1,188 +0,0 @@
|
||||
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())
|
@ -1,188 +0,0 @@
|
||||
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())
|
@ -1,119 +0,0 @@
|
||||
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
|
@ -1,59 +0,0 @@
|
||||
# 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)
|
@ -1,209 +0,0 @@
|
||||
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
|
@ -1,118 +0,0 @@
|
||||
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
|
@ -1,65 +0,0 @@
|
||||
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
15
sample.env
@ -1,15 +0,0 @@
|
||||
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"
|
Loading…
x
Reference in New Issue
Block a user