Added search feature to `!funfact`

- Users can now search for fun facts using `!funfact search [keywords]` without brackets. Returns best match.
- Also added some new facts
- Minor tweaks to force-sync of Discord slash commands
kami_dev
Kami 2025-02-14 11:42:20 +01:00
parent 0f1077778f
commit 6da1744990
7 changed files with 119 additions and 33 deletions

View File

@ -12,7 +12,7 @@ import modules.utility
from modules.db import log_message, lookup_user, log_bot_event from modules.db import log_message, lookup_user, log_bot_event
primary_guild = globals.constants.primary_discord_guild()["object"] primary_guild = globals.constants.primary_discord_guild()
class DiscordBot(commands.Bot): class DiscordBot(commands.Bot):
def __init__(self): def __init__(self):
@ -86,7 +86,13 @@ class DiscordBot(commands.Bot):
await ctx.reply(_result) await ctx.reply(_result)
async def on_message(self, message): async def on_message(self, message):
globals.log(f"Message detected by '{message.author.name}' in '{message.author.guild.name}' - #'{message.channel.name}'", "DEBUG") if message.guild:
guild_name = message.guild.name
channel_name = message.channel.name
else:
guild_name = "DM"
channel_name = "Direct Message"
globals.log(f"Message detected by '{message.author.name}' in '{guild_name}' - #'{channel_name}'", "DEBUG")
#globals.log(f"Message body:\n{message}\nMessage content: {message.content}", "DEBUG") # Full message debug #globals.log(f"Message body:\n{message}\nMessage content: {message.content}", "DEBUG") # Full message debug
globals.log(f"Message content: '{message.content}'", "DEBUG") # Partial message debug (content only) globals.log(f"Message content: '{message.content}'", "DEBUG") # Partial message debug (content only)
globals.log(f"Attempting UUI lookup on '{message.author.name}' ...", "DEBUG") globals.log(f"Attempting UUI lookup on '{message.author.name}' ...", "DEBUG")
@ -106,15 +112,15 @@ class DiscordBot(commands.Bot):
user_is_bot=is_bot user_is_bot=is_bot
) )
globals.log(f"... UUI lookup complete", "DEBUG")
user_data = lookup_user(db_conn=self.db_conn, identifier=user_id, identifier_type="discord_user_id") user_data = lookup_user(db_conn=self.db_conn, identifier=user_id, identifier_type="discord_user_id")
user_uuid = user_data["UUID"] if user_data else "UNKNOWN" user_uuid = user_data["UUID"] if user_data else "UNKNOWN"
globals.log(f"... UUI lookup complete", "DEBUG")
if user_uuid: if user_uuid:
# The "platform" can be e.g. "discord" or you can store the server name # The "platform" can be e.g. "discord" or you can store the server name
platform_str = f"discord-{message.guild.name}" if message.guild else "discord-DM" platform_str = f"discord-{guild_name}"
# The channel name can be message.channel.name or "DM" if it's a private channel # The channel name can be message.channel.name or "DM" if it's a private channel
channel_str = message.channel.name if hasattr(message.channel, "name") else "DM" channel_str = channel_name
# If you have attachments, you could gather them as links. # If you have attachments, you could gather them as links.
try: try:
@ -183,20 +189,22 @@ class DiscordBot(commands.Bot):
# Sync slash commands globally # Sync slash commands globally
#await self.tree.sync() #await self.tree.sync()
#globals.log("Discord slash commands synced.") #globals.log("Discord slash commands synced.")
primary_guild_int = int(self.config["discord_guilds"][0])
num_guilds = len(self.config["discord_guilds"]) num_guilds = len(self.config["discord_guilds"])
cmd_tree_result = (await self.tree.sync(guild=primary_guild)) cmd_tree_result = (await self.tree.sync(guild=primary_guild["object"]))
command_names = [command.name for command in cmd_tree_result] if cmd_tree_result else None command_names = [command.name for command in cmd_tree_result] if cmd_tree_result else None
try: if primary_guild["id"]:
guild_info = await modules.utility.get_guild_info(self, primary_guild_int) try:
primary_guild_name = guild_info["name"] guild_info = await modules.utility.get_guild_info(self, primary_guild["id"])
except Exception as e: primary_guild_name = guild_info["name"]
primary_guild_name = f"{primary_guild_int} (id)" except Exception as e:
globals.log(f"Guild lookup failed: {e}", "ERROR") primary_guild_name = f"{primary_guild["id"]}"
globals.log(f"Guild lookup failed: {e}", "ERROR")
_log_message = f"{num_guilds} guilds (global)" if num_guilds > 1 else f"guild: {primary_guild_name}" _log_message = f"{num_guilds} guilds (global)" if num_guilds > 1 else f"guild: {primary_guild_name}"
globals.log(f"Discord slash commands force synced to {_log_message}") globals.log(f"Discord slash commands force synced to {_log_message}")
globals.log(f"Discord slash commands that got synced: {command_names}") globals.log(f"Discord slash commands that got synced: {command_names}")
else:
globals.log("Discord commands synced globally.")
except Exception as e: except Exception as e:
globals.log(f"Unable to sync Discord slash commands: {e}") globals.log(f"Unable to sync Discord slash commands: {e}")

View File

@ -4,6 +4,7 @@ import time
from modules import utility from modules import utility
import globals import globals
import json import json
import re
from modules import db from modules import db
@ -873,7 +874,51 @@ async def send_message(ctx, text):
await ctx.send(text) await ctx.send(text)
# Common backend function to get a random fun fact # Common backend function to get a random fun fact
def get_fun_fact(): def get_fun_fact(keywords=None):
"""
If keywords is None or empty, returns a random fun fact.
Otherwise, searches for the best matching fun fact in dictionary/funfacts.json.
For each fun fact:
- Awards 2 points for each keyword found as a whole word.
- Awards 1 point for each keyword found as a partial match.
- Subtracts 1 point for each keyword provided.
In the event of a tie, one fun fact is chosen at random.
"""
with open('dictionary/funfacts.json', 'r') as f: with open('dictionary/funfacts.json', 'r') as f:
facts = json.load(f) facts = json.load(f)
return random.choice(facts)
# If no keywords provided, return a random fact.
if not keywords:
return random.choice(facts)
if len(keywords) < 2:
return "If you want to search, please append the command with `search [keywords]` without brackets."
keywords = keywords[1:]
lower_keywords = [kw.lower() for kw in keywords]
best_score = None
best_facts = []
for fact in facts:
score_total = 0
fact_lower = fact.lower()
# For each keyword, check for whole word and partial matches.
for kw in lower_keywords:
if re.search(r'\b' + re.escape(kw) + r'\b', fact_lower):
score_total += 2
elif kw in fact_lower:
score_total += 1
# Apply penalty for each keyword.
final_score = score_total - len(lower_keywords)
if best_score is None or final_score > best_score:
best_score = final_score
best_facts = [fact]
elif final_score == best_score:
best_facts.append(fact)
if not best_facts:
return "No matching fun facts found."
return random.choice(best_facts)

View File

@ -35,9 +35,10 @@ def setup(bot):
config_data = globals.load_config_file() config_data = globals.load_config_file()
@bot.command(name='funfact', aliases=['fun-fact']) @bot.command(name='funfact', aliases=['fun-fact'])
async def funfact_command(ctx): async def funfact_command(ctx, *keywords):
fact = cc.get_fun_fact() # keywords is a tuple of strings from the command arguments.
# Replies to the invoking user fact = cc.get_fun_fact(list(keywords))
# Reply to the invoking user.
await ctx.reply(fact) await ctx.reply(fact)

View File

@ -15,10 +15,11 @@ def setup(bot, db_conn=None):
""" """
@commands.command(name='funfact', aliases=['fun-fact']) @commands.command(name='funfact', aliases=['fun-fact'])
async def funfact_command(self, ctx: commands.Context): async def funfact_command(self, ctx: commands.Context, *keywords: str):
fact = cc.get_fun_fact() # Convert keywords tuple to list and pass to get_fun_fact.
# Replies to the invoking user by prefixing their name fact = cc.get_fun_fact(list(keywords))
await ctx.reply(fact) # Reply to the invoking user by prefixing their name.
await ctx.reply(f"@{ctx.author.name} {fact}")
@bot.command(name="greet") @bot.command(name="greet")
async def cmd_greet(ctx: commands.Context): async def cmd_greet(ctx: commands.Context):

View File

@ -1,5 +1,6 @@
{ {
"discord_guilds": [896713616089309184], "discord_guilds": [896713616089309184],
"sync_commands_globally": true,
"twitch_channels": ["OokamiKunTV", "ookamipup"], "twitch_channels": ["OokamiKunTV", "ookamipup"],
"command_modules": ["cmd_discord", "cmd_twitch", "cmd_common"], "command_modules": ["cmd_discord", "cmd_twitch", "cmd_common"],
"logging": { "logging": {

View File

@ -506,6 +506,28 @@
"The historical miniaturization of transistors has been a key driver in the rapid evolution of computing hardware.", "The historical miniaturization of transistors has been a key driver in the rapid evolution of computing hardware.",
"Advances in image processing now enable computers to interpret and analyze visual data with remarkable precision.", "Advances in image processing now enable computers to interpret and analyze visual data with remarkable precision.",
"Some nuclear power plants use naturally sourced water for cooling, challenging the notion that all require artificial systems.", "Some nuclear power plants use naturally sourced water for cooling, challenging the notion that all require artificial systems.",
"Webster Lake, aka 'Chargoggagoggmanchauggagoggchaubunagungamaugg', can be translated to 'You fish on your side, I'll fish on my side, and no one shall fish in the middle.'" "Webster Lake, aka 'Chargoggagoggmanchauggagoggchaubunagungamaugg', can be translated to 'You fish on your side, I'll fish on my side, and no one shall fish in the middle.'",
"Cows, along with other ruminants like sheep, deer and giraffes, have 4 'stomachs', or compartments within the stomach, called the rumen, the reticulum, the omasum and the abomasum.",
"It is proposed that cheese have existed for around 10.000 years, with the earliest archaelogical record being almost 8.000 years old.",
"Wolves have been observed exhibiting mourning behaviors, lingering near the remains of fallen pack members in a manner that suggests they experience grief.",
"Recent research reveals that the unique patterns in wolf howls serve as a vocal fingerprint, conveying detailed information about an individual's identity and emotional state.",
"Contrary to the classic 'alpha' myth, many wolf packs function as family units where the parents lead the group and even subordinate wolves may reproduce.",
"Wolves display remarkable flexibility in their hunting strategies, adapting their techniques based on the prey available and the terrain, demonstrating problem-solving abilities rarely attributed to wild carnivores.",
"In certain wolf populations, individuals have been seen sharing food with non-pack members, challenging the long-held view of wolves as strictly territorial and competitive.",
"Studies show that a wolf's sense of smell is so acute that it can detect prey from several miles away and even follow scent trails that are days old.",
"Cows can recognize and remember the faces of up to 50 individuals, both other cows and humans, indicating a surprisingly complex memory capacity.",
"Beyond their docile reputation, cows have been found to experience a range of emotions, displaying signs of depression when isolated and apparent joy when in the company of their close companions.",
"Research has demonstrated that cows can solve simple puzzles for a reward, challenging the stereotype that they are only passive grazers.",
"Cows communicate through a rich array of vocalizations and subtle body language, suggesting they convey complex emotional states that scientists are only beginning to decipher.",
"Cheese may have been discovered accidentally when milk was stored in containers lined with rennet-rich animal stomachs, causing it to curdle.",
"There are over 1,800 distinct varieties of cheese worldwide, far more than the few types most people are familiar with.",
"Aged cheeses contain virtually no lactose, which explains why many lactose-intolerant individuals can enjoy them without discomfort.",
"Cheese rolling, an annual event in Gloucestershire, England, is not just a quirky tradition but an ancient competitive sport with a history spanning centuries.",
"The characteristic holes in Swiss cheese, known as 'eyes,' are formed by gas bubbles released by bacteria during the fermentation process.",
"Pule cheese, made from the milk of Balkan donkeys, is one of the world's most expensive cheeses, costing over a thousand euros per kilo due to its rarity and labor-intensive production.",
"Cheese may have addictive qualities: during digestion, casomorphins are released from milk proteins, interacting with brain receptors in a way similar to opiates.",
"Some artisanal cheeses are aged in natural caves, where unique microclimates contribute to complex flavors that are difficult to replicate in modern facilities.",
"While Cheddar cheese is now a global staple, it originally came from the small English village of Cheddar, and its production methods have evolved dramatically over time.",
"Modern cheese production often uses vegetarian rennet, derived from microbial or plant sources, challenging the common belief that all cheese is made with animal-derived enzymes.",
"Cows are related to whales, as they both evolved from land-dwelling, even-toed ungulates, hence why a baby whale is called a 'calf'."
] ]

View File

@ -214,10 +214,18 @@ class Constants:
guild is defined; otherwise, `None`. guild is defined; otherwise, `None`.
- "id": The integer ID of the primary guild if available; otherwise, `None`. - "id": The integer ID of the primary guild if available; otherwise, `None`.
""" """
# Checks for a True/False value in the config file to determine if commands sync should be global or single-guild
# If this is 'true' in config, it will
sync_commands_globally = config_data["sync_commands_globally"]
primary_guild_object = None primary_guild_object = None
primary_guild_int = int(config_data["discord_guilds"][0]) if len(config_data["discord_guilds"]) == 1 else None primary_guild_int = None
if primary_guild_int:
primary_guild_object = discord.Object(id=primary_guild_int) if not sync_commands_globally:
log("Discord commands sync set to single-guild in config")
primary_guild_int = int(config_data["discord_guilds"][0]) if len(config_data["discord_guilds"]) > 0 else None
if primary_guild_int:
primary_guild_object = discord.Object(id=primary_guild_int)
return_dict = {"object": primary_guild_object, "id": primary_guild_int} return_dict = {"object": primary_guild_object, "id": primary_guild_int}
return return_dict return return_dict