Update to yaboli 1.1.2

This commit is contained in:
Joscha 2019-04-14 21:49:30 +00:00
parent 45e5c006e7
commit bf5c619da0
5 changed files with 286 additions and 292 deletions

14
.gitignore vendored
View file

@ -1,3 +1,13 @@
**/__pycache__ # python stuff
*.cookie __pycache__/
# venv stuff
bin/
include/
lib/
lib64
pyvenv.cfg
# config files
*.conf *.conf
cookie_jar

6
bot.conf.default Normal file
View file

@ -0,0 +1,6 @@
[general]
nick = tom
cookiefile = bot.cookie
[rooms]
test

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
yaboli >=1.1.2, <1.2.0

View file

@ -1,9 +0,0 @@
[general]
nick = tom
cookiefile = tom.cookie
[rooms]
# Format:
# room
# room=password
test

548
tom.py
View file

@ -1,337 +1,323 @@
import asyncio
import configparser
import logging import logging
import re import re
import yaboli import yaboli
from yaboli.utils import *
ELEMENTS = [ ELEMENTS = [
("H", "Hydrogen"), ("H", "Hydrogen"),
("He", "Helium"), ("He", "Helium"),
("Li", "Lithium"), ("Li", "Lithium"),
("Be", "Beryllium"), ("Be", "Beryllium"),
("B", "Boron"), ("B", "Boron"),
("C", "Carbon"), ("C", "Carbon"),
("N", "Nitrogen"), ("N", "Nitrogen"),
("O", "Oxygen"), ("O", "Oxygen"),
("F", "Fluorine"), ("F", "Fluorine"),
("Ne", "Neon"), ("Ne", "Neon"),
("Na", "Sodium"), ("Na", "Sodium"),
("Mg", "Magnesium"), ("Mg", "Magnesium"),
("Al", "Aluminium"), ("Al", "Aluminium"),
("Si", "Silicon"), ("Si", "Silicon"),
("P", "Phosphorus"), ("P", "Phosphorus"),
("S", "Sulfur"), ("S", "Sulfur"),
("Cl", "Chlorine"), ("Cl", "Chlorine"),
("Ar", "Argon"), ("Ar", "Argon"),
("K", "Potassium"), ("K", "Potassium"),
("Ca", "Calcium"), ("Ca", "Calcium"),
("Sc", "Scandium"), ("Sc", "Scandium"),
("Ti", "Titanium"), ("Ti", "Titanium"),
("V", "Vanadium"), ("V", "Vanadium"),
("Cr", "Chromium"), ("Cr", "Chromium"),
("Mn", "Manganese"), ("Mn", "Manganese"),
("Fe", "Iron"), ("Fe", "Iron"),
("Co", "Cobalt"), ("Co", "Cobalt"),
("Ni", "Nickel"), ("Ni", "Nickel"),
("Cu", "Copper"), ("Cu", "Copper"),
("Zn", "Zinc"), ("Zn", "Zinc"),
("Ga", "Gallium"), ("Ga", "Gallium"),
("Ge", "Germanium"), ("Ge", "Germanium"),
("As", "Arsenic"), ("As", "Arsenic"),
("Se", "Selenium"), ("Se", "Selenium"),
("Br", "Bromine"), ("Br", "Bromine"),
("Kr", "Krypton"), ("Kr", "Krypton"),
("Rb", "Rubidium"), ("Rb", "Rubidium"),
("Sr", "Strontium"), ("Sr", "Strontium"),
("Y", "Yttrium"), ("Y", "Yttrium"),
("Zr", "Zirconium"), ("Zr", "Zirconium"),
("Nb", "Niobium"), ("Nb", "Niobium"),
("Mo", "Molybdenum"), ("Mo", "Molybdenum"),
("Tc", "Technetium"), ("Tc", "Technetium"),
("Ru", "Ruthenium"), ("Ru", "Ruthenium"),
("Rh", "Rhodium"), ("Rh", "Rhodium"),
("Pd", "Palladium"), ("Pd", "Palladium"),
("Ag", "Silver"), ("Ag", "Silver"),
("Cd", "Cadmium"), ("Cd", "Cadmium"),
("In", "Indium"), ("In", "Indium"),
("Sn", "Tin"), ("Sn", "Tin"),
("Sb", "Antimony"), ("Sb", "Antimony"),
("Te", "Tellurium"), ("Te", "Tellurium"),
("I", "Iodine"), ("I", "Iodine"),
("Xe", "Xenon"), ("Xe", "Xenon"),
("Cs", "Caesium"), ("Cs", "Caesium"),
("Ba", "Barium"), ("Ba", "Barium"),
("La", "Lanthanum"), ("La", "Lanthanum"),
("Ce", "Cerium"), ("Ce", "Cerium"),
("Pr", "Praseodymium"), ("Pr", "Praseodymium"),
("Nd", "Neodymium"), ("Nd", "Neodymium"),
("Pm", "Promethium"), ("Pm", "Promethium"),
("Sm", "Samarium"), ("Sm", "Samarium"),
("Eu", "Europium"), ("Eu", "Europium"),
("Gd", "Gadolinium"), ("Gd", "Gadolinium"),
("Tb", "Terbium"), ("Tb", "Terbium"),
("Dy", "Dysprosium"), ("Dy", "Dysprosium"),
("Ho", "Holmium"), ("Ho", "Holmium"),
("Er", "Erbium"), ("Er", "Erbium"),
("Tm", "Thulium"), ("Tm", "Thulium"),
("Yb", "Ytterbium"), ("Yb", "Ytterbium"),
("Lu", "Lutetium"), ("Lu", "Lutetium"),
("Hf", "Hafnium"), ("Hf", "Hafnium"),
("Ta", "Tantalum"), ("Ta", "Tantalum"),
("W", "Tungsten"), ("W", "Tungsten"),
("Re", "Rhenium"), ("Re", "Rhenium"),
("Os", "Osmium"), ("Os", "Osmium"),
("Ir", "Iridium"), ("Ir", "Iridium"),
("Pt", "Platinum"), ("Pt", "Platinum"),
("Au", "Gold"), ("Au", "Gold"),
("Hg", "Mercury"), ("Hg", "Mercury"),
("Tl", "Thallium"), ("Tl", "Thallium"),
("Pb", "Lead"), ("Pb", "Lead"),
("Bi", "Bismuth"), ("Bi", "Bismuth"),
("Po", "Polonium"), ("Po", "Polonium"),
("At", "Astatine"), ("At", "Astatine"),
("Rn", "Radon"), ("Rn", "Radon"),
("Fr", "Francium"), ("Fr", "Francium"),
("Ra", "Radium"), ("Ra", "Radium"),
("Ac", "Actinium"), ("Ac", "Actinium"),
("Th", "Thorium"), ("Th", "Thorium"),
("Pa", "Protactinium"), ("Pa", "Protactinium"),
("U", "Uranium"), ("U", "Uranium"),
("Np", "Neptunium"), ("Np", "Neptunium"),
("Pu", "Plutonium"), ("Pu", "Plutonium"),
("Am", "Americium"), ("Am", "Americium"),
("Cm", "Curium"), ("Cm", "Curium"),
("Bk", "Berkelium"), ("Bk", "Berkelium"),
("Cf", "Californium"), ("Cf", "Californium"),
("Es", "Einsteinium"), ("Es", "Einsteinium"),
("Fm", "Fermium"), ("Fm", "Fermium"),
("Md", "Mendelevium"), ("Md", "Mendelevium"),
("No", "Nobelium"), ("No", "Nobelium"),
("Lr", "Lawrencium"), ("Lr", "Lawrencium"),
("Rf", "Rutherfordium"), ("Rf", "Rutherfordium"),
("Db", "Dubnium"), ("Db", "Dubnium"),
("Sg", "Seaborgium"), ("Sg", "Seaborgium"),
("Bh", "Bohrium"), ("Bh", "Bohrium"),
("Hs", "Hassium"), ("Hs", "Hassium"),
("Mt", "Meitnerium"), ("Mt", "Meitnerium"),
("Ds", "Darmstadtium"), ("Ds", "Darmstadtium"),
("Rg", "Roentgenium"), ("Rg", "Roentgenium"),
("Cn", "Copernicium"), ("Cn", "Copernicium"),
("Nh", "Nihonium"), ("Nh", "Nihonium"),
("Fl", "Flerovium"), ("Fl", "Flerovium"),
("Mc", "Moscovium"), ("Mc", "Moscovium"),
("Lv", "Livermorium"), ("Lv", "Livermorium"),
("Ts", "Tennessine"), ("Ts", "Tennessine"),
("Og", "Oganesson"), ("Og", "Oganesson"),
# Isotopes, but hey, they make the words look better # Isotopes, but hey, they make the words look better
("D", "Deuterium"), ("D", "Deuterium"),
("T", "Tritium"), ("T", "Tritium"),
# Non-elements and eastereggs # Non-elements and eastereggs
("E", "Euphorium"), ("E", "Euphorium"),
("Xyzzy", "Plugh"), ("Xyzzy", "Plugh"),
("Plugh", "Xyzzy"), ("Plugh", "Xyzzy"),
("hunter2", "*******"), ("hunter2", "*******"),
] ]
ELEMDICT = {symbol.lower(): name for (symbol, name) in ELEMENTS} ELEMDICT = {symbol.lower(): name for (symbol, name) in ELEMENTS}
ELEMLENGTHS = set(len(symbol) for (symbol, _) in ELEMENTS) ELEMLENGTHS = set(len(symbol) for (symbol, _) in ELEMENTS)
class Node: class Node:
def __init__(self, stump): def __init__(self, stump):
self.stump = stump self.stump = stump
self.edges = [] self.edges = []
class Edge: class Edge:
LETTER = 0 LETTER = 0
ELEMENT = 1 ELEMENT = 1
def __init__(self, start, end, weight, edgetype, text): def __init__(self, start, end, weight, edgetype, text):
self.start = start self.start = start
self.end = end self.end = end
self.weight = weight self.weight = weight
self.edgetype = edgetype self.edgetype = edgetype
self.text = text self.text = text
def elem_prefixes(text): def elem_prefixes(text):
elems = [] elems = []
for i in ELEMLENGTHS: for i in ELEMLENGTHS:
if len(text) < i: continue if len(text) < i: continue
start, rest = text[:i], text[i:] start, rest = text[:i], text[i:]
elem = ELEMDICT.get(start.lower(), None) elem = ELEMDICT.get(start.lower(), None)
if elem: if elem:
elems.append((elem, rest)) elems.append((elem, rest))
return elems return elems
def create_graph(text): def create_graph(text):
graph = {} graph = {}
stumps = [text[i:] for i in range(len(text))] stumps = [text[i:] for i in range(len(text))]
# goal node # goal node
graph[""] = Node("") graph[""] = Node("")
# creating all the nodes # creating all the nodes
for stump in stumps: for stump in stumps:
graph[stump] = Node(stump) graph[stump] = Node(stump)
# creating single-letter links # creating single-letter links
weight = len(text) weight = len(text)
for start in stumps: for start in stumps:
char, end = start[:1], start[1:] char, end = start[:1], start[1:]
nstart = graph.get(start) nstart = graph.get(start)
nend = graph.get(end) nend = graph.get(end)
edge = Edge(nstart, nend, weight, Edge.LETTER, char) edge = Edge(nstart, nend, weight, Edge.LETTER, char)
nstart.edges.append(edge) nstart.edges.append(edge)
# creating element links # creating element links
for stump in stumps: for stump in stumps:
elems = elem_prefixes(stump) elems = elem_prefixes(stump)
for (elem, rest) in elems: for (elem, rest) in elems:
nstart = graph.get(stump) nstart = graph.get(stump)
nend = graph.get(rest) nend = graph.get(rest)
edge = Edge(nstart, nend, 1, Edge.ELEMENT, elem) edge = Edge(nstart, nend, 1, Edge.ELEMENT, elem)
nstart.edges.append(edge) nstart.edges.append(edge)
return graph return graph
def smallest(graph, nodes): def smallest(graph, nodes):
smallest = None smallest = None
for node in nodes: for node in nodes:
if not smallest or node.length < smallest.length: if not smallest or node.length < smallest.length:
smallest = node smallest = node
return smallest return smallest
def dijkstra(graph, start, end): def dijkstra(graph, start, end):
unvisited = set() unvisited = set()
visited = set() visited = set()
nstart = graph.get(start) nstart = graph.get(start)
nstart.step = None nstart.step = None
nstart.length = 0 nstart.length = 0
for edge in nstart.edges: for edge in nstart.edges:
node = edge.end node = edge.end
unvisited.add(node) unvisited.add(node)
node.step = edge node.step = edge
node.length = edge.weight node.length = edge.weight
visited.add(nstart) visited.add(nstart)
while unvisited: while unvisited:
small = smallest(graph, unvisited) small = smallest(graph, unvisited)
unvisited.remove(small) unvisited.remove(small)
visited.add(small) visited.add(small)
if small.stump == "": if small.stump == "":
break break
for edge in small.edges: for edge in small.edges:
node = edge.end node = edge.end
if node in visited: continue if node in visited: continue
length = small.length + edge.weight length = small.length + edge.weight
if node in unvisited: if node in unvisited:
if length < node.length: if length < node.length:
node.step = edge node.step = edge
node.length = length node.length = length
else: else:
unvisited.add(node) unvisited.add(node)
node.step = edge node.step = edge
node.length = length node.length = length
path = [] path = []
node = graph.get(end) node = graph.get(end)
while node.step: while node.step:
path.append(node.step) path.append(node.step)
node = node.step.start node = node.step.start
return reversed(path) return reversed(path)
def format_path(path): def format_path(path):
parts = [] parts = []
part = "" part = ""
for edge in path: for edge in path:
if edge.edgetype == Edge.LETTER: if edge.edgetype == Edge.LETTER:
part += edge.text part += edge.text
else: else:
if part: if part:
parts.append(part) parts.append(part)
part = "" part = ""
parts.append(edge.text) parts.append(edge.text)
if part: if part:
parts.append(part) parts.append(part)
return "-".join(parts) return "-".join(parts)
def shortest_path(text): def shortest_path(text):
graph = create_graph(text) graph = create_graph(text)
path = dijkstra(graph, text, "") path = dijkstra(graph, text, "")
return path return path
class Tom(yaboli.Module): class Tom(yaboli.Module):
DESCRIPTION = ( DESCRIPTION = "spell a word using chemical elements, if possible"
"'tom' attempts to spell a word using the symbols of the periodic table." HELP_GENERAL = "/me spells a word using chemical elements, if possible"
" For example, 'Hi' becomes 'Hydrogen Iodine'.\n" HELP_SPECIFIC = [
"Because of this, only the letters a-z and A-Z may be used in a word.\n" "'tom' attempts to spell a word using the symbols of the periodic"
"'tom' uses en.wikipedia.org/wiki/List_of_chemical_elements for the element names.\n" " table. For example, 'Hi' becomes 'Hydrogen Iodine'.",
) "Because of this, only the letters a-z and A-Z may be used in a word.",
COMMANDS = "!tom <word> - attempts to spell the word using chemical elements\n" "'tom' uses en.wikipedia.org/wiki/List_of_chemical_elements for the"
AUTHOR = "Created by @Garmy using github.com/Garmelon/yaboli\n" " element names.",
CREDITS = "Inspired by Tomsci. Algorithm based on a suggestion by Xyzzy.\n" "",
"!tom <word> - attempts to spell the word using chemical elements",
"",
"Inspired by Tomsci. Algorithm based on a suggestion by Xyzzy.",
"Made by @Garmy with https://github.com/Garmelon/yaboli.",
]
SHORT_DESCRIPTION = "spell a word using chemical elements, if possible" ALLOWED_CHARS = r"a-zA-Z*\w"
SHORT_HELP = "/me spells a word using chemical elements, if possible" ELEM_RE = "([" + ALLOWED_CHARS + "]*)([^" + ALLOWED_CHARS + "]*)"
LONG_DESCRIPTION = DESCRIPTION + COMMANDS + CREDITS def __init__(self, *args, **kwargs):
LONG_HELP = DESCRIPTION + COMMANDS + AUTHOR + CREDITS super().__init__(*args, **kwargs)
ALLOWED_CHARS = r"a-zA-Z*\w" if self.standalone:
ELEM_RE = "([" + ALLOWED_CHARS + "]*)([^" + ALLOWED_CHARS + "]*)" self.register_botrulez(kill=True, restart=True)
async def on_command_general(self, room, message, command, argstr): self.register_general("tom", self.cmd_tom)
await self.command_tom(room, message, command, argstr)
@yaboli.command("tom") async def cmd_tom(self, room, message, args_):
async def command_tom(self, room, message, argstr): # The weird naming of args_ is because I don't want to touch the below
args = [] # function and it's easier to just rename the function parameter.
while argstr: argstr = args_.raw
match = re.match(self.ELEM_RE, argstr)
if not match: break
args.append((match.group(1), match.group(2))) args = []
argstr = argstr[match.end():] while argstr:
match = re.match(self.ELEM_RE, argstr)
if not match: break
spellings = [] args.append((match.group(1), match.group(2)))
for fst, snd in args: argstr = argstr[match.end():]
if fst:
spelling = format_path(shortest_path(fst)) + snd
else:
spelling = snd
spellings.append(spelling)
text = "".join(spellings) spellings = []
await room.send(text, message.mid) for fst, snd in args:
if fst:
spelling = format_path(shortest_path(fst)) + snd
else:
spelling = snd
spellings.append(spelling)
def main(configfile): text = "".join(spellings)
logging.basicConfig(level=logging.INFO) await message.reply(text)
config = configparser.ConfigParser(allow_no_value=True)
config.read(configfile)
nick = config.get("general", "nick")
cookiefile = config.get("general", "cookiefile", fallback=None)
module = Tom()
bot = yaboli.ModuleBot(module, nick, cookiefile=cookiefile)
for room, password in config.items("rooms"):
if not password:
password = None
bot.join_room(room, password=password)
asyncio.get_event_loop().run_forever()
if __name__ == "__main__": if __name__ == "__main__":
main("tom.conf") yaboli.enable_logging(level=logging.DEBUG)
yaboli.run(Tom)