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