322 lines
14 KiB
Python
322 lines
14 KiB
Python
#!/usr/bin/env python3.12
|
|
# pylint: disable=bare-except, broad-exception-caught
|
|
|
|
"""
|
|
Quote cog for Havoc
|
|
"""
|
|
|
|
import traceback
|
|
import time
|
|
import os
|
|
import datetime
|
|
import asyncio
|
|
import discord
|
|
import aiosqlite as sqlite3
|
|
from discord.ext import bridge, commands
|
|
from disc_havoc import Havoc
|
|
|
|
class DB:
|
|
"""DB Utility for Quote Cog"""
|
|
def __init__(self, bot: Havoc):
|
|
self.bot: Havoc = bot
|
|
self.db_path = os.path.join("/", "usr", "local", "share",
|
|
"sqlite_dbs", "quotes.db")
|
|
self.hp_chanid = 1157529874936909934
|
|
|
|
|
|
async def get_quote_count(self):
|
|
"""Get Quote Count"""
|
|
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
|
async with await db_conn.execute("SELECT COUNT (*) FROM quotes") as db_cursor:
|
|
result = await db_cursor.fetchone()
|
|
return result[-1]
|
|
|
|
async def remove_quote(self, quote_id: int):
|
|
"""Remove Quote from DB"""
|
|
try:
|
|
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
|
async with await db_conn.execute("DELETE FROM quotes WHERE id = ?", (quote_id,)) as _:
|
|
await db_conn.commit()
|
|
return True
|
|
except:
|
|
await self.bot.get_channel(self.hp_chanid).send(traceback.format_exc())
|
|
return False
|
|
|
|
async def add_quote(self, message_id: int, channel_id: int,
|
|
quoted_member_id: int,
|
|
message_time: int,
|
|
quoter_friendly: str,
|
|
quoted_friendly: str,
|
|
channel_friendly: str,
|
|
message_content: str,
|
|
):
|
|
"""Add Quote to DB"""
|
|
params = (
|
|
quoter_friendly,
|
|
int(time.time()),
|
|
quoted_friendly,
|
|
quoted_member_id,
|
|
channel_friendly,
|
|
channel_id,
|
|
message_id,
|
|
message_time,
|
|
quoter_friendly,
|
|
message_content,
|
|
)
|
|
|
|
try:
|
|
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
|
# pylint: disable=line-too-long
|
|
db_conn.row_factory = lambda c, r: dict([(col[0], r[idx]) for idx, col in enumerate(c.description)])
|
|
async with await db_conn.execute("INSERT INTO quotes (added_by, added_at, quoted_user_display, quoted_user_memberid, quoted_channel_display, quoted_channel_id, quoted_message_id, quoted_message_time, added_by_friendly, quoted_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
params) as _: # pylint: enable=line-too-long
|
|
await db_conn.commit()
|
|
return True
|
|
except:
|
|
return traceback.format_exc()
|
|
|
|
async def fetch_quote(self, random: bool = False, quoteid: int = None, added_by: str = None, quoted_user: str = None, content: str = None):
|
|
"""Fetch Quote from DB"""
|
|
try:
|
|
query_head = "SELECT id, added_by_friendly, added_at, quoted_user_display, quoted_channel_display, quoted_message_time, quoted_message FROM quotes"
|
|
query = ""
|
|
params = None
|
|
|
|
if random:
|
|
query = f"{query_head} ORDER BY RANDOM() LIMIT 1"
|
|
elif quoteid:
|
|
query = f"{query_head} WHERE id = ? LIMIT 1"
|
|
params = (quoteid,)
|
|
elif added_by:
|
|
query = f"{query_head} WHERE added_by_friendly LIKE ? ORDER BY RANDOM() LIMIT 5"
|
|
params = (f"%{added_by}%",)
|
|
elif quoted_user:
|
|
query = f"{query_head} WHERE quoted_user_display LIKE ? ORDER BY RANDOM() LIMIT 5"
|
|
params = (f"%{quoted_user}%",)
|
|
elif content:
|
|
query = f"{query_head} WHERE quoted_message LIKE ? ORDER BY RANDOM() LIMIT 5"
|
|
params = (f"%{content}%",)
|
|
|
|
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
|
db_conn.row_factory = lambda c, r: dict([(col[0], r[idx]) for idx, col in enumerate(c.description)])
|
|
async with await db_conn.execute(query, params) as db_cursor:
|
|
results = await db_cursor.fetchall()
|
|
if not results:
|
|
return {
|
|
'err': 'No results for query',
|
|
}
|
|
if random or quoteid:
|
|
chosen = results[-1]
|
|
return {
|
|
str(k): v for k,v in chosen.items()
|
|
}
|
|
else:
|
|
return [
|
|
{ str(k): v for k,v in _.items() }
|
|
for _ in results
|
|
]
|
|
except:
|
|
return traceback.format_exc()
|
|
|
|
|
|
class Quote(commands.Cog):
|
|
"""Quote Cog for Havoc"""
|
|
def __init__(self, bot: Havoc):
|
|
self.bot: Havoc = bot
|
|
self.db = DB(self.bot)
|
|
|
|
def is_homeserver(): # pylint: disable=no-method-argument
|
|
"""Check if channel/interaction is within homeserver"""
|
|
def predicate(ctx):
|
|
try:
|
|
return ctx.guild.id == 1145182936002482196
|
|
except:
|
|
traceback.print_exc()
|
|
return False
|
|
return commands.check(predicate)
|
|
|
|
@commands.message_command(name="Add Quote")
|
|
async def add_quote(self, ctx, message: discord.Message):
|
|
"""Add A Quote"""
|
|
hp_chanid = 1157529874936909934
|
|
try:
|
|
if message.author.bot:
|
|
return await ctx.respond("Quotes are for real users, not bots.", ephemeral=True)
|
|
quoter_friendly = ctx.author.display_name
|
|
quoted_message_id = message.id
|
|
quoted_channel_friendly = f'#{ctx.channel.name}'
|
|
quoted_channel_id = ctx.channel.id
|
|
message_content = message.content
|
|
message_author_friendly = message.author.display_name
|
|
message_author_id = message.author.id
|
|
message_time = int(message.created_at.timestamp())
|
|
message_escaped = discord.utils.escape_mentions(discord.utils.escape_markdown(message_content)).strip()
|
|
|
|
if len(message_escaped) < 3:
|
|
return await ctx.respond("**Error**: Message (text content) is not long enough to quote.", ephemeral=True)
|
|
|
|
if len(message_escaped) > 512:
|
|
return await ctx.respond("**Error**: Message (text content) is too long to quote.", ephemeral=True)
|
|
|
|
result = await self.db.add_quote(message_id=quoted_message_id,
|
|
channel_id=quoted_channel_id,
|
|
quoted_member_id=message_author_id,
|
|
message_time=message_time,
|
|
quoter_friendly=quoter_friendly,
|
|
quoted_friendly=message_author_friendly,
|
|
channel_friendly=quoted_channel_friendly,
|
|
message_content=message_content)
|
|
if not result:
|
|
return await ctx.respond("Failed!", ephemeral=True)
|
|
else:
|
|
return await ctx.respond("OK!", ephemeral=True)
|
|
except:
|
|
await self.bot.get_channel(hp_chanid).send(traceback.format_exc())
|
|
|
|
|
|
@bridge.bridge_command(aliases=['rand'])
|
|
@is_homeserver() # pylint: disable=too-many-function-args
|
|
async def randquote(self, ctx):
|
|
"""Get a random quote"""
|
|
try:
|
|
random_quote = await self.db.fetch_quote(random=True)
|
|
if random_quote.get('err'):
|
|
return await ctx.respond("Failed to get a quote")
|
|
|
|
quote_id = random_quote.get('id')
|
|
quoted_friendly = random_quote.get('quoted_user_display', 'Unknown')
|
|
adder_friendly = random_quote.get('added_by_friendly', 'Unknown')
|
|
message_time = datetime.datetime.fromtimestamp(random_quote.get('quoted_message_time'))
|
|
message_channel = random_quote.get('quoted_channel_display')
|
|
quote_added_at = datetime.datetime.fromtimestamp(random_quote.get('added_at'))
|
|
quote_content = random_quote.get('quoted_message')
|
|
|
|
embed = discord.Embed(
|
|
colour=discord.Colour.orange(),
|
|
title=f"Quote #{quote_id}",
|
|
)
|
|
embed.description = f"**{quoted_friendly}:** {quote_content}"
|
|
embed.add_field(name="Original Message Time", value=message_time)
|
|
embed.add_field(name="Channel", value=message_channel)
|
|
embed.add_field(name="Quote ID", value=quote_id)
|
|
embed.footer = discord.EmbedFooter(text=f"Added by {adder_friendly} {quote_added_at}")
|
|
|
|
return await ctx.respond(embed=embed)
|
|
except:
|
|
error = await ctx.respond(traceback.format_exc())
|
|
await asyncio.sleep(10)
|
|
await error.delete()
|
|
|
|
@bridge.bridge_command(aliases=['qg'])
|
|
@is_homeserver() # pylint: disable=too-many-function-args
|
|
async def quoteget(self, ctx, quoteid):
|
|
"""Get a specific quote by ID"""
|
|
try:
|
|
if not str(quoteid).strip().isnumeric():
|
|
return await ctx.respond("**Error**: Quote ID must be numeric.")
|
|
fetched_quote = await self.db.fetch_quote(quoteid=quoteid)
|
|
if fetched_quote.get('err'):
|
|
return await ctx.respond("**Error**: Quote not found")
|
|
|
|
quote_id = fetched_quote.get('id')
|
|
quoted_friendly = fetched_quote.get('quoted_user_display', 'Unknown')
|
|
adder_friendly = fetched_quote.get('added_by_friendly', 'Unknown')
|
|
message_time = datetime.datetime.fromtimestamp(fetched_quote.get('quoted_message_time'))
|
|
message_channel = fetched_quote.get('quoted_channel_display')
|
|
quote_added_at = datetime.datetime.fromtimestamp(fetched_quote.get('added_at'))
|
|
quote_content = fetched_quote.get('quoted_message')
|
|
|
|
embed = discord.Embed(
|
|
colour=discord.Colour.orange(),
|
|
title=f"Quote #{quote_id}",
|
|
)
|
|
embed.description = f"**{quoted_friendly}:** {quote_content}"
|
|
embed.add_field(name="Original Message Time", value=message_time)
|
|
embed.add_field(name="Channel", value=message_channel)
|
|
embed.add_field(name="Quote ID", value=quote_id)
|
|
embed.footer = discord.EmbedFooter(text=f"Added by {adder_friendly} {quote_added_at}")
|
|
|
|
return await ctx.respond(embed=embed)
|
|
except:
|
|
error = await ctx.respond(traceback.format_exc())
|
|
await asyncio.sleep(10)
|
|
await error.delete()
|
|
|
|
@bridge.bridge_command(aliases=['qs'])
|
|
@is_homeserver() # pylint: disable=too-many-function-args
|
|
async def quotesearch(self, ctx, *, content: str):
|
|
"""Search for a quote (by content)"""
|
|
try:
|
|
found_quotes = await self.db.fetch_quote(content=content)
|
|
if isinstance(found_quotes, dict) and found_quotes.get('err'):
|
|
return await ctx.respond(f"Quote search failed: {found_quotes.get('err')}")
|
|
|
|
embeds = []
|
|
|
|
for quote in found_quotes:
|
|
quote_id = quote.get('id')
|
|
quoted_friendly = quote.get('quoted_user_display', 'Unknown')
|
|
adder_friendly = quote.get('added_by_friendly', 'Unknown')
|
|
message_time = datetime.datetime.fromtimestamp(quote.get('quoted_message_time'))
|
|
message_channel = quote.get('quoted_channel_display')
|
|
quote_added_at = datetime.datetime.fromtimestamp(quote.get('added_at'))
|
|
quote_content = quote.get('quoted_message')
|
|
|
|
# await ctx.respond(f"**{quoted_friendly}**: {quote}")ed_friendly = quote.get('quoted_user_display', 'Unknown')
|
|
adder_friendly = quote.get('added_by_friendly', 'Unknown')
|
|
message_time = datetime.datetime.fromtimestamp(quote.get('quoted_message_time'))
|
|
message_channel = quote.get('quoted_channel_display')
|
|
quote_added_at = datetime.datetime.fromtimestamp(quote.get('added_at'))
|
|
quote = quote.get('quoted_message')
|
|
|
|
# await ctx.respond(f"**{quoted_friendly}**: {quote}")
|
|
embed = discord.Embed(
|
|
colour=discord.Colour.orange(),
|
|
title=f"Quote #{quote_id}",
|
|
)
|
|
embed.description = f"**{quoted_friendly}:** {quote_content}"
|
|
embed.add_field(name="Original Message Time", value=message_time)
|
|
embed.add_field(name="Channel", value=message_channel)
|
|
embed.add_field(name="Quote ID", value=quote_id)
|
|
embed.footer = discord.EmbedFooter(text=f"Added by {adder_friendly} {quote_added_at}")
|
|
embeds.append(embed)
|
|
|
|
return await ctx.respond(embeds=embeds)
|
|
except Exception as e:
|
|
await ctx.respond(f"Error: {type(e).__name__} - {str(e)}")
|
|
|
|
@bridge.bridge_command(aliases=['nq'])
|
|
@is_homeserver() # pylint: disable=too-many-function-args
|
|
async def nquotes(self, ctx):
|
|
"""Get # of quotes stored"""
|
|
try:
|
|
quote_count = await self.db.get_quote_count()
|
|
if not quote_count:
|
|
return await ctx.respond("**Error**: No quotes found!")
|
|
return await ctx.respond(f"I currently have **{quote_count}** quotes stored.")
|
|
|
|
except Exception as e:
|
|
await ctx.respond(f"Error: {type(e).__name__} - {str(e)}")
|
|
|
|
@bridge.bridge_command(aliases=['qr'])
|
|
@commands.is_owner()
|
|
@is_homeserver() # pylint: disable=too-many-function-args
|
|
async def quoteremove(self, ctx, quoteid):
|
|
"""Remove a quote (by id)
|
|
Owner only"""
|
|
try:
|
|
if not str(quoteid).strip().isnumeric():
|
|
return await ctx.respond("**Error**: Quote ID must be numeric.")
|
|
quoteid = int(quoteid)
|
|
remove_quote = await self.db.remove_quote(quoteid)
|
|
if not remove_quote:
|
|
return await ctx.respond("**Error**: Failed!", ephemeral=True)
|
|
return await ctx.respond("Removed!", ephemeral=True)
|
|
|
|
except Exception as e:
|
|
await ctx.respond(f"Error: {type(e).__name__} - {str(e)}")
|
|
|
|
def setup(bot):
|
|
"""Run on Cog Load"""
|
|
bot.add_cog(Quote(bot))
|