Add tracery-inspired example
parent
4edbf32ef9
commit
d31e322b10
|
@ -112,6 +112,20 @@ Codes accessible by all users:
|
||||||
- `ping`: send a pong message.
|
- `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
|
## Running a bot as a background process
|
||||||
|
|
||||||
There are multiple ways to have a bot run in the background.
|
There are multiple ways to have a bot run in the background.
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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#"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,4 @@
|
||||||
|
local hachi = require("itte")
|
||||||
|
hachi.confs.prefix = "hachi"
|
||||||
|
|
||||||
|
hachi.run()
|
|
@ -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
|
|
@ -369,7 +369,7 @@ end
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
itte_config = {
|
itte_config = {
|
||||||
debug = true,
|
debug = false,
|
||||||
messages = {
|
messages = {
|
||||||
help = "一、二、三、らーめん缶! Hello, I am a ramen vending machine. " ..
|
help = "一、二、三、らーめん缶! Hello, I am a ramen vending machine. " ..
|
||||||
"Please type a code for service: {{codes}} " ..
|
"Please type a code for service: {{codes}} " ..
|
||||||
|
|
Loading…
Reference in New Issue