317 lines
14 KiB
Python
317 lines
14 KiB
Python
import discord
|
|
from discord.ext import commands, tasks
|
|
from discord import app_commands
|
|
import os
|
|
from dotenv import load_dotenv
|
|
import sqlite3
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from discord import ui, Interaction
|
|
import random
|
|
import sys
|
|
import requests
|
|
import time
|
|
|
|
## Defines ##
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
# Load bot token and welcome channel id from environment variables
|
|
TOKEN = os.getenv('BOT_TOKEN')
|
|
|
|
# Setup logging
|
|
logging.basicConfig(filename='bot.log', level=logging.INFO)
|
|
|
|
|
|
## Function and Class Definitions ##
|
|
|
|
class PersistentViewBot(commands.Bot):
|
|
def __init__(self):
|
|
# Define intents
|
|
intents = discord.Intents.none()
|
|
# intents.auto_moderation = True
|
|
# intents.auto_moderation_configuration = True
|
|
# intents.auto_moderation_execution = True
|
|
# intents.bans = True
|
|
# intents.dm_messages = True
|
|
# intents.dm_reactions = True
|
|
# intents.dm_typing = True
|
|
# intents.emojis = True
|
|
# intents.emojis_and_stickers = True
|
|
# intents.guild_messages = True
|
|
# intents.guild_reactions = True
|
|
# intents.guild_scheduled_events = True
|
|
# intents.guild_typing = True
|
|
intents.guilds = True
|
|
# intents.integrations = True
|
|
# intents.invites = True
|
|
# intents.members = True
|
|
intents.message_content = True
|
|
intents.messages = True
|
|
# intents.moderation = True
|
|
# intents.presences = True
|
|
# intents.reactions = True
|
|
# intents.typing = True
|
|
# intents.value = True
|
|
# intents.voice_states = True
|
|
# intents.webhooks = True
|
|
super().__init__(command_prefix=commands.when_mentioned_or("/"), intents=intents)
|
|
# async def setup_hook(self) -> None:
|
|
# self.add_view(OnboardButtons())
|
|
#self.add_view(OnboardButtons()) #Add More views with more add_view commands.
|
|
|
|
|
|
## Instantiate bot ##
|
|
bot = PersistentViewBot()
|
|
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print(f'Logged in as {bot.user}')
|
|
# await welcome_message()
|
|
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
print("Users in the DB:")
|
|
c.execute("SELECT id, username FROM users")
|
|
for row in c.fetchall():
|
|
print(f' ID: {row[0]} User: {row[1]}')
|
|
print("Servers in the DB:")
|
|
c.execute("SELECT id, join_url FROM mc_servers")
|
|
for row in c.fetchall():
|
|
print(f' ID: {row[0]} URL: {row[1]}')
|
|
print("Permissions in the DB:")
|
|
c.execute("SELECT user_id, mc_server_id, permission_level FROM mc_server_users")
|
|
for row in c.fetchall():
|
|
print(f' U: {row[0]} S: {row[1]} Perm: {row[2]}')
|
|
|
|
print("Ready.")
|
|
|
|
@bot.tree.command(name='addserver', description="Add a Minecraft server to the bot")
|
|
@app_commands.describe(
|
|
status_url="The Minecraft Minder root URL to query/change server status from, like `http://example.com/minecraft.php?password=1234`",
|
|
join_url="The Minecraft server hostname and port for people to join, like `minecraft.example.com:25565`"
|
|
)
|
|
async def addserver(interaction: discord.Interaction, status_url: str, join_url: str):
|
|
logging.info(f'Addserver: {interaction.user.name} {interaction.user.id} added {status_url} {join_url}')
|
|
|
|
# Connect to the database using a context manager
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
c.execute("INSERT OR REPLACE INTO users (id, username) VALUES (?,?)", (interaction.user.id,interaction.user.name))
|
|
c.execute("INSERT INTO mc_servers (status_url, join_url) VALUES (?,?)", (status_url,join_url))
|
|
server_id = c.lastrowid
|
|
c.execute("INSERT OR REPLACE INTO mc_server_users (user_id, mc_server_id, permission_level) VALUES (?,?,?)",
|
|
(interaction.user.id, server_id, 100))
|
|
su_id = c.lastrowid
|
|
|
|
conn.commit()
|
|
logging.warning(f'Addserver: st: {status_url} jo: {join_url} u: {interaction.user.id} s: {server_id} su: {su_id}')
|
|
await interaction.response.send_message(content="Added!", ephemeral=True)
|
|
|
|
@bot.tree.command(name='adduser', description="Give a user privileges to control your Minecraft server")
|
|
@app_commands.describe(member="The member you want to add", server_id="The internal server ID to add them to")
|
|
async def adduser(interaction: discord.Interaction, member: discord.Member, server_id: int):
|
|
logging.info(f'Adduser: {member.name} {member.id} {server_id}')
|
|
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
c.execute("SELECT s.id, su.permission_level FROM mc_servers s JOIN mc_server_users su ON su.user_id = ? AND su.mc_server_id = ? AND su.mc_server_id = s.id", (interaction.user.id,server_id))
|
|
row = c.fetchone()
|
|
if row:
|
|
if row[1] >= 100:
|
|
c.execute("INSERT OR REPLACE INTO users (id, username) VALUES (?,?)", (member.id,member.name))
|
|
|
|
c.execute("SELECT user_id, mc_server_id FROM mc_server_users WHERE user_id = ? AND mc_server_id = ?", (member.id, server_id))
|
|
if c.fetchone():
|
|
logging.warning(f'Adduser: u: {interaction.user.id} m: {member.name} s: {row[0]} has perm')
|
|
await interaction.response.send_message(content="{} is already added to the server!".format(member.mention), ephemeral=True)
|
|
return
|
|
else:
|
|
c.execute("INSERT OR REPLACE INTO mc_server_users (user_id, mc_server_id, permission_level) VALUES (?,?,?)",
|
|
(member.id, row[0], 1))
|
|
|
|
conn.commit()
|
|
logging.warning(f'Adduser: u: {interaction.user.id} m: {member.name} s: {row[0]}')
|
|
await interaction.response.send_message(content="Added {} to the server!".format(member.mention))
|
|
return
|
|
else:
|
|
logging.error(f'Adduser: u: {interaction.user.id} m: {member.name} s: {server_id} privfail')
|
|
await interaction.response.send_message(content="You don't have enough privileges to add users to server #{}! ({}<100)".format(server_id, row[1]), ephemeral=True)
|
|
return
|
|
else:
|
|
logging.error(f'Adduser: u: {interaction.user.id} m: {member.name} s: {server_id} permfail')
|
|
await interaction.response.send_message(content="You don't have permission to access server #{}!".format(server_id), ephemeral=True)
|
|
return
|
|
|
|
@bot.tree.command(name='removeuser', description="Remove a user from the ability to control your Minecraft server")
|
|
@app_commands.describe(member="The member you want to remove", server_id="The internal server ID to remove them from")
|
|
async def removeuser(interaction: discord.Interaction, member: discord.Member, server_id: int):
|
|
logging.info(f'Removeuser: {member.name} {member.id} {server_id}')
|
|
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
c.execute("SELECT s.id, su.permission_level FROM mc_servers s JOIN mc_server_users su ON su.user_id = ? AND su.mc_server_id = ? AND su.mc_server_id = s.id", (interaction.user.id,server_id))
|
|
row = c.fetchone()
|
|
if row:
|
|
if row[1] >= 100:
|
|
c.execute("SELECT user_id, mc_server_id FROM mc_server_users WHERE user_id = ? AND mc_server_id = ?", (member.id, server_id))
|
|
if c.fetchone() == None:
|
|
logging.warning(f'Removeuser: u: {interaction.user.id} m: {member.name} s: {row[0]} doesn\'t have perm')
|
|
await interaction.response.send_message(content="{} is already removed from the server!".format(member.mention), ephemeral=True)
|
|
return
|
|
else:
|
|
c.execute("DELETE FROM mc_server_users WHERE user_id=? AND mc_server_id=?", (member.id, row[0]))
|
|
|
|
conn.commit()
|
|
logging.warning(f'Removeuser: u: {interaction.user.id} m: {member.name} s: {row[0]}')
|
|
await interaction.response.send_message(content="Removed {} from the server!".format(member.mention), ephemeral=True)
|
|
return
|
|
else:
|
|
logging.error(f'Removeuser: u: {interaction.user.id} m: {member.name} s: {server_id} privfail')
|
|
await interaction.response.send_message(content="You don't have enough privileges to remove users from server #{}! ({}<100)".format(server_id, row[1]), ephemeral=True)
|
|
return
|
|
else:
|
|
logging.error(f'Removeuser: u: {interaction.user.id} m: {member.name} s: {server_id} permfail')
|
|
await interaction.response.send_message(content="You don't have permission to access server #{}!".format(server_id), ephemeral=True)
|
|
return
|
|
|
|
@bot.tree.command(name='listservers', description="Show the IDs of the MC servers you have access to")
|
|
async def listservers(interaction: discord.Interaction):
|
|
logging.info(f'Listservers: {interaction.user.name}')
|
|
|
|
# Connect to the database using a context manager
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
c.execute("SELECT id, status_url, join_url FROM mc_servers s JOIN mc_server_users su ON su.user_id = ?", (interaction.user.id,))
|
|
|
|
o = ""
|
|
for row in c.fetchall():
|
|
o += '- **ID**: {}\n - **Status URL**: {}\n - **Join URL**: {}\n'.format(row[0], row[1], row[2])
|
|
if o == "":
|
|
o = "No servers."
|
|
|
|
await interaction.response.send_message(content=o, ephemeral=True)
|
|
|
|
@bot.tree.command(name='status', description="Minecraft Status")
|
|
async def cmd_status(interaction: discord.Interaction):
|
|
logging.warning(f'Member {interaction.user} queried Minecraft status.')
|
|
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
# Todo: select "this guild's/channel's" server not just the first match
|
|
c.execute("SELECT s.id, su.permission_level, s.status_url, s.join_url FROM mc_servers s JOIN mc_server_users su ON su.user_id = ? AND su.mc_server_id = s.id", (interaction.user.id,))
|
|
row = c.fetchone()
|
|
if row:
|
|
if row[1] >= 1:
|
|
logging.warning(f'Member {interaction.user} started Minecraft.')
|
|
msg = await interaction.response.defer(thinking=True)
|
|
res = requests.post(row[2], data={"action": "status"})
|
|
await interaction.followup.send(content=f'{res.text.capitalize()}\nJoin at `{row[3]}`')
|
|
|
|
@bot.tree.command(name='start', description="Start Minecraft")
|
|
async def cmd_start(interaction: discord.Interaction):
|
|
logging.info(f'Start: {interaction.user.name} {interaction.user.id}')
|
|
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
# Todo: select "this guild's/channel's" server not just the first match
|
|
c.execute("SELECT s.id, su.permission_level, s.status_url, s.join_url FROM mc_servers s JOIN mc_server_users su ON su.user_id = ? AND su.mc_server_id = s.id", (interaction.user.id,))
|
|
row = c.fetchone()
|
|
if row:
|
|
if row[1] >= 1:
|
|
logging.warning(f'Member {interaction.user} started Minecraft.')
|
|
msg = await interaction.response.defer(thinking=True)
|
|
res = requests.post(row[2], data={"action": "on"})
|
|
cont=True
|
|
count=0
|
|
while cont:
|
|
count += 1
|
|
time.sleep(5)
|
|
res = requests.post(row[2], data={"action": "status"})
|
|
if "running" in res.text:
|
|
await interaction.followup.send(content=f'Started: {res.text.capitalize()}\nJoin at `{row[3]}`')
|
|
cont=False
|
|
elif count > 10:
|
|
await interaction.followup.send(content=f'Starting timed out: {res.text.capitalize()}')
|
|
cont=False
|
|
return
|
|
else:
|
|
logging.error(f'Start: u: {interaction.user.id} m: {interaction.user.name} s: {row[0]} permfail')
|
|
await interaction.response.send_message(content="You don't have permission to access server #{}!".format(row[0]), ephemeral=True)
|
|
return
|
|
else:
|
|
logging.error(f'Start: u: {interaction.user.id} m: {interaction.user.name} permfail')
|
|
await interaction.response.send_message(content="You don't have permission to access any servers!", ephemeral=True)
|
|
return
|
|
|
|
@bot.command(name='99')
|
|
async def cmd_nine_nine(ctx):
|
|
brooklyn_99_quotes = [
|
|
'I\'m the human form of the 💯 emoji.',
|
|
'Bingpot!',
|
|
(
|
|
'Cool. Cool cool cool cool cool cool cool, '
|
|
'no doubt no doubt no doubt no doubt.'
|
|
),
|
|
]
|
|
|
|
response = random.choice(brooklyn_99_quotes)
|
|
await ctx.send(response)
|
|
|
|
@bot.command(name='shutdown')
|
|
async def cmd_shutdown(ctx):
|
|
"""Shutdown the bot"""
|
|
await ctx.send("Shutting down!")
|
|
sys.exit()
|
|
|
|
@bot.command(name='sync')
|
|
async def cmd_sync(ctx):
|
|
synced = await bot.tree.sync()
|
|
o = f'{len(synced)}/{len(bot.tree.get_commands())} Commands Synced: ({", ".join([str(o.name) for o in synced])})'
|
|
await ctx.send(o, ephemeral=True)
|
|
logging.warning(o)
|
|
|
|
@bot.event
|
|
async def on_member_remove(member):
|
|
# Log member leave
|
|
logging.warning(f'Member {member.name} left the server.')
|
|
|
|
|
|
## Runtime ##
|
|
|
|
# Connect to SQLite database
|
|
with sqlite3.connect('bot_data.db') as conn:
|
|
c = conn.cursor()
|
|
|
|
# Create table with fields if it doesn't exist already
|
|
c.execute('''CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
username TEXT
|
|
)''')
|
|
c.execute('''CREATE TABLE IF NOT EXISTS mc_servers (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
status_url TEXT,
|
|
join_url TEXT
|
|
)''')
|
|
c.execute('''CREATE TABLE IF NOT EXISTS mc_server_users (
|
|
user_id TEXT,
|
|
mc_server_id TEXT,
|
|
permission_level INTEGER,
|
|
FOREIGN KEY (user_id) REFERENCES users (id),
|
|
FOREIGN KEY (mc_server_id) REFERENCES mc_servers (id),
|
|
PRIMARY KEY (user_id, mc_server_id)
|
|
)''')
|
|
|
|
conn.commit()
|
|
|
|
# Start bot
|
|
bot.run(TOKEN)
|