Add scheduled tasks
parent
8530a3bb77
commit
20fd463f5b
|
@ -47,7 +47,8 @@
|
|||
|
||||
- `itte_handlers`: most bots listen for some pre-defined code words (often
|
||||
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
|
||||
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
|
||||
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")
|
||||
containing selection tables and creates handlers to return responses, one
|
||||
|
|
|
@ -439,16 +439,19 @@ end
|
|||
|
||||
-- Serve meat-based ramen.
|
||||
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
|
||||
local roll = math.random(1, 100)
|
||||
if roll == 77 then
|
||||
irc.message(cxt, { msg.reply_to },
|
||||
string.gsub(itte_config.messages.special, "{{ramen}}",
|
||||
ramen.make_ramen("meat_special")))
|
||||
irc.message(cxt, recipients, string.gsub(itte_config.messages.special,
|
||||
"{{ramen}}", ramen.make_ramen("meat_special")))
|
||||
else
|
||||
irc.message(cxt, { msg.reply_to },
|
||||
string.gsub(itte_config.messages.serve, "{{ramen}}",
|
||||
ramen.make_ramen("meat")))
|
||||
irc.message(cxt, recipients, string.gsub(itte_config.messages.serve,
|
||||
"{{ramen}}", ramen.make_ramen("meat")))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -474,4 +477,10 @@ function h.water(cxt, msg)
|
|||
end
|
||||
|
||||
|
||||
-- Serve ramen at the interval specified in the config.
|
||||
function h.th_ramen(cxt, task)
|
||||
h.ramen(cxt, task)
|
||||
end
|
||||
|
||||
|
||||
itte_handlers = h
|
||||
|
|
|
@ -9,6 +9,14 @@ itte_servers = {
|
|||
auth_user = "USER",
|
||||
code_prefix = "!",
|
||||
admins = { USER = "PASSWORD" },
|
||||
tasks = {
|
||||
ramen = {
|
||||
interval = "daily",
|
||||
time = "12:00",
|
||||
handler = "th_ramen",
|
||||
recipients = { "#ramenkan" },
|
||||
},
|
||||
},
|
||||
},
|
||||
casa = {
|
||||
host = "m455.casa",
|
||||
|
@ -21,6 +29,14 @@ itte_servers = {
|
|||
auth_pass = "PASSWORD",
|
||||
code_prefix = "!",
|
||||
admins = { USER = "PASSWORD" },
|
||||
tasks = {
|
||||
ramen = {
|
||||
interval = "daily",
|
||||
time = "17:00",
|
||||
handler = "th_ramen",
|
||||
recipients = { "#kitchen" },
|
||||
},
|
||||
},
|
||||
},
|
||||
tildechat = {
|
||||
host = "irc.tilde.chat",
|
||||
|
|
117
itte.lua
117
itte.lua
|
@ -24,6 +24,7 @@ itte.confs = {
|
|||
itte.config = {
|
||||
debug = false,
|
||||
admin_handlers = { "connect", "quit", "reload", "servers", "join", "part" },
|
||||
task_handler_prefix = "th_",
|
||||
messages = {
|
||||
connect_success = "Connected to {{server}}.",
|
||||
help = "Service codes available: {{codes}}",
|
||||
|
@ -56,6 +57,8 @@ itte.config = {
|
|||
redact = { "********" },
|
||||
send = { "send" },
|
||||
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
|
||||
itte.state = {
|
||||
connected = false,
|
||||
timeout = 0.1,
|
||||
}
|
||||
itte.contexts = {}
|
||||
|
||||
|
@ -143,16 +147,19 @@ function itte.get_config(reload)
|
|||
if itte_handlers ~= nil then itte.handlers = itte_handlers 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
|
||||
-- Reconstruct the context tables
|
||||
-- This only works if the server name has not changed between reloads.
|
||||
for name, prop in pairs(itte.servers) do
|
||||
cxt_old = itte.contexts[name]
|
||||
itte.contexts[name] = prop
|
||||
itte.contexts[name].name = name
|
||||
itte.contexts[name].cmds = itte.get_commands(prop)
|
||||
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
|
||||
|
@ -307,7 +314,7 @@ function itte.init_socket(name, svr)
|
|||
context.con = ssl.wrap(context.con, con_params)
|
||||
context.con:dohandshake()
|
||||
-- Set a short timeout to be non-blocking
|
||||
context.con:settimeout(0.1)
|
||||
context.con:settimeout(itte.state.timeout)
|
||||
|
||||
-- Set context and global states
|
||||
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],
|
||||
itte.config.debug)
|
||||
itte.state.connected = false
|
||||
os.exit()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -767,6 +775,12 @@ function itte._h.help(cxt, msg)
|
|||
end
|
||||
|
||||
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, ", " ..
|
||||
cxt.code_prefix)
|
||||
-- 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)
|
||||
|
@ -838,9 +852,12 @@ function itte.listen(name, str)
|
|||
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
||||
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
|
||||
-- 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)
|
||||
-- Check the handlers table
|
||||
elseif util.has_key(itte.handlers, msg.code) then
|
||||
|
@ -872,6 +889,83 @@ function itte.add_listener(cxt)
|
|||
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 = [[ ()
|
||||
Run the client.
|
||||
]]
|
||||
|
@ -882,12 +976,19 @@ function itte.run()
|
|||
itte.connect_server(name)
|
||||
end
|
||||
|
||||
while itte.state.connected do
|
||||
if itte.state.connected then
|
||||
for _, cxt in pairs(itte.contexts) do
|
||||
itte.add_listener(cxt)
|
||||
itte.add_tasks(cxt)
|
||||
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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue