From 8007fbd042e8c185f57a97238f2069f4679793c5 Mon Sep 17 00:00:00 2001 From: mio Date: Tue, 22 Mar 2022 05:44:25 +0000 Subject: [PATCH] Add tracery-inspired example --- docs/README.md | 14 +++ examples/hachi/foils/8ball.lua | 25 +++++ examples/hachi/foils/carto.lua | 62 +++++++++++ examples/hachi/foils/pizza.lua | 181 ++++++++++++++++++++++++++++++++ examples/hachi/folia.lua | 79 ++++++++++++++ examples/hachi/hachi.config.lua | 36 +++++++ examples/hachi/hachi.lua | 4 + examples/hachi/hachi.service | 11 ++ 8 files changed, 412 insertions(+) create mode 100644 examples/hachi/foils/8ball.lua create mode 100644 examples/hachi/foils/carto.lua create mode 100644 examples/hachi/foils/pizza.lua create mode 100644 examples/hachi/folia.lua create mode 100644 examples/hachi/hachi.config.lua create mode 100644 examples/hachi/hachi.lua create mode 100644 examples/hachi/hachi.service diff --git a/docs/README.md b/docs/README.md index ea80f34..2c63f36 100644 --- a/docs/README.md +++ b/docs/README.md @@ -112,6 +112,20 @@ Codes accessible by all users: - `ping`: send a pong message. +## Examples + +The `examples/` directory has a few demo bots to show how to use the module. + +- *hellobot*: a "hello world" example that greets users in several different + languages when they type `!hello`. + +- *ramenkan*: a bot that serves ramen. It has some custom config settings. + +- *hachi*: a bot inspired by tracery text expansion. Loads files ("foils") + containing selection tables and creates handlers to return responses, one + service code per foil. + + ## Running a bot as a background process There are multiple ways to have a bot run in the background. diff --git a/examples/hachi/foils/8ball.lua b/examples/hachi/foils/8ball.lua new file mode 100644 index 0000000..d0b7cb6 --- /dev/null +++ b/examples/hachi/foils/8ball.lua @@ -0,0 +1,25 @@ +-- Source: https://en.wikipedia.org/wiki/Magic_8-ball +grammar = { + origin = { + "it is certain", + "it is decidedly so", + "without a doubt", + "yes — definitely", + "you may rely on it", + "as I see it, yes", + "most likely", + "outlook good", + "yes", + "signs point to yes", + "reply hazy try again", + "ask again later", + "better not tell you now", + "cannot predict now", + "concentrate and ask again", + "don't count on it", + "my reply is no", + "my sources say no", + "outlook not so good", + "very doubtful" + } +} diff --git a/examples/hachi/foils/carto.lua b/examples/hachi/foils/carto.lua new file mode 100644 index 0000000..96b7964 --- /dev/null +++ b/examples/hachi/foils/carto.lua @@ -0,0 +1,62 @@ +-- Source: based loosely on interpretations by Bev G +-- https://exemplore.com/fortune-divination/Playing-Card-Tarot-Yes-No-Answers +grammar = { + origin = { + -- Spades: thought, communication + "♠ A — yes, knowing the truth", + "♠ 2 — no, truth as yet unknown", + "♠ 3 — no, lacking communication", + "♠ 4 — maybe, needing time to recover", + "♠ 5 — maybe, be direct about intentions", + "♠ 6 — yes, proceed slowly, path of least resistance", + "♠ 7 — yes, if convinced; no, if attempting deception", + "♠ 8 — maybe, wait and try hard", + "♠ 9 — no, getting one's affairs in order first", + "♠ 10 — no, most likely not happening", + "♠ J — yes, focusing on the goal", + "♠ Q — yes, if knowing the truth beforehand", + "♠ K — likely, weighing all options objectively", + -- Clubs: inspiration, action + "♣ A — yes, seize the momentum", + "♣ 2 — no, no more to be done besides reorganising plans", + "♣ 3 — yes, with patience", + "♣ 4 — yes, all is well so far", + "♣ 5 — no, push through the current situation first", + "♣ 6 — yes, definitely", + "♣ 7 — maybe, if others can be convinced", + "♣ 8 — yes, do it now", + "♣ 9 — yes, slowly", + "♣ 10 — no, hang on and wait for the situation to improve", + "♣ J — yes, if safe; no, if risky", + "♣ Q — yes, with any encouragement you can find", + "♣ K — yes, without losing sight of the bigger picture", + -- Diamonds: material, money + "♦ A — yes, all is favourable", + "♦ 2 — yes, if organised and in control", + "♦ 3 — yes, for work-related matters", + "♦ 4 — yes, after reallocating resources", + "♦ 5 — no, risks cannot be taken", + "♦ 6 — yes, if giving; unlikely, if receiving", + "♦ 7 — maybe, if ready to initiate change", + "♦ 8 — yes, check the details first", + "♦ 9 — yes, to be done alone", + "♦ 10 — yes, if having family support or blessings", + "♦ J — yes, with hard work", + "♦ Q — yes, while handling the practical aspects", + "♦ K — yes, with studious determination", + -- Hearts: love, emotions + "♥ A — yes, enjoy the moment", + "♥ 2 — yes, welcome it", + "♥ 3 — yes, have fun", + "♥ 4 — no, not in a good emotional state", + "♥ 5 — no, with a heavy heart", + "♥ 6 — yes, possibly related to the past", + "♥ 7 — maybe, there are multiple potential paths", + "♥ 8 — yes, although slowly or reluctantly", + "♥ 9 — yes, it is fine", + "♥ 10 — yes, a good time to aspire towards fulfillment", + "♥ J — yes, though short-lived", + "♥ Q — yes, be attentive to others' needs", + "♥ K — likely, with restraint" + } +} diff --git a/examples/hachi/foils/pizza.lua b/examples/hachi/foils/pizza.lua new file mode 100644 index 0000000..42c85d2 --- /dev/null +++ b/examples/hachi/foils/pizza.lua @@ -0,0 +1,181 @@ +grammar = { + cheese = { + "asiago", + "bocconcini", + "catupiry", + "cheddar", + "feta", + "fontina", + "goat cheese", + "gorgonzola", + "Monterey Jack", + "mozzarella", + "Oaxaca cheese", + "paneer", + "parmesan", + "provolone", + "ricotta", + "stracchino" + }, + crust = { + "deep-dish", + "extra-thick crust", + "mochi crust", + "Neapolitan", + "Roman", + "square", + "thick crust", + "thin crust" + }, + herb = { + "basil", + "cilantro", + "dill", + "oregano", + "rosemary" + }, + meat = { + "bacon", + "bulgogi beef", + "rendang beef", + "roast beef", + "satay beef", + "achari chicken", + "balado chicken", + "dak galbi", + "grilled chicken", + "rendang chicken", + "rotisserie chicken", + "tandoori chicken", + "teriyaki chicken", + "chicken tikka masala", + "chorizo", + "crocodile", + "emu", + "ham", + "parma ham", + "döner kebab", + "meatballs", + "Peking duck", + "peperoncini", + "pepperoni", + "prosciutto", + "salami", + "sauage", + "German sausage", + "Maltese sausage", + "tripas" + }, + mushroom = { + "cremini mushrooms", + "enoki", + "portobello mushrooms", + "shiitake", + "truffle" + }, + seafood = { + "anchovies", + "canned tuna", + "clams", + "crab meat", + "crawfish", + "eel", + "lobster", + "mussels", + "oysters", + "poke", + "prawns", + "salmon", + "salmon teriyaki", + "scallops", + "scampi", + "scungilli", + "shrimp", + "tom yum and shrimp", + "smoked salmon", + "squid", + "tuna" + }, + vegetable = { + "artichokes", + "arugula", + "asparagus", + "avocado", + "broccoli", + "capers", + "chilis", + "jalapeños", + "serrano chilis", + "corn", + "cucumbers", + "pickled cucumbers", + "kale", + "kimchi", + "green olives", + "onions", + "red onions", + "green peppers", + "red peppers", + "red bell peppers", + "peas", + "potatoes", + "sweet potatoes", + "tomatoes", + "sun-dried tomatoes", + "spinach", + "zucchini" + }, + garnish = { + "ground black pepper", + "minced garlic", + "melted butter", + "curry", + "marinara", + "olive oil", + "extra virgin olive oil", + "balsamic vinegar", + "barbecue sauce", + "béarnaise sauce", + "brown sauce", + "donair sauce", + "honey barbecue sauce", + "sriracha", + "tabasco sauce", + "tomato sauce", + "white sauce" + }, + other = { + "hard-boiled eggs", + "labane", + "nachos", + "poutine", + "bananas", + "pineapples" + }, + topping = { + "#cheese#", + "#herb#", + "#meat#", + "#mushroom#", + "#other#", + "#seafood#", + "#vegetable#" + }, + forecast = { + "#crust#, #topping# and #topping# with a chance of #topping#", + "lots of #topping#, with #topping# all the way to the #crust#, and a greater chance of #topping# and #topping# throughout", + "a meaty mix of #meat#, #meat# and #meat#", + "a toss-up of #vegetable#, #vegetable# and #vegetable#", + "a dynamic system of #cheese# and #cheese# is moving in towards the #crust#, along with more #cheese#", + "some scattered #vegetable#, high on the #mushroom#, and just a touch of #topping# below", + "a pretty #crust# with lots of #mushroom#, #herb# and #garnish#", + "plenty of #topping# and #garnish#, followed by trace amounts of #herb#", + "#topping#, #topping# and a shower of #garnish#", + "#meat# and #vegetable# followed later by #cheese#", + "expect some #garnish# and #herb# over the #topping# and #topping#", + "starts with some #meat#, then moves into #vegetable# and #mushroom# over the #crust#" + }, + origin = { + "Pizza forecast: #forecast#" + } +} diff --git a/examples/hachi/folia.lua b/examples/hachi/folia.lua new file mode 100644 index 0000000..d99af35 --- /dev/null +++ b/examples/hachi/folia.lua @@ -0,0 +1,79 @@ +local util = require("itteutil") + +-- Default settings +local folia = { + foils_dir = "", + debug = false, +} + + +util.docs.get_foils = [[ (directory_str) + Given the full path to a directory of foils, return the foil contents as a + table. + ]] +function folia.get_foils(str) + local foil_list_str = io.popen("ls " .. str) + local foils = {} + if foil_list_str then + local foil_list = util.split_str(foil_list_str:read("*a")) + for i = 1, #foil_list do + local key = string.gsub(foil_list[i], ".lua", "") + local func, err = loadfile(str .. "/" .. foil_list[i]) + if func then + func() + foils[key] = grammar + end + end + -- Unset global variables + func = nil + grammar = nil + end + return foils +end + + +util.docs.output = [[ (key_str) + Given a foil name or foils table key, output a random expanded string. + ]] +function folia.output(key) + local foils = folia.get_foils(folia.foils_dir) + if table.concat(util.table_keys(foils), "") == "" then + util.debug("output", "Error: cannot retrieve foils." .. + " Please make sure the full directory path is correct.", folia.debug) + do return end + elseif not util.has_key(foils, key) then + util.debug("output", "Error: the key `" .. key .. + "` is not in the foils table.", folia.debug) + do return end + elseif not util.has_key(foils[key], "origin") then + util.debug("output", "Error: no origin found in the foil.", folia.debug) + do return end + elseif foils[key]["origin"] == {} then + util.debug("output", "Error: empty origin.", folia.debug) + do return end + end + + local output_str = util.pick(foils[key]["origin"])[1] + local pattern = "#(%w+)#" + util.debug("output", "level 1: " .. output_str, folia.debug) + -- Check for expansion objects and replace them until none are found + while string.find(output_str, pattern) ~= nil do + local hash = output_str:sub(string.find(output_str, pattern)) + local obj = hash:sub(2, #hash - 1) + -- Check the object key is in the foil grammar, and replace one instance + -- at a time. + if util.has_key(foils[key], obj) then + local expand_str = util.pick(foils[key][obj])[1] + output_str = string.gsub(output_str, hash, expand_str, 1) + util.debug("output", "level n: " .. output_str, folia.debug) + -- If the object key is not in the grammar, replace references with a + -- placeholder. + else + output_str = string.gsub(output_str, hash, "[" .. obj .. "]") + end + end + return output_str +end + + +return folia diff --git a/examples/hachi/hachi.config.lua b/examples/hachi/hachi.config.lua new file mode 100644 index 0000000..8f3b3b8 --- /dev/null +++ b/examples/hachi/hachi.config.lua @@ -0,0 +1,36 @@ +local irc = require("itte") +local folia = require("folia") + + +itte_config = { + debug = true, + messages = { + help = "Today's your lucky day, for I, hachi, also known to none as" .. + " the handsomer Hermes, shall bring you messages of ... well," .. + " wouldn't you like to know? For a bit of faux fortune-telling or" .. + " fickle forecasting, ask a question, then try: {{codes}}", + quit = "May fortune smile upon you all!", + reload = "Config reloaded.", + }, +} + +itte_handlers = {} + +folia.foils_dir = "/home/mio/bin/itte/examples/hachi/foils" + + +local hachi = {} + + +-- Create handlers for each foil +function hachi.generate_handlers(handlers, foils_dir) + local foils = folia.get_foils(foils_dir) + for key, foil in pairs(foils) do + handlers[key] = function (cxt, msg) + irc.message(cxt, { msg.reply_to }, folia.output(key)) + end + end +end + + +hachi.generate_handlers(itte_handlers, folia.foils_dir) diff --git a/examples/hachi/hachi.lua b/examples/hachi/hachi.lua new file mode 100644 index 0000000..d3d23ad --- /dev/null +++ b/examples/hachi/hachi.lua @@ -0,0 +1,4 @@ +local hachi = require("itte") +hachi.confs.prefix = "hachi" + +hachi.run() diff --git a/examples/hachi/hachi.service b/examples/hachi/hachi.service new file mode 100644 index 0000000..de0d817 --- /dev/null +++ b/examples/hachi/hachi.service @@ -0,0 +1,11 @@ +[Unit] +Description=hachi — a tracery-inspired IRC bot + +[Service] +WorkingDirectory=%h/bin/itte/examples/hachi +ExecStart=/usr/bin/lua ./hachi.lua +Restart=always +RestartSec=300 + +[Install] +WantedBy=default.target