Add tracery-inspired example
parent
4edbf32ef9
commit
d31e322b10
|
@ -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.
|
||||
|
|
|
@ -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 = {
|
||||
debug = true,
|
||||
debug = false,
|
||||
messages = {
|
||||
help = "一、二、三、らーめん缶! Hello, I am a ramen vending machine. " ..
|
||||
"Please type a code for service: {{codes}} " ..
|
||||
|
|
Loading…
Reference in New Issue