The bot still stores any number of explanations for a given word, and prints all of them when using !wtf detail <word>. This means that it is still vulnerable to some types of attacks. Maybe I'll fix that sometime in the future.
183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
import asyncio
|
|
import logging
|
|
import re
|
|
|
|
import yaboli
|
|
|
|
if __name__ == "__main__":
|
|
from wtfdb import WtfDB
|
|
else:
|
|
from .wtfdb import WtfDB
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class Wtf(yaboli.Module):
|
|
MAX_TERMS = 5
|
|
MAX_TERM_LENGTH = 1024
|
|
MAX_EXPLANATIONS = 15
|
|
MAX_EXPLANATION_LENGTH = 1024
|
|
|
|
DESCRIPTION = ("a database of explanations for words, acronyms and"
|
|
" initialisms")
|
|
HELP_GENERAL = DESCRIPTION
|
|
HELP_SPECIFIC = [
|
|
"'wtf' is a database of explanations for words, acronyms and"
|
|
" initialisms. It is inspired by the linux wtf program and uses"
|
|
" its acronyms, in addition to ones set by users.",
|
|
"",
|
|
"!wtf is <term> - look up a term (also responds to 'wtf is')",
|
|
"!wtf add <term> <explanation> - add a new explanation",
|
|
"!wtf detail <term> - shows more info about the term's explanations",
|
|
"!wtf delete <id> - delete explanation with corresponding id (look"
|
|
" up the id using !wtf detail)",
|
|
"!wtf replace <id> <explanation> - a shortcut for deleting and"
|
|
" re-adding with a different explanation",
|
|
"",
|
|
"Uses most acronyms of arch's community/wtf package.",
|
|
"Made by @Garmy using https://github.com/Garmelon/yaboli.",
|
|
]
|
|
|
|
SECTION = "wtf"
|
|
|
|
RE_IS = re.compile(r"\s*is\s+(.*)")
|
|
RE_ADD = re.compile(r"\s*add\s+(\S+)\s+(.+)")
|
|
RE_DETAIL = re.compile(r"\s*detail\s+(.*)")
|
|
RE_DELETE = re.compile(r"\s*delete\s+(\d+)\s*")
|
|
RE_REPLACE = re.compile(r"\s*replace\s+(\d+)\s+(.+)")
|
|
|
|
RE_WTF_IS = re.compile(r"\s*wtf\s+is\s+(.*)")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
dbfile = self.config[self.SECTION]["db"]
|
|
self.db = WtfDB(dbfile)
|
|
|
|
if self.standalone:
|
|
self.register_botrulez(kill=True, restart=True)
|
|
|
|
self.register_general("wtf", self.cmd_wtf)
|
|
|
|
def _format_explanations(self, explanations, detail=False):
|
|
# Id, Term, Explanation, Author
|
|
if detail:
|
|
explanations = [f"{i}: {t} — {e} (by {a})" for i, t, e, a in explanations]
|
|
else:
|
|
explanations = [f"{t} — {e}" for _, t, e, _ in explanations]
|
|
|
|
if len(explanations) > self.MAX_EXPLANATIONS:
|
|
message = ("Some explanations were omitted because this bot only"
|
|
f" displays {self.MAX_EXPLANATIONS} explanations per"
|
|
" term.")
|
|
explanations = explanations[:-1] + [message]
|
|
|
|
return explanations
|
|
|
|
async def _find_explanations(self, terms, detail=False):
|
|
lines = []
|
|
for term in terms:
|
|
explanations = await self.db.find_full(term, self.MAX_EXPLANATIONS + 1)
|
|
if explanations:
|
|
lines.extend(self._format_explanations(explanations, detail=detail))
|
|
else:
|
|
lines.append(f"{term!r} not found.")
|
|
return lines
|
|
|
|
async def send_explanations(self, message, termstr):
|
|
terms = [term for term in termstr.split() if term]
|
|
terms = terms[:self.MAX_TERMS]
|
|
|
|
if not terms: return
|
|
|
|
if max(map(len, terms)) > self.MAX_TERM_LENGTH:
|
|
await message.reply(("A term can be at most"
|
|
f" {self.MAX_TERM_LENGTH} characters long."))
|
|
return
|
|
|
|
lines = await self._find_explanations(terms)
|
|
await message.reply("\n".join(lines))
|
|
return
|
|
|
|
async def on_send(self, room, message):
|
|
await super().on_send(room, message)
|
|
|
|
match = self.RE_WTF_IS.fullmatch(message.content)
|
|
if match:
|
|
terms = match.group(1)
|
|
await self.send_explanations(message, terms)
|
|
|
|
async def cmd_wtf(self, room, message, args):
|
|
match_is = self.RE_IS.fullmatch(args.raw)
|
|
if match_is:
|
|
terms = match_is.group(1)
|
|
await self.send_explanations(message, terms)
|
|
|
|
match_add = self.RE_ADD.fullmatch(args.raw)
|
|
if match_add:
|
|
term = match_add.group(1)
|
|
explanation = match_add.group(2).strip()
|
|
|
|
if len(term) > self.MAX_TERM_LENGTH:
|
|
await message.reply(("A term can be at most"
|
|
f" {self.MAX_TERM_LENGTH} characters long."))
|
|
return
|
|
|
|
if len(explanation) > self.MAX_EXPLANATION_LENGTH:
|
|
await message.reply(("An explanation can be at most"
|
|
f" {self.MAX_EXPLANATION_LENGTH} characters long."))
|
|
return
|
|
|
|
await self.db.add(term, explanation, message.sender.nick)
|
|
logger.info((f"{message.sender.atmention} added explanation:"
|
|
f" {term} - {explanation}"))
|
|
await message.reply(f"Added explanation: {term} — {explanation}")
|
|
return
|
|
|
|
match_detail = self.RE_DETAIL.fullmatch(args.raw)
|
|
if match_detail:
|
|
terms = match_detail.group(1)
|
|
terms = [term for term in terms.split() if term]
|
|
if not terms: return
|
|
lines = await self._find_explanations(terms, detail=True)
|
|
await message.reply("\n".join(lines))
|
|
return
|
|
|
|
match_delete = self.RE_DELETE.fullmatch(args.raw)
|
|
if match_delete:
|
|
aid = match_delete.group(1)
|
|
await self.db.delete(aid)
|
|
logger.info((f"{message.sender.atmention} deleted explanation with"
|
|
" id {aid}"))
|
|
await message.reply(f"Deleted.")
|
|
return
|
|
|
|
match_replace = self.RE_REPLACE.fullmatch(args.raw)
|
|
if match_replace:
|
|
aid = match_replace.group(1)
|
|
explanation = match_replace.group(2).strip()
|
|
|
|
if len(explanation) > self.MAX_EXPLANATION_LENGTH:
|
|
await message.reply(("An explanation can be at most"
|
|
f" {self.MAX_EXPLANATION_LENGTH} characters long."))
|
|
return
|
|
|
|
term = await self.db.get(aid)
|
|
if term is None:
|
|
await message.reply(f"No explanation with id {aid} exists.")
|
|
else:
|
|
await self.db.delete(aid)
|
|
logger.info((f"{message.sender.atmention} deleted explanation"
|
|
f" with id {aid}"))
|
|
await self.db.add(term, explanation, message.sender.nick)
|
|
logger.info((f"{message.sender.atmention} added explanation:"
|
|
f" {term} - {explanation}"))
|
|
await message.reply(f"Changed explanation: {term} — {explanation}")
|
|
return
|
|
|
|
# else...
|
|
await message.reply("Incorrect command, see the !help for details.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
yaboli.enable_logging(level=logging.DEBUG)
|
|
yaboli.run(Wtf)
|