Add scheduled tasks
parent
8530a3bb77
commit
20fd463f5b
|
@ -47,7 +47,8 @@
|
||||||
|
|
||||||
- `itte_handlers`: most bots listen for some pre-defined code words (often
|
- `itte_handlers`: most bots listen for some pre-defined code words (often
|
||||||
known as "commands") and respond by calling a handler, or function to
|
known as "commands") and respond by calling a handler, or function to
|
||||||
handle the request accordingly.
|
handle the request accordingly. Handlers for scheduled tasks can also be
|
||||||
|
added.
|
||||||
|
|
||||||
The module will refer to the keywords as codes to distinguish them
|
The module will refer to the keywords as codes to distinguish them
|
||||||
from IRC commands sent to the server like `JOIN` or `PART`. Name the
|
from IRC commands sent to the server like `JOIN` or `PART`. Name the
|
||||||
|
@ -119,7 +120,8 @@ 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
|
- *hellobot*: a "hello world" example that greets users in several different
|
||||||
languages when they type `!hello`.
|
languages when they type `!hello`.
|
||||||
|
|
||||||
- *ramenkan*: a bot that serves ramen. It has some custom config settings.
|
- *ramenkan*: a bot that serves ramen. It has some custom config settings and
|
||||||
|
scheduled tasks.
|
||||||
|
|
||||||
- *hachi*: a bot inspired by tracery text expansion. Loads files ("foils")
|
- *hachi*: a bot inspired by tracery text expansion. Loads files ("foils")
|
||||||
containing selection tables and creates handlers to return responses, one
|
containing selection tables and creates handlers to return responses, one
|
||||||
|
|
|
@ -439,16 +439,19 @@ end
|
||||||
|
|
||||||
-- Serve meat-based ramen.
|
-- Serve meat-based ramen.
|
||||||
function h.mramen(cxt, msg)
|
function h.mramen(cxt, msg)
|
||||||
|
local recipients = { msg.reply_to }
|
||||||
|
-- Scheduled task recipients
|
||||||
|
if msg.recipients ~= nil then
|
||||||
|
recipients = msg.recipients
|
||||||
|
end
|
||||||
-- 1% chance of special set
|
-- 1% chance of special set
|
||||||
local roll = math.random(1, 100)
|
local roll = math.random(1, 100)
|
||||||
if roll == 77 then
|
if roll == 77 then
|
||||||
irc.message(cxt, { msg.reply_to },
|
irc.message(cxt, recipients, string.gsub(itte_config.messages.special,
|
||||||
string.gsub(itte_config.messages.special, "{{ramen}}",
|
"{{ramen}}", ramen.make_ramen("meat_special")))
|
||||||
ramen.make_ramen("meat_special")))
|
|
||||||
else
|
else
|
||||||
irc.message(cxt, { msg.reply_to },
|
irc.message(cxt, recipients, string.gsub(itte_config.messages.serve,
|
||||||
string.gsub(itte_config.messages.serve, "{{ramen}}",
|
"{{ramen}}", ramen.make_ramen("meat")))
|
||||||
ramen.make_ramen("meat")))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -474,4 +477,10 @@ function h.water(cxt, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Serve ramen at the interval specified in the config.
|
||||||
|
function h.th_ramen(cxt, task)
|
||||||
|
h.ramen(cxt, task)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
itte_handlers = h
|
itte_handlers = h
|
||||||
|
|
|
@ -9,6 +9,14 @@ itte_servers = {
|
||||||
auth_user = "USER",
|
auth_user = "USER",
|
||||||
code_prefix = "!",
|
code_prefix = "!",
|
||||||
admins = { USER = "PASSWORD" },
|
admins = { USER = "PASSWORD" },
|
||||||
|
tasks = {
|
||||||
|
ramen = {
|
||||||
|
interval = "daily",
|
||||||
|
time = "12:00",
|
||||||
|
handler = "th_ramen",
|
||||||
|
recipients = { "#ramenkan" },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
casa = {
|
casa = {
|
||||||
host = "m455.casa",
|
host = "m455.casa",
|
||||||
|
@ -21,6 +29,14 @@ itte_servers = {
|
||||||
auth_pass = "PASSWORD",
|
auth_pass = "PASSWORD",
|
||||||
code_prefix = "!",
|
code_prefix = "!",
|
||||||
admins = { USER = "PASSWORD" },
|
admins = { USER = "PASSWORD" },
|
||||||
|
tasks = {
|
||||||
|
ramen = {
|
||||||
|
interval = "daily",
|
||||||
|
time = "17:00",
|
||||||
|
handler = "th_ramen",
|
||||||
|
recipients = { "#kitchen" },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tildechat = {
|
tildechat = {
|
||||||
host = "irc.tilde.chat",
|
host = "irc.tilde.chat",
|
||||||
|
|
117
itte.lua
117
itte.lua
|
@ -24,6 +24,7 @@ itte.confs = {
|
||||||
itte.config = {
|
itte.config = {
|
||||||
debug = false,
|
debug = false,
|
||||||
admin_handlers = { "connect", "quit", "reload", "servers", "join", "part" },
|
admin_handlers = { "connect", "quit", "reload", "servers", "join", "part" },
|
||||||
|
task_handler_prefix = "th_",
|
||||||
messages = {
|
messages = {
|
||||||
connect_success = "Connected to {{server}}.",
|
connect_success = "Connected to {{server}}.",
|
||||||
help = "Service codes available: {{codes}}",
|
help = "Service codes available: {{codes}}",
|
||||||
|
@ -56,6 +57,8 @@ itte.config = {
|
||||||
redact = { "********" },
|
redact = { "********" },
|
||||||
send = { "send" },
|
send = { "send" },
|
||||||
svrs_not_found = { "config", "Error: servers not found." },
|
svrs_not_found = { "config", "Error: servers not found." },
|
||||||
|
task_added = { "task", "Task `{{name}}` added." },
|
||||||
|
task_activated = { "task", "Task `{{name}}` activated at {{ts}}." },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +81,7 @@ itte.docs._h = {}
|
||||||
-- Internal only: track application state
|
-- Internal only: track application state
|
||||||
itte.state = {
|
itte.state = {
|
||||||
connected = false,
|
connected = false,
|
||||||
|
timeout = 0.1,
|
||||||
}
|
}
|
||||||
itte.contexts = {}
|
itte.contexts = {}
|
||||||
|
|
||||||
|
@ -143,16 +147,19 @@ function itte.get_config(reload)
|
||||||
if itte_handlers ~= nil then itte.handlers = itte_handlers end
|
if itte_handlers ~= nil then itte.handlers = itte_handlers end
|
||||||
if itte_admins ~= nil then itte.admins = itte_admins end
|
if itte_admins ~= nil then itte.admins = itte_admins end
|
||||||
|
|
||||||
-- If reloading, reconstruct the context tables.
|
|
||||||
-- This only works if the name name has not changed between reloads.
|
|
||||||
if reload then
|
if reload then
|
||||||
|
-- Reconstruct the context tables
|
||||||
|
-- This only works if the server name has not changed between reloads.
|
||||||
for name, prop in pairs(itte.servers) do
|
for name, prop in pairs(itte.servers) do
|
||||||
cxt_old = itte.contexts[name]
|
cxt_old = itte.contexts[name]
|
||||||
itte.contexts[name] = prop
|
itte.contexts[name] = prop
|
||||||
itte.contexts[name].name = name
|
itte.contexts[name].name = name
|
||||||
itte.contexts[name].cmds = itte.get_commands(prop)
|
itte.contexts[name].cmds = itte.get_commands(prop)
|
||||||
itte.contexts[name].con = cxt_old.con
|
itte.contexts[name].con = cxt_old.con
|
||||||
itte.contexts[name].con:settimeout(0.5)
|
itte.contexts[name].con:settimeout(itte.state.timeout)
|
||||||
|
|
||||||
|
-- Reconstruct the task coroutines
|
||||||
|
itte.add_tasks(itte.contexts[name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -307,7 +314,7 @@ function itte.init_socket(name, svr)
|
||||||
context.con = ssl.wrap(context.con, con_params)
|
context.con = ssl.wrap(context.con, con_params)
|
||||||
context.con:dohandshake()
|
context.con:dohandshake()
|
||||||
-- Set a short timeout to be non-blocking
|
-- Set a short timeout to be non-blocking
|
||||||
context.con:settimeout(0.1)
|
context.con:settimeout(itte.state.timeout)
|
||||||
|
|
||||||
-- Set context and global states
|
-- Set context and global states
|
||||||
context.state.connected = true
|
context.state.connected = true
|
||||||
|
@ -385,6 +392,7 @@ function itte.disconnect_server(name)
|
||||||
util.debug(itte.config.debugs.exit[1], itte.config.debugs.exit[2],
|
util.debug(itte.config.debugs.exit[1], itte.config.debugs.exit[2],
|
||||||
itte.config.debug)
|
itte.config.debug)
|
||||||
itte.state.connected = false
|
itte.state.connected = false
|
||||||
|
os.exit()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -767,6 +775,12 @@ function itte._h.help(cxt, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local custom_h = util.table_keys(itte.handlers)
|
local custom_h = util.table_keys(itte.handlers)
|
||||||
|
-- Remove task handlers
|
||||||
|
for h = 1, #custom_h do
|
||||||
|
if string.find(custom_h[h], itte.config.task_handler_prefix) == 1 then
|
||||||
|
custom_h[h] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
local codes = cxt.code_prefix .. table.concat(custom_h, ", " ..
|
local codes = cxt.code_prefix .. table.concat(custom_h, ", " ..
|
||||||
cxt.code_prefix)
|
cxt.code_prefix)
|
||||||
-- Core service codes are shown only to admins
|
-- Core service codes are shown only to admins
|
||||||
|
@ -810,7 +824,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- Service code mapping and runtime
|
-- Service code and taskd task mapping
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
itte.docs.listen = [[ (name_str, data_str)
|
itte.docs.listen = [[ (name_str, data_str)
|
||||||
|
@ -838,9 +852,12 @@ function itte.listen(name, str)
|
||||||
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Ignore calls to task handlers
|
||||||
|
if (string.find(msg.code, itte.config.task_handler_prefix) == 1) then
|
||||||
|
do return end
|
||||||
-- Check for the service code in the functions table before attempting to
|
-- Check for the service code in the functions table before attempting to
|
||||||
-- call the function.
|
-- call the function.
|
||||||
if util.has_key(itte._h, msg.code) then
|
elseif util.has_key(itte._h, msg.code) then
|
||||||
itte._h[msg.code](cxt, msg)
|
itte._h[msg.code](cxt, msg)
|
||||||
-- Check the handlers table
|
-- Check the handlers table
|
||||||
elseif util.has_key(itte.handlers, msg.code) then
|
elseif util.has_key(itte.handlers, msg.code) then
|
||||||
|
@ -872,6 +889,83 @@ function itte.add_listener(cxt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
itte.docs.add_tasks = [[ (context_table)
|
||||||
|
Create coroutines for tasks.
|
||||||
|
]]
|
||||||
|
function itte.add_tasks(cxt)
|
||||||
|
-- Exit function if there are no tasks for the context
|
||||||
|
if (cxt.tasks == nil) or (cxt.tasks == {}) then
|
||||||
|
do return end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Construct a task coroutine and call its handler function when resumed
|
||||||
|
local co = function(name, task)
|
||||||
|
if util.has_key(itte.handlers, task.handler) then
|
||||||
|
local ts = os.date("%Y-%m-%d %H:%M:%S", os.time())
|
||||||
|
local task_str = string.gsub(itte.config.debugs.task_activated[2],
|
||||||
|
"{{name}}", name)
|
||||||
|
task_str = string.gsub(task_str, "{{ts}}", ts)
|
||||||
|
util.debug(itte.config.debugs.task_activated[1], task_str,
|
||||||
|
itte.config.debug)
|
||||||
|
itte.handlers[task.handler](cxt, task)
|
||||||
|
-- Suspend coroutine to be reactivated later
|
||||||
|
coroutine.yield(co)
|
||||||
|
task.done = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialise task coroutine
|
||||||
|
for name, task in pairs(cxt.tasks) do
|
||||||
|
task.co = coroutine.create(co)
|
||||||
|
task.done = false
|
||||||
|
util.debug(itte.config.debugs.task_added[1],
|
||||||
|
string.gsub(itte.config.debugs.task_added[2], "{{name}}", name),
|
||||||
|
itte.config.debug)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
itte.docs.check_tasks = [[ (context_table)
|
||||||
|
Compare task times to the current time and resume a task's coroutine if
|
||||||
|
the task interval or time match the current time.
|
||||||
|
]]
|
||||||
|
function itte.check_tasks(cxt)
|
||||||
|
-- Exit function if there are no tasks for the context
|
||||||
|
if (cxt.tasks == nil) or (cxt.tasks == {}) then
|
||||||
|
do return end
|
||||||
|
end
|
||||||
|
|
||||||
|
local dt = util.split_str(os.date("%w %Y %m %d %H %M", os.time()))
|
||||||
|
-- Support preset minute intervals
|
||||||
|
local interval_min = { 5, 10, 15, 20, 30 }
|
||||||
|
for name, task in pairs(cxt.tasks) do
|
||||||
|
local task_min = string.sub(task.time, string.find(task.time, ":") + 1,
|
||||||
|
string.find(task.time, ":") + 2)
|
||||||
|
if (util.has_key(interval_min, tonumber(task.interval:sub(1, -2))) and
|
||||||
|
tonumber(dt[6]) % tonumber(task.interval:sub(1, -2)) == 0) or
|
||||||
|
(task.interval == "hourly" and task_min == dt[6]) or
|
||||||
|
(task.interval == "daily" and task.time == dt[5] .. ":" .. dt[6]) or
|
||||||
|
(task.interval == "weekly" and task.weekday == dt[1] and
|
||||||
|
task.time == dt[5] .. ":" .. dt[6]) or
|
||||||
|
(task.interval == "monthly" and task.day == dt[4] and
|
||||||
|
task.time == dt[5] .. ":" .. dt[6])
|
||||||
|
then
|
||||||
|
-- Run only once per interval
|
||||||
|
while not task.done do
|
||||||
|
coroutine.resume(task.co, name, task)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Reset the done flag after running task
|
||||||
|
task.done = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Runtime
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
itte.docs.run = [[ ()
|
itte.docs.run = [[ ()
|
||||||
Run the client.
|
Run the client.
|
||||||
]]
|
]]
|
||||||
|
@ -882,12 +976,19 @@ function itte.run()
|
||||||
itte.connect_server(name)
|
itte.connect_server(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
while itte.state.connected do
|
if itte.state.connected then
|
||||||
for _, cxt in pairs(itte.contexts) do
|
for _, cxt in pairs(itte.contexts) do
|
||||||
itte.add_listener(cxt)
|
itte.add_tasks(cxt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
while itte.state.connected do
|
||||||
|
for _, cxt in pairs(itte.contexts) do
|
||||||
|
itte.add_listener(cxt)
|
||||||
|
itte.check_tasks(cxt)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue