REF: Added in Music code
REF: Added in Terms of Services Code REF: Added in Data Privacy Code REF: Added the code needed for Selena to run DOC: launch.json is only for development
This commit is contained in:
parent
9f02c1da5e
commit
9123144fe2
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Selena",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "E:\\Development\\AI Development\\Selena\\main.py",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
22
config.py
Normal file
22
config.py
Normal file
@ -0,0 +1,22 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
config = {
|
||||
'DISCORD_TOKEN': os.getenv('DISCORD_TOKEN'),
|
||||
'GUILD_ID_1': int(os.getenv('DISCORD_GUILD_ID')),
|
||||
'GUILD_ID_2': int(os.getenv('DISCORD_GUILD_ID_2')),
|
||||
'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': {
|
||||
'music': {'enabled': True},
|
||||
'terms_privacy': {'enabled': True},
|
||||
'data_privacy': {'enabled': True}
|
||||
}
|
||||
}
|
56
main.py
Normal file
56
main.py
Normal file
@ -0,0 +1,56 @@
|
||||
import discord
|
||||
from config import config
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
TOKEN = config['DISCORD_TOKEN']
|
||||
GUILD_ID_1 = config['GUILD_ID_1']
|
||||
GUILD_ID_2 = config['GUILD_ID_2']
|
||||
|
||||
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...")
|
||||
await self.tree.sync(guild=discord.Object(id=GUILD_ID_1))
|
||||
logging.info(f"Modules setup and commands synchronized to {GUILD_ID_1}")
|
||||
await self.tree.sync(guild=discord.Object(id=GUILD_ID_2))
|
||||
logging.info(f"Modules setup and commands synchronized to {GUILD_ID_2}")
|
||||
# Call setup_hook for xp_module here
|
||||
if self.xp_module:
|
||||
await self.xp_module.setup_hook()
|
||||
|
||||
def load_modules(self):
|
||||
if config['modules']['music']['enabled']:
|
||||
from modules.music.music import setup as music_setup
|
||||
music_setup(self)
|
||||
logging.info("Music module loaded")
|
||||
|
||||
if config['modules']['terms_privacy']['enabled']:
|
||||
from modules.terms_privacy.terms_privacy import setup as terms_privacy_setup
|
||||
terms_privacy_setup(self)
|
||||
logging.info("Terms and Privacy module loaded")
|
||||
|
||||
if config['modules']['data_privacy']['enabled']:
|
||||
from modules.data_privacy.data_privacy import setup as data_privacy_setup
|
||||
data_privacy_setup(self)
|
||||
logging.info("Data Privacy module loaded")
|
||||
|
||||
|
||||
bot = Selena()
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
logging.info(f'{bot.user.name} has connected to Discord!')
|
||||
|
||||
bot.run(TOKEN)
|
51
modules/admin/data_privacy.py
Normal file
51
modules/admin/data_privacy.py
Normal file
@ -0,0 +1,51 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
import sqlite3
|
||||
|
||||
|
||||
class DataPrivacy:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.db_path = 'data/selena.db'
|
||||
|
||||
async def fetch_user_data(self, user_id):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM user_data WHERE user_id = ?", (user_id,))
|
||||
data = cursor.fetchall()
|
||||
conn.close()
|
||||
return data
|
||||
|
||||
async def delete_user_data(self, user_id):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM user_data WHERE user_id = ?", (user_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def setup(self, tree: app_commands.CommandTree):
|
||||
@tree.command(name="request_data", description="Request your stored data")
|
||||
async def request_data_command(interaction: discord.Interaction):
|
||||
user_id = interaction.user.id
|
||||
data = await self.fetch_user_data(user_id)
|
||||
if data:
|
||||
await interaction.response.send_message(f"Your data: {data}", ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message("No data found for your user.", ephemeral=True)
|
||||
|
||||
@tree.command(name="delete_data", description="Request deletion of your stored data")
|
||||
async def delete_data_command(interaction: discord.Interaction):
|
||||
user_id = interaction.user.id
|
||||
await self.delete_user_data(user_id)
|
||||
await interaction.response.send_message("Your data has been deleted.", ephemeral=True)
|
||||
|
||||
if not tree.get_command("request_data"):
|
||||
tree.add_command(request_data_command)
|
||||
|
||||
if not tree.get_command("delete_data"):
|
||||
tree.add_command(delete_data_command)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
data_privacy = DataPrivacy(bot)
|
||||
data_privacy.setup(bot.tree)
|
73
modules/admin/terms_privacy.py
Normal file
73
modules/admin/terms_privacy.py
Normal file
@ -0,0 +1,73 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
import sqlite3
|
||||
|
||||
|
||||
class TermsPrivacy:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.db_path = 'data/selena.db'
|
||||
self.privacy_policy_url = "https://advtech92.github.io/selena-website/privacy_policy.html"
|
||||
self.terms_of_service_url = "https://advtech92.github.io/selena-website/terms_of_service.html"
|
||||
|
||||
async def user_opt_out(self, user_id):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("INSERT INTO opt_out_users (user_id) VALUES (?)", (user_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
async def user_opt_in(self, user_id):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM opt_out_users WHERE user_id = ?", (user_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
async def is_user_opted_out(self, user_id):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT 1 FROM opt_out_users WHERE user_id = ?", (user_id,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result is not None
|
||||
|
||||
def setup(self, tree: app_commands.CommandTree):
|
||||
@tree.command(name="privacy_policy", description="Show the privacy policy")
|
||||
async def privacy_policy_command(interaction: discord.Interaction):
|
||||
embed = discord.Embed(title="Privacy Policy", url=self.privacy_policy_url, description="Read our privacy policy.", color=discord.Color.blue())
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
|
||||
@tree.command(name="terms_of_service", description="Show the terms of service")
|
||||
async def terms_of_service_command(interaction: discord.Interaction):
|
||||
embed = discord.Embed(title="Terms of Service", url=self.terms_of_service_url, description="Read our terms of service.", color=discord.Color.blue())
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
|
||||
@tree.command(name="opt_out", description="Opt out of using the bot")
|
||||
async def opt_out_command(interaction: discord.Interaction):
|
||||
user_id = interaction.user.id
|
||||
await self.user_opt_out(user_id)
|
||||
await interaction.response.send_message("You have opted out of using the bot.", ephemeral=True)
|
||||
|
||||
@tree.command(name="opt_in", description="Opt back in to using the bot")
|
||||
async def opt_in_command(interaction: discord.Interaction):
|
||||
user_id = interaction.user.id
|
||||
await self.user_opt_in(user_id)
|
||||
await interaction.response.send_message("You have opted back in to using the bot.", ephemeral=True)
|
||||
|
||||
if not tree.get_command("privacy_policy"):
|
||||
tree.add_command(privacy_policy_command)
|
||||
|
||||
if not tree.get_command("terms_of_service"):
|
||||
tree.add_command(terms_of_service_command)
|
||||
|
||||
if not tree.get_command("opt_out"):
|
||||
tree.add_command(opt_out_command)
|
||||
|
||||
if not tree.get_command("opt_in"):
|
||||
tree.add_command(opt_in_command)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
terms_privacy = TermsPrivacy(bot)
|
||||
terms_privacy.setup(bot.tree)
|
139
modules/music/music.py
Normal file
139
modules/music/music.py
Normal file
@ -0,0 +1,139 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
from yt_dlp import YoutubeDL
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
YTDL_OPTIONS = {
|
||||
'format': 'bestaudio',
|
||||
'noplaylist': 'True',
|
||||
}
|
||||
|
||||
FFMPEG_OPTIONS = {
|
||||
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
||||
'options': '-vn'
|
||||
}
|
||||
|
||||
ytdl = YoutubeDL(YTDL_OPTIONS)
|
||||
|
||||
|
||||
class Music(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.queue = []
|
||||
self.is_playing = False
|
||||
self.volume = 0.3
|
||||
|
||||
async def join(self, interaction: discord.Interaction):
|
||||
channel = interaction.user.voice.channel
|
||||
if interaction.guild.voice_client is None:
|
||||
await channel.connect(self_deaf=True) # Join the channel deafened
|
||||
await interaction.followup.send("Joined the voice channel.")
|
||||
else:
|
||||
await interaction.followup.send("Already in a voice channel.")
|
||||
|
||||
async def leave(self, interaction: discord.Interaction):
|
||||
if interaction.guild.voice_client:
|
||||
await interaction.guild.voice_client.disconnect()
|
||||
await interaction.followup.send("Left the voice channel.")
|
||||
else:
|
||||
await interaction.followup.send("Not connected to a voice channel.")
|
||||
|
||||
async def play(self, interaction: discord.Interaction, search: str):
|
||||
if not interaction.guild.voice_client:
|
||||
await self.join(interaction)
|
||||
|
||||
info = ytdl.extract_info(f"ytsearch:{search}", download=False)['entries'][0]
|
||||
url = info['url']
|
||||
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
self.queue.append((url, info['title']))
|
||||
await interaction.followup.send(f'Queued: {info["title"]}')
|
||||
else:
|
||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS), volume=self.volume)
|
||||
interaction.guild.voice_client.play(source, after=lambda e: self.bot.loop.create_task(self.play_next(interaction)))
|
||||
self.is_playing = True
|
||||
await interaction.followup.send(f'Playing: {info["title"]}')
|
||||
|
||||
async def play_next(self, interaction: discord.Interaction):
|
||||
if self.queue:
|
||||
url, title = self.queue.pop(0)
|
||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS), volume=self.volume)
|
||||
interaction.guild.voice_client.play(source, after=lambda e: self.bot.loop.create_task(self.play_next(interaction)))
|
||||
await interaction.followup.send(f'Playing next: {title}')
|
||||
else:
|
||||
self.is_playing = False
|
||||
|
||||
async def pause(self, interaction: discord.Interaction):
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
interaction.guild.voice_client.pause()
|
||||
await interaction.followup.send("Paused the song.")
|
||||
else:
|
||||
await interaction.followup.send("No song is currently playing.")
|
||||
|
||||
async def resume(self, interaction: discord.Interaction):
|
||||
if interaction.guild.voice_client.is_paused():
|
||||
interaction.guild.voice_client.resume()
|
||||
await interaction.followup.send("Resumed the song.")
|
||||
else:
|
||||
await interaction.followup.send("The song is not paused.")
|
||||
|
||||
async def stop(self, interaction: discord.Interaction):
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
interaction.guild.voice_client.stop()
|
||||
self.queue = []
|
||||
await interaction.followup.send("Stopped the song.")
|
||||
else:
|
||||
await interaction.followup.send("No song is currently playing.")
|
||||
|
||||
async def set_volume(self, interaction: discord.Interaction, volume: float):
|
||||
self.volume = volume
|
||||
if interaction.guild.voice_client and interaction.guild.voice_client.source:
|
||||
interaction.guild.voice_client.source.volume = self.volume
|
||||
await interaction.followup.send(f"Volume set to {volume * 100}%.")
|
||||
else:
|
||||
await interaction.followup.send("No audio source found.")
|
||||
|
||||
def setup(self, tree: discord.app_commands.CommandTree):
|
||||
@tree.command(name="join", description="Join the voice channel")
|
||||
async def join_command(interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
await self.join(interaction)
|
||||
|
||||
@tree.command(name="leave", description="Leave the voice channel")
|
||||
async def leave_command(interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
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()
|
||||
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()
|
||||
await self.pause(interaction)
|
||||
|
||||
@tree.command(name="resume", description="Resume the paused song")
|
||||
async def resume_command(interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
await self.resume(interaction)
|
||||
|
||||
@tree.command(name="stop", description="Stop the current song")
|
||||
async def stop_command(interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
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()
|
||||
await self.set_volume(interaction, volume)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
music = Music(bot)
|
||||
music.setup(bot.tree)
|
Loading…
x
Reference in New Issue
Block a user