discord-havoc/util/misc_util.py

453 lines
18 KiB
Python
Raw Normal View History

2025-02-13 14:51:35 -05:00
import os
import logging
import traceback
import random
import datetime
import pytz
2025-03-12 09:37:44 -04:00
from typing import Optional, LiteralString, Union
2025-02-13 14:51:35 -05:00
import regex
import aiosqlite as sqlite3
from aiohttp import ClientSession, ClientTimeout
from bohancompliment import ComplimentGenerator
from discord import Embed
class Util:
"""Misc Utility"""
def __init__(self) -> None:
self.URL_URBANDICTIONARY: str = "http://api.urbandictionary.com/v0/define"
self.URL_INSULTAPI: str = "https://insult.mattbas.org/api/insult"
self.COMPLIMENT_GENERATOR = ComplimentGenerator()
2025-02-15 13:57:47 -05:00
self.dbs: dict[str, str|LiteralString] = {
2025-02-13 14:51:35 -05:00
'whisky': os.path.join("/usr/local/share",
"sqlite_dbs", "whiskey.db"),
'drinks': os.path.join("/usr/local/share",
"sqlite_dbs", "cocktails.db"),
'strains': os.path.join("/usr/local/share",
"sqlite_dbs", "strains.db"),
'qajoke': os.path.join("/usr/local/share",
"sqlite_dbs", "qajoke.db"),
'rjokes': os.path.join("/usr/local/share",
"sqlite_dbs", "rjokes.db"),
'randmsg': os.path.join("/usr/local/share",
"sqlite_dbs", "randmsg.db"),
'stats': os.path.join("/usr/local/share",
"sqlite_dbs", "havoc_stats.db"),
'cookies': os.path.join("/usr/local/share",
"sqlite_dbs", "cookies.db"),
}
self.COFFEES: list = ['a cup of french-pressed coffee', 'a cup of cold brew', 'a cup of flash brew',
'a cup of Turkish coffee', 'a cup of Moka', 'an espresso',
'a cup of Nescafe coffee',
'an iced coffee', 'a Frappé', 'a freddo cappuccino',
'a cup of Chock full o\'Nuts', 'a cup of Folgers', 'a cup of Lavazza',
'a cup of Maxwell House', 'a cup of Moccona', 'a cup of Mr. Brown Coffee',
'a cup of affogato al caffè',
'a cup of Caffè Medici', 'a cup of Café Touba',
'a double-double', 'an indian filter coffee', 'a cup of pocillo',
'a cup of caffè americano', 'a cup of caffè lungo', 'a latte', 'a manilo',
'a flat white', 'a cup of café cubano', 'a cup of caffè crema',
'a cup of cafe zorro', 'an espresso roberto', 'an espresso romano',
'an espresso sara', 'a guillermo', 'a ristretto', 'a cup of melya',
'a cup of caffè marocchino', 'a cup of café miel', 'a cup of café de olla',
2025-03-16 07:54:41 -04:00
'a Mazagran', 'a Palazzo', 'an ice shot', 'a macchiato',
'a cortado', 'a red eye', 'a cappuccino',
'a mocha', 'a café au lait', 'a bicerin',
'a caffè corretto', 'a ca phe trung', 'a café bombón', 'a Vienna coffee',
'a flat black', 'a lungo', 'a doppio', 'a ristretto bianco', 'a piccolo latte',
'a gibraltar', 'a breve', 'a café con leche', 'a su café', 'a café del tiempo',
'a java chip frappuccino', 'a pumpkin spice latte', 'a caramel macchiato',
'a white chocolate mocha', 'a hazelnut coffee', 'a toffee nut latte',
'a peppermint mocha', 'a cinnamon dolce latte', 'a coconut milk latte',
'an almond milk cappuccino', 'an oat milk latte', 'a caramel frappuccino',
'a chocolate frappuccino', 'a butter pecan coffee', 'a maple pecan latte',
'a sea salt caramel mocha', 'a nitro cold brew', 'a pumpkin cold brew',
'a honey almond flat white', 'a sweet cream cold brew', 'a matcha latte',
2025-03-24 09:08:29 -04:00
'a golden latte', 'a turmeric latte', 'a beetroot latte', 'a kopi luwak']
2025-02-13 14:51:35 -05:00
self.LAST_5_COFFEES: list = []
def tdTuple(self, td:datetime.timedelta) -> tuple:
"""
Create TimeDelta Tuple
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
td (datetime.timedelta)
Returns:
tuple
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
def _t(t, n):
if t < n:
return (t, 0)
v = t//n
return (t - (v * n), v)
(s, h) = _t(td.seconds, 3600)
(s, m) = _t(s, 60)
(mics, mils) = _t(td.microseconds, 1000)
return (td.days, h, m, s, mics, mils)
2025-02-15 13:57:47 -05:00
async def get_counter(self, counter: Optional[str] = None) -> Optional[dict]:
2025-02-13 14:51:35 -05:00
"""
Get Counter
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
counter (Optional[str])
Returns:
2025-02-15 13:57:47 -05:00
Optional[dict]
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
2025-02-15 13:57:47 -05:00
stats_db: str|LiteralString = self.dbs.get('stats', '')
if not stats_db:
return None
async with sqlite3.connect(stats_db,
2025-02-13 14:51:35 -05:00
timeout=3) as db_conn:
2025-02-15 13:57:47 -05:00
db_conn.row_factory = sqlite3.Row
2025-02-13 14:51:35 -05:00
query: str = "SELECT ? FROM stats LIMIT 1"
if not counter:
2025-02-15 13:57:47 -05:00
query = "SELECT * FROM stats LIMIT 1"
2025-02-13 14:51:35 -05:00
async with await db_conn.execute(query, (counter,) if counter else None) as db_cursor:
2025-02-15 13:57:47 -05:00
result = await db_cursor.fetchone()
2025-02-13 14:51:35 -05:00
return result
2025-02-15 13:57:47 -05:00
async def get_stats_embed(self) -> Optional[Embed]:
2025-02-13 14:51:35 -05:00
"""
Get Stats Embed
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[Embed]
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
2025-02-15 13:57:47 -05:00
counters: Optional[dict] = await self.get_counter()
if not counters:
return None
2025-02-13 14:51:35 -05:00
embed: Embed = Embed(title="Stats")
counter_message: str = ""
counters_sorted: dict = dict(sorted(counters.items(),
key=lambda item: item[1], reverse=True))
for counter, value in counters_sorted.items():
2025-02-15 13:57:47 -05:00
counter = regex.sub(r'_', ' ',
2025-02-13 14:51:35 -05:00
counter.strip()).title()
counter_message += f"- {value} {counter}\n"
embed.description = counter_message.strip()
return embed
async def increment_counter(self, counter: str) -> bool:
"""
Increment Counter
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
counter (str)
Returns:
bool
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
2025-02-15 13:57:47 -05:00
stats_db: str|LiteralString = self.dbs.get('stats', '')
if not stats_db:
return False
async with sqlite3.connect(stats_db,
2025-02-13 14:51:35 -05:00
timeout=3) as db_conn:
async with await db_conn.execute(f"UPDATE stats SET {counter} = {counter} + 1") as db_cursor:
if db_cursor.rowcount < 0:
logging.critical("[karma::increment_counter] Fail! %s", db_cursor.rowcount)
return False
await db_conn.commit()
return True
2025-02-15 13:57:47 -05:00
async def get_ud_def(self, term: str) -> tuple[str, str]:
2025-02-13 14:51:35 -05:00
"""
Get Definition from UD
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
2025-02-15 13:57:47 -05:00
term (str)
2025-02-13 14:51:35 -05:00
Returns:
tuple[str, str]
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
try:
async with ClientSession() as session:
async with await session.get(self.URL_URBANDICTIONARY,
params={
"term": term,
},
headers = {
'content-type': 'application/json; charset=utf-8',
}, timeout=ClientTimeout(connect=5, sock_read=5)) as request:
logging.debug("UD returned: %s",
await request.text())
data: dict = await request.json()
if "list" in data:
definitions: list[dict] = data["list"]
if definitions:
definition: dict = definitions[0]
definition_word: str = definition.get("word", "N/A")
definition_text: str = regex.sub(r'(\r|\n|\r\n)', ' ', definition["definition"].strip())
return (definition_word, definition_text) # Tuple: Returned word, returned definition
else:
return (term, "Not found!")
else:
return (term, "Error retrieving data from Urban Dictionary")
except Exception as e:
traceback.print_exc()
return (term, f"ERR: {str(e)}")
async def get_insult(self, recipient: str) -> str:
"""
Get Insult
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
recipient (str)
Returns:
str
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
async with ClientSession() as session:
async with await session.get(f"{self.URL_INSULTAPI}?who={recipient}") as request:
request.raise_for_status()
return await request.text()
async def get_compliment(self, subject: str,
2025-03-24 15:14:38 -04:00
language: Optional[str] = 'en') -> str:
2025-02-13 14:51:35 -05:00
"""
Get Compliment
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
subject (str)
2025-03-24 15:14:38 -04:00
language (Optional[str]) (default: 'en')
2025-02-13 14:51:35 -05:00
Returns:
str
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
return self.COMPLIMENT_GENERATOR.compliment_in_language(subject, language)
2025-02-15 13:57:47 -05:00
async def get_whisky(self) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get Whisky
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
whisky_db: str|LiteralString = self.dbs.get('whisky', '')
if not whisky_db:
return None
async with sqlite3.connect(database=whisky_db,
timeout=2) as db_conn:
db_query: str = "SELECT name, category, description FROM whiskeys ORDER BY random() LIMIT 1"
async with await db_conn.execute(db_query) as db_cursor:
db_result: Optional[Union[sqlite3.Row, tuple]] = await db_cursor.fetchone()
if not db_result:
return None
(name, category, description) = db_result
name = regex.sub(r'(^\p{White_Space}|\r|\n)', '',
2025-02-13 14:51:35 -05:00
regex.sub(r'\p{White_Space}{2,}', ' ',
2025-02-15 13:57:47 -05:00
name.strip()))
category = regex.sub(r'(^\p{White_Space}|\r|\n)', '',
regex.sub(r'\p{White_Space}{2,}', ' ',
category.strip()))
description = regex.sub(r'(^\p{White_Space}|\r|\n)', '',
regex.sub(r'\p{White_Space}{2,}', ' ',
description.strip()))
return (name, category, description)
2025-02-13 14:51:35 -05:00
2025-02-15 13:57:47 -05:00
async def get_drink(self) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get Drink
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
drinks_db: str|LiteralString = self.dbs.get('drinks', '')
if not drinks_db:
return None
async with sqlite3.connect(database=drinks_db,
timeout=2) as db_conn:
db_query: str = "SELECT name, ingredients FROM cocktails ORDER BY random() LIMIT 1"
async with await db_conn.execute(db_query) as db_cursor:
db_result: tuple = await db_cursor.fetchone()
(name, ingredients) = db_result
name = regex.sub(r'(^\p{White_Space}|\r|\n)', '', regex.sub(r'\p{White_Space}{2,}', ' ', name.strip()))
ingredients = regex.sub(r'(^\p{White_Space}|\r|\n)', '', regex.sub(r'\p{White_Space}{2,}', ' ', ingredients.strip()))
ingredients = regex.sub(r'\*', '\u2731', ingredients.strip())
return (name, ingredients)
2025-02-13 14:51:35 -05:00
2025-02-15 13:57:47 -05:00
async def get_strain(self, strain: Optional[str] = None) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get Strain
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Args:
strain (Optional[str])
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
strains_db: str|LiteralString = self.dbs.get('strains', '')
if not strains_db:
return None
async with sqlite3.connect(database=strains_db,
timeout=2) as db_conn:
db_params: Optional[tuple] = None
if not strain:
db_query: str = "SELECT name, description FROM strains_w_desc ORDER BY random() LIMIT 1"
else:
db_query = "SELECT name, description FROM strains_w_desc WHERE name LIKE ?"
db_params = (f"%{strain.strip()}%",)
async with await db_conn.execute(db_query, db_params) as db_cursor:
db_result: Optional[tuple] = await db_cursor.fetchone()
return db_result
2025-02-13 14:51:35 -05:00
2025-02-15 13:57:47 -05:00
async def get_qajoke(self) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get QA Joke
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
qajoke_db: str|LiteralString = self.dbs.get('qajoke', '')
if not qajoke_db:
return None
async with sqlite3.connect(database=qajoke_db,
timeout=2) as db_conn:
db_query: str = "SELECT question, answer FROM jokes ORDER BY RANDOM() LIMIT 1"
async with await db_conn.execute(db_query) as cursor:
2025-02-13 14:51:35 -05:00
(question, answer) = await cursor.fetchone()
return (question, answer)
return None
2025-02-15 13:57:47 -05:00
async def get_rjoke(self) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get r/joke Joke
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
rjokes_db: str|LiteralString = self.dbs.get('rjokes', '')
if not rjokes_db:
return None
async with sqlite3.connect(database=rjokes_db, timeout=2) as db_conn:
db_query: str = "SELECT title, body, score FROM jokes WHERE score >= 100 ORDER BY RANDOM() LIMIT 1'"
async with await db_conn.execute(db_query) as cursor:
2025-02-13 14:51:35 -05:00
(title, body, score) = await cursor.fetchone()
return (title, body, score)
return None
async def get_random_fact(self) -> str:
"""
Get Random Fact
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
str
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
try:
facts_api_url: str = "https://uselessfacts.jsph.pl/api/v2/facts/random"
facts_backup_url: str = "https://cnichols1734.pythonanywhere.com/facts/random"
async with ClientSession() as client:
try:
async with await client.get(facts_api_url,
timeout=ClientTimeout(connect=5, sock_read=5)) as request:
2025-02-15 13:57:47 -05:00
_json: dict = await request.json()
fact: str = _json.get('text', None)
2025-02-13 14:51:35 -05:00
if not fact:
raise BaseException("RandFact Src 1 Failed")
return fact
except:
async with await client.get(facts_backup_url,
timeout=ClientTimeout(connect=5, sock_read=5)) as request:
2025-02-15 13:57:47 -05:00
_json = await request.json()
fact = _json.get('fact', None)
2025-02-13 14:51:35 -05:00
return fact
except Exception as e:
traceback.print_exc()
return f"Failed to get a random fact :( [{str(e)}]"
2025-02-15 13:57:47 -05:00
async def get_cookie(self) -> Optional[dict]:
2025-02-13 14:51:35 -05:00
"""
Get Cookie
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[dict]
2025-02-16 20:07:02 -05:00
2025-02-15 13:57:47 -05:00
"""
cookies_db = self.dbs.get('cookies', '')
if not cookies_db:
return None
async with sqlite3.connect(cookies_db,
timeout=2) as db_conn:
db_query: str = "SELECT name, origin, image_url FROM cookies ORDER BY RANDOM() LIMIT 1"
async with await db_conn.execute(db_query) as db_cursor:
2025-02-13 14:51:35 -05:00
(name, origin, image_url) = await db_cursor.fetchone()
return {
'name': name,
'origin': origin,
'image_url': image_url
}
2025-02-24 06:21:56 -05:00
def get_coffee(self,
recipient_allergic: Optional[bool] = False) -> Optional[str]:
2025-02-13 14:51:35 -05:00
"""
Get Coffee
2025-02-16 20:07:02 -05:00
2025-02-24 06:21:56 -05:00
Args:
recipient_allergic (bool): Is the recipient allergic? (so we know when to keep our nuts out of it)
2025-02-13 14:51:35 -05:00
Returns:
str
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
try:
randomCoffee: str = random.choice(self.COFFEES)
2025-02-24 06:21:56 -05:00
if self.LAST_5_COFFEES and randomCoffee in self.LAST_5_COFFEES\
or (recipient_allergic and "nut" in randomCoffee.lower()):
2025-02-13 14:51:35 -05:00
return self.get_coffee() # Recurse
if len(self.LAST_5_COFFEES) >= 5:
self.LAST_5_COFFEES.pop() # Store no more than 5 of the last served coffees
self.LAST_5_COFFEES.append(randomCoffee)
return randomCoffee
except:
traceback.print_exc()
2025-02-15 13:57:47 -05:00
return None
2025-02-13 14:51:35 -05:00
2025-02-15 13:57:47 -05:00
def get_days_to_xmas(self) -> Optional[tuple]:
2025-02-13 14:51:35 -05:00
"""
Get # of Days until Xmas
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[tuple]
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
2025-02-15 13:57:47 -05:00
today: datetime.datetime = datetime.datetime.now(tz=pytz.UTC)
xmas: datetime.datetime = datetime.datetime(
2025-02-13 14:51:35 -05:00
year=today.year,
month=12,
day=25,
tzinfo=pytz.UTC,
)
2025-03-12 09:37:44 -04:00
td: datetime.timedelta = (xmas - today)
2025-02-13 14:51:35 -05:00
days, hours, minutes, seconds, us, ms = self.tdTuple(td)
return (days, hours, minutes, seconds, ms, us)
2025-02-15 13:57:47 -05:00
async def get_randmsg(self) -> Optional[str]:
2025-02-13 14:51:35 -05:00
"""
Get Random Message from randmsg.db
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
Returns:
2025-02-15 13:57:47 -05:00
Optional[str]
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
"""
2025-02-15 13:57:47 -05:00
randmsg_db: str|LiteralString = self.dbs.get('randmsg', '')
if not randmsg_db:
return None
2025-02-13 14:51:35 -05:00
async with sqlite3.connect(database=randmsg_db,
timeout=2) as db_conn:
2025-02-15 13:57:47 -05:00
db_query: str = "SELECT msg FROM msgs ORDER BY RANDOM() LIMIT 1"
async with await db_conn.execute(db_query) as db_cursor:
2025-02-13 14:51:35 -05:00
(result,) = await db_cursor.fetchone()
return result