Add tracery-inspired example

trunk
mio 2022-03-22 05:44:25 +00:00
parent 4edbf32ef9
commit d31e322b10
9 changed files with 413 additions and 1 deletions

View File

@ -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.

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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#"
}
}

View File

@ -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

View File

@ -0,0 +1,36 @@
local irc = require("itte")
local folia = require("folia")
itte_config = {
debug = false,
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)

View File

@ -0,0 +1,4 @@
local hachi = require("itte")
hachi.confs.prefix = "hachi"
hachi.run()

View File

@ -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

View File

@ -369,7 +369,7 @@ end
-- ---------------------------------------------------------------------------
itte_config = {
debug = true,
debug = false,
messages = {
help = "一、二、三、らーめん缶! Hello, I am a ramen vending machine. " ..
"Please type a code for service: {{codes}} " ..