Made chatbot setup prompt variable, and created an activities cog to shame League players.
This commit is contained in:
parent
6229dd1ade
commit
9cbcfc5b6b
14
.env.example
Normal file
14
.env.example
Normal 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
BIN
boywife.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
207
cogs/activities.py
Normal file
207
cogs/activities.py
Normal 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))
|
||||||
@ -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))
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user