Eh, kinda working
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
DISCORD_TOKEN=YOUR_DISCORD_BOT_TOKEN
|
||||
|
||||
MYSQL_HOST=localhost
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=password
|
||||
MYSQL_DATABASE=momentum
|
||||
|
||||
SITUPS=3
|
||||
BICEP_CURLS=4
|
||||
PUSHUPS=5
|
||||
SQUATS=5
|
||||
LUNGES=2
|
||||
RUSSIAN_TWISTS=3
|
||||
WALL_PUSHUPS=5
|
||||
26
bot.py
Normal file
26
bot.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
from db import init_db
|
||||
from commands import register
|
||||
|
||||
load_dotenv()
|
||||
|
||||
class Momentum(discord.Client):
|
||||
def __init__(self):
|
||||
super().__init__(intents=discord.Intents.default())
|
||||
self.tree = app_commands.CommandTree(self)
|
||||
|
||||
async def setup_hook(self):
|
||||
register(self.tree)
|
||||
await self.tree.sync()
|
||||
print("🌐 Commands synced")
|
||||
|
||||
async def on_ready(self):
|
||||
print(f"🔥 Momentum online as {self.user}")
|
||||
init_db()
|
||||
|
||||
bot = Momentum()
|
||||
bot.run(os.getenv("DISCORD_TOKEN"))
|
||||
264
commands.py
Normal file
264
commands.py
Normal file
@@ -0,0 +1,264 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from db import get_db
|
||||
from constants import VALORANT_RANKS, WORKOUTS
|
||||
from helpers import apply_rr
|
||||
import random
|
||||
|
||||
def register(tree: app_commands.CommandTree):
|
||||
|
||||
# -------------------
|
||||
# /create
|
||||
# -------------------
|
||||
@tree.command(name="create", description="Link your Valorant account")
|
||||
@app_commands.describe(
|
||||
val_tag="VALUSERNAME#TAG",
|
||||
rank="Your current Valorant rank",
|
||||
rr="Current RR (0-99)"
|
||||
)
|
||||
@app_commands.choices(
|
||||
rank=[app_commands.Choice(name=r, value=r) for r in VALORANT_RANKS]
|
||||
)
|
||||
async def create(
|
||||
interaction: discord.Interaction,
|
||||
val_tag: str,
|
||||
rank: str,
|
||||
rr: int
|
||||
):
|
||||
db = get_db()
|
||||
cur = db.cursor()
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO users (discord_id, val_tag, rank, rr)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
val_tag=VALUES(val_tag),
|
||||
rank=VALUES(rank),
|
||||
rr=VALUES(rr)
|
||||
""", (str(interaction.user.id), val_tag, rank, rr))
|
||||
|
||||
cur.close()
|
||||
db.close()
|
||||
|
||||
await interaction.response.send_message("✅ Account linked", ephemeral=True)
|
||||
|
||||
# -------------------
|
||||
# /match
|
||||
# -------------------
|
||||
enabled_workouts = [
|
||||
app_commands.Choice(name=name, value=name)
|
||||
for name, value in WORKOUTS.items()
|
||||
if value > 0
|
||||
]
|
||||
|
||||
@tree.command(name="match", description="Log a Valorant match")
|
||||
@app_commands.describe(
|
||||
result="Match result",
|
||||
rr_change="RR gained or lost",
|
||||
kd="Kills/Deaths (e.g. 20/15)",
|
||||
workout="Workout assigned for deaths"
|
||||
)
|
||||
@app_commands.choices(
|
||||
result=[
|
||||
app_commands.Choice(name="WIN", value="WIN"),
|
||||
app_commands.Choice(name="LOSS", value="LOSS")
|
||||
],
|
||||
workout=enabled_workouts
|
||||
)
|
||||
async def match(
|
||||
interaction: discord.Interaction,
|
||||
result: str,
|
||||
rr_change: int,
|
||||
kd: str,
|
||||
workout: str
|
||||
):
|
||||
try:
|
||||
kills, deaths = map(int, kd.split("/"))
|
||||
except ValueError:
|
||||
await interaction.response.send_message(
|
||||
"❌ K/D must be in format `kills/deaths` (e.g. 20/15)",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
db = get_db()
|
||||
cur = db.cursor(dictionary=True)
|
||||
|
||||
cur.execute(
|
||||
"SELECT * FROM users WHERE discord_id=%s",
|
||||
(str(interaction.user.id),)
|
||||
)
|
||||
user = cur.fetchone()
|
||||
|
||||
if not user:
|
||||
await interaction.response.send_message(
|
||||
"❌ You must run `/create` first",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
new_rank, new_rr = apply_rr(user["rank"], user["rr"], rr_change)
|
||||
|
||||
per_death = WORKOUTS[workout]
|
||||
workout_total = deaths * per_death
|
||||
|
||||
cur.execute("""
|
||||
UPDATE users
|
||||
SET
|
||||
kills = kills + %s,
|
||||
deaths = deaths + %s,
|
||||
rank = %s,
|
||||
rr = %s
|
||||
WHERE discord_id = %s
|
||||
""", (kills, deaths, new_rank, new_rr, str(interaction.user.id)))
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO workouts (discord_id, workout, amount)
|
||||
VALUES (%s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE amount = amount + %s
|
||||
""", (str(interaction.user.id), workout, workout_total, workout_total))
|
||||
|
||||
cur.close()
|
||||
db.close()
|
||||
|
||||
await interaction.response.send_message(
|
||||
f"🏆 Match logged\n"
|
||||
f"**Rank:** {new_rank} ({new_rr} RR)\n"
|
||||
f"**Workout:** {workout} × {workout_total}"
|
||||
)
|
||||
|
||||
# -------------------
|
||||
# /stats
|
||||
# -------------------
|
||||
@tree.command(name="stats", description="Show Momentum stats")
|
||||
async def stats(
|
||||
interaction: discord.Interaction,
|
||||
user: discord.User | None = None
|
||||
):
|
||||
target = user or interaction.user
|
||||
|
||||
db = get_db()
|
||||
cur = db.cursor(dictionary=True)
|
||||
|
||||
cur.execute(
|
||||
"SELECT * FROM users WHERE discord_id=%s",
|
||||
(str(target.id),)
|
||||
)
|
||||
u = cur.fetchone()
|
||||
|
||||
if not u:
|
||||
await interaction.response.send_message(
|
||||
"❌ User not found",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
cur.execute(
|
||||
"SELECT workout, amount FROM workouts WHERE discord_id=%s",
|
||||
(str(target.id),)
|
||||
)
|
||||
workout_rows = cur.fetchall()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{target.display_name}'s Momentum Stats",
|
||||
color=discord.Color.red()
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Rank",
|
||||
value=f"{u['rank']} ({u['rr']} RR)",
|
||||
inline=False
|
||||
)
|
||||
embed.add_field(
|
||||
name="Valorant",
|
||||
value=u["val_tag"],
|
||||
inline=False
|
||||
)
|
||||
embed.add_field(
|
||||
name="Kills / Deaths",
|
||||
value=f"{u['kills']} / {u['deaths']}",
|
||||
inline=False
|
||||
)
|
||||
|
||||
workout_text = (
|
||||
"\n".join(f"{w['workout']}: {w['amount']}" for w in workout_rows)
|
||||
if workout_rows else "None"
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Workouts Completed",
|
||||
value=workout_text,
|
||||
inline=False
|
||||
)
|
||||
|
||||
cur.close()
|
||||
db.close()
|
||||
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
# -------------------
|
||||
# /workout
|
||||
# -------------------
|
||||
@tree.command(name="workout", description="Get a random workout")
|
||||
async def workout(interaction: discord.Interaction):
|
||||
enabled = [w for w, v in WORKOUTS.items() if v > 0]
|
||||
choice = random.choice(enabled)
|
||||
|
||||
await interaction.response.send_message(
|
||||
f"💪 **{choice}**\n"
|
||||
f"{WORKOUTS[choice]} per death"
|
||||
)
|
||||
|
||||
# -------------------
|
||||
# /edit (admin)
|
||||
# -------------------
|
||||
@tree.command(name="edit", description="Edit a user's stats (Admin)")
|
||||
@app_commands.choices(
|
||||
rank=[app_commands.Choice(name=r, value=r) for r in VALORANT_RANKS]
|
||||
)
|
||||
async def edit(
|
||||
interaction: discord.Interaction,
|
||||
user: discord.User,
|
||||
rank: str | None = None,
|
||||
rr: int | None = None,
|
||||
val_tag: str | None = None
|
||||
):
|
||||
if not interaction.user.guild_permissions.administrator:
|
||||
await interaction.response.send_message(
|
||||
"❌ Admins only",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
fields = []
|
||||
values = []
|
||||
|
||||
if rank:
|
||||
fields.append("rank=%s")
|
||||
values.append(rank)
|
||||
if rr is not None:
|
||||
fields.append("rr=%s")
|
||||
values.append(rr)
|
||||
if val_tag:
|
||||
fields.append("val_tag=%s")
|
||||
values.append(val_tag)
|
||||
|
||||
if not fields:
|
||||
await interaction.response.send_message(
|
||||
"Nothing to update",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
values.append(str(user.id))
|
||||
|
||||
db = get_db()
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
f"UPDATE users SET {', '.join(fields)} WHERE discord_id=%s",
|
||||
values
|
||||
)
|
||||
cur.close()
|
||||
db.close()
|
||||
|
||||
await interaction.response.send_message("✏️ User updated")
|
||||
23
constants.py
Normal file
23
constants.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
VALORANT_RANKS = [
|
||||
"Iron 1","Iron 2","Iron 3",
|
||||
"Bronze 1","Bronze 2","Bronze 3",
|
||||
"Silver 1","Silver 2","Silver 3",
|
||||
"Gold 1","Gold 2","Gold 3",
|
||||
"Platinum 1","Platinum 2","Platinum 3",
|
||||
"Diamond 1","Diamond 2","Diamond 3",
|
||||
"Ascendant 1","Ascendant 2","Ascendant 3",
|
||||
"Immortal 1","Immortal 2","Immortal 3",
|
||||
"Radiant"
|
||||
]
|
||||
|
||||
WORKOUTS = {
|
||||
"Sit Ups": int(os.getenv("SITUPS", 0)),
|
||||
"Bicep Curls": int(os.getenv("BICEP_CURLS", 0)),
|
||||
"Pushups": int(os.getenv("PUSHUPS", 0)),
|
||||
"Squats": int(os.getenv("SQUATS", 0)),
|
||||
"Lunges": int(os.getenv("LUNGES", 0)),
|
||||
"Russian Twists": int(os.getenv("RUSSIAN_TWISTS", 0)),
|
||||
"Wall Pushups": int(os.getenv("WALL_PUSHUPS", 0)),
|
||||
}
|
||||
43
db.py
Normal file
43
db.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import mysql.connector
|
||||
import os
|
||||
|
||||
def get_db():
|
||||
return mysql.connector.connect(
|
||||
host=os.getenv("MYSQL_HOST"),
|
||||
user=os.getenv("MYSQL_USER"),
|
||||
password=os.getenv("MYSQL_PASSWORD"),
|
||||
database=os.getenv("MYSQL_DATABASE"),
|
||||
autocommit=True
|
||||
)
|
||||
|
||||
def init_db():
|
||||
db = get_db()
|
||||
cur = db.cursor()
|
||||
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
discord_id VARCHAR(32) PRIMARY KEY,
|
||||
val_tag VARCHAR(32) NOT NULL,
|
||||
rank VARCHAR(32) NOT NULL,
|
||||
rr INT DEFAULT 0,
|
||||
kills INT DEFAULT 0,
|
||||
deaths INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS workouts (
|
||||
discord_id VARCHAR(32),
|
||||
workout VARCHAR(32),
|
||||
amount INT DEFAULT 0,
|
||||
PRIMARY KEY (discord_id, workout),
|
||||
FOREIGN KEY (discord_id)
|
||||
REFERENCES users(discord_id)
|
||||
ON DELETE CASCADE
|
||||
)
|
||||
""")
|
||||
|
||||
cur.close()
|
||||
db.close()
|
||||
print("✅ Database ready")
|
||||
11
helpers.py
Normal file
11
helpers.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from constants import VALORANT_RANKS
|
||||
|
||||
def apply_rr(rank: str, rr: int, change: int):
|
||||
rr += change
|
||||
idx = VALORANT_RANKS.index(rank)
|
||||
|
||||
while rr >= 100 and idx < len(VALORANT_RANKS) - 1:
|
||||
rr -= 100
|
||||
idx += 1
|
||||
|
||||
return VALORANT_RANKS[idx], rr
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
discord.py>=2.3.2
|
||||
mysql-connector-python>=8.3.0
|
||||
python-dotenv>=1.0.1
|
||||
Reference in New Issue
Block a user