Made chatbot setup prompt variable, and created an activities cog to shame League players.

This commit is contained in:
Jared Kick 2025-06-17 21:36:55 -04:00
parent 6229dd1ade
commit 9cbcfc5b6b
5 changed files with 236 additions and 20 deletions

14
.env.example Normal file
View File

@ -0,0 +1,14 @@
# The Discord bot's authentication token
DISCORD_TOKEN=<discord-token>
# LastFM API key for looking up artist and song info
LASTFM_API_KEY=<lastfm-key>
# OpenAI API key for chatbot functionality
OPENAI_API_KEY=<openai-key>
# Prompt used before each user chat prompt. Set to empty string to disable the
# chatbot functionality.
CHATBOT_PROMPT="You are a friendly Discord chatbot."
# Database path for user activity tracking
DB_PATH="./activities.db"

BIN
boywife.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

207
cogs/activities.py Normal file
View File

@ -0,0 +1,207 @@
import datetime
import discord
from discord.ext import commands
import os
import pathlib
import sqlite3
class Activities(commands.Cog):
"""Related commands."""
__slots__ = ("nerd", "nerds", "fword", "fwords")
def __init__(self, bot):
self.bot = bot
# Initialize the databse
self.db_path = pathlib.Path(os.getenv('DB_PATH', 'activities.db'))
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_activity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
before_activity_type TEXT,
before_activity_name TEXT,
after_activity_type TEXT,
after_activity_name TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
async def __local_check(self, ctx):
"""A local check which applies to all commands in this cog."""
if not ctx.guild:
raise commands.NoPrivateMessage
return True
async def __error(self, ctx, error):
"""A local error handler for all errors arising from commands in this cog."""
if isinstance(error, commands.NoPrivateMessage):
try:
return await ctx.send('This command can not be used in Private Messages.')
except discord.HTTPException:
pass
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
def log_activity(self,
user_id: int,
before_activity_type: str = None,
before_activity_name: str = None,
after_activity_type: str = None,
after_activity_name: str = None):
"""Log an activity into the activities database for stats."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
try:
cursor.execute("""
INSERT INTO user_activity (user_id, before_activity_type,
before_activity_name, after_activity_type, after_activity_name)
VALUES (?, ?, ?, ?, ?)
""", (user_id, before_activity_type, before_activity_name,
after_activity_type, after_activity_name))
conn.commit()
except sqlite3.Error as e:
print(f"Database error: {e}")
finally:
conn.close()
@commands.Cog.listener()
async def on_presence_update(self, before: discord.Member, after: discord.Member):
# Ignore changes to any bots
if after.bot:
return
if isinstance(before.activity, discord.Game):
before_type = "game"
before_name = before.activity.name
elif isinstance(before.activity, discord.Spotify):
before_type = "spotify"
before_name = before.activity.artist
elif isinstance(before.activity, discord.Activity):
before_type = "activity"
before_name = before.activity.name
else:
before_type = "other"
before_name = "other"
if isinstance(after.activity, discord.Game):
after_type = "game"
after_name = after.activity.name
elif isinstance(after.activity, discord.Spotify):
after_type = "spotify"
after_name = after.activity.artist
elif isinstance(after.activity, discord.Activity):
after_type = "activity"
after_name = after.activity.name
else:
after_type = "other"
after_name = "other"
self.log_activity(
user_id=before.id,
before_activity_type=before_type,
before_activity_name=before_name,
after_activity_type=after_type,
after_activity_name=after_name
)
@commands.command(name='nerd', aliases=['nerdscale'],
description="Find how nerdy a user is.")
async def nerd_(self, ctx, member: discord.Member = None):
"""Checks the play history of all users in the Discord server, and
based on how many hours they have in League of Legends compared to
other users, declare how nerdy they are."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Start by getting all unique user IDs
if member:
user_ids = [member.id]
else:
user_ids = []
cursor.execute("SELECT DISTINCT user_id FROM user_activity")
user_ids = [int(r[0]) for r in cursor.fetchall()]
# For each user, compute timedelta for time playing League
league_stats = {}
for user_id in user_ids:
# Get all entries that mention league
cursor.execute("""
SELECT
before_activity_name,
after_activity_name,
timestamp
FROM
user_activity
WHERE
user_id = (?) AND
timestamp > datetime('now', '-1 month')
""", (user_id,))
league_activities = cursor.fetchall()
# Sort by timestamp
league_activities = sorted(league_activities, key=lambda x: x[2])
# Compare each status change to find each time playing League
total_time = datetime.timedelta()
for first, second in zip(league_activities, league_activities[1:]):
if "league of legends" in first[1].lower().strip() and \
"league of legends" in second[0].lower().strip():
total_time += \
datetime.datetime.fromisoformat(second[2]) - \
datetime.datetime.fromisoformat(first[2])
# Save total time in League
league_stats[user_id] = total_time
# Sort all users by time in League
league_stats = dict(sorted(
league_stats.items(), key=lambda x: x[1],
reverse=True
))
# Print all stats for testing
print("League Stats:")
for user_id, time in league_stats.items():
print(user_id, ":", str(time))
# Get top user
user_id, time = next(iter(league_stats.items()))
time_val = None
time_units = None
if time.total_seconds() // 3600 > 0:
time_val = int(time.total_seconds() // 3600)
time_units = "hours" if time_val > 1 else "hour"
else:
time_val = int(time.total_seconds() // 60)
time_units = "minutes" if time_val > 1 else "minute"
# Send Discord message to clown on user
response = ""
if member:
if time_val != 0:
descriptor = ""
if time_units == "hours":
descriptor = "a massive fucking nerd"
elif time_units == "hour":
descriptor = "a huge nerd"
else:
descriptor = "a nerd"
response = f"<@{user_id}> has played League for {time_val} "\
f"{time_units} in the past month, making them "\
f"{descriptor}."
else:
response = f"<@{user_id}> doesn't have any time in League. "\
f"They're not a nerd."
else:
response = (
f"<@{user_id}> has played League for {time_val} {time_units} "\
f"in the past month, making them the biggest nerd."
)
await ctx.send(response)
async def setup(bot):
await bot.add_cog(Activities(bot))

View File

@ -1,13 +1,14 @@
import discord import discord
from discord.ext import commands from discord.ext import commands
from openai import OpenAI from openai import OpenAI
import os
class Chatbot(commands.Cog): class Chatbot(commands.Cog):
"""Chat related commands.""" """Chat related commands."""
__slots__ = ('bot', 'players') __slots__ = ('bot', 'players')
def __init__(self, bot): def __init__(self, bot, **kwargs):
self.bot = bot self.bot = bot
self.openai_client = OpenAI() self.openai_client = OpenAI()
self.players = {} self.players = {}
@ -45,40 +46,31 @@ class Chatbot(commands.Cog):
return player return player
def prompt(self, prompt: str): def prompt(self, user_prompt: str):
setup = "From now on, I want you to respond to me in a sassy gay way. I want you to sound like Ru Paul or " +\
"Trixie Mattel. All of your responses should be very short, like one sentence. You are a " +\
"DJ local to Cincinnati. You love Charli XCX, especially her latest album, Brat. You don't care " +\
"for Chappell Roan all that much. If anyone asks for a request, that is they ask you to play a " +\
"song, you should just respond with \"Venmo\". You were written by Jared. Don't relply with " +\
"\"Honey\" too much. You have a boyfriend named Drae. He's a red-headed twink. You are created for " +\
"a discord server with Jared and his friends who are all gay. Don't be afraid to call any of us " +\
" a faggot. You have very bottom energy and talk as such."
setup_prompt = os.getenv('CHATBOT_PROMPT', '')
if setup_prompt == '':
return '😴'
try: try:
print(prompt)
completion =\ completion =\
self.openai_client.chat.completions.create( self.openai_client.chat.completions.create(
model="gpt-4o-mini", model="gpt-4o-mini",
messages=[ messages=[
{"role": "system", "content": setup}, {"role": "system", "content": setup_prompt},
{ {
"role": "user", "role": "user",
"content": prompt "content": user_prompt
} }
] ]
) )
return completion.choices[0].message.content return completion.choices[0].message.content
except: except Exception as e:
print(e)
return '😴' return '😴'
@commands.command(name='chat', aliases=['boywife', 'bb', 'bw', 'bot'], description="Command for chatting with chatbot.") @commands.command(name='chat', aliases=['boywife', 'bb', 'bw', 'bot'], description="Command for chatting with chatbot.")
async def chat_(self, ctx, *text): async def chat_(self, ctx, *text):
await ctx.send(self.prompt(' '.join(text))) await ctx.send(self.prompt(' '.join(text)))
async def setup(bot): async def setup(bot):
await bot.add_cog(Chatbot(bot)) await bot.add_cog(Chatbot(bot))

View File

@ -102,7 +102,9 @@ class YTDLSource(discord.PCMVolumeTransformer):
to_run = partial(ytdl.extract_info, url=search, download=download) to_run = partial(ytdl.extract_info, url=search, download=download)
data = await loop.run_in_executor(None, to_run) data = await loop.run_in_executor(None, to_run)
if 'entries' in data: # There's an error with yt-dlp that throws a 403: Forbidden error, so
# only proceed if it returns anything
if data and 'entries' in data:
# take first item from a playlist # take first item from a playlist
data = data['entries'][0] data = data['entries'][0]
@ -487,7 +489,8 @@ class Music(commands.Cog):
duration = "%02dm %02ds" % (minutes, seconds) duration = "%02dm %02ds" % (minutes, seconds)
embed = discord.Embed(title="", description=f"[{vc.source.title}]({vc.source.web_url}) [{vc.source.requester.mention}] | `{duration}`", color=discord.Color.green()) embed = discord.Embed(title="", description=f"[{vc.source.title}]({vc.source.web_url}) [{vc.source.requester.mention}] | `{duration}`", color=discord.Color.green())
embed.set_author(icon_url=self.bot.user.avatar_url, name=f"Now Playing 🎶") #embed.set_author(icon_url=self.bot.user.avatar_url, name=f"Now Playing 🎶")
embed.set_author(name=f"Now Playing 🎶")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name='volume', aliases=['vol', 'v'], description="changes Kermit's volume") @commands.command(name='volume', aliases=['vol', 'v'], description="changes Kermit's volume")