- Add more-detailed usage doc
- Add a smaller hello world example
- Include a user systemd file
- Fix config check to include second-level nested tables
trunk 1.1
mio 2022-03-21 03:17:04 +00:00
parent 75d3f149f2
commit 4edbf32ef9
12 changed files with 319 additions and 55 deletions

5
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.servers.lua
examples/itte*.lua
!sample.servers.lua
examples/*/itte*.lua
!hellobot.servers.lua
!ramenkan.sample.servers.lua

View File

@ -7,33 +7,16 @@ Currently supported:
- Authentication via SASL (plain) or Nickserv
- Joining multiple servers
- Ad-hoc connecting to servers and joining channels
- Config reload
## Requirements
## Installation
- [Lua 5.x](https://www.lua.org/)
- [luasocket](https://w3.impa.br/~diego/software/luasocket/)
- [luasec](https://github.com/brunoos/luasec)
## Usage
See the bot files in the `examples/` directory for usage notes.
## Example: ramenkan
- Install Lua and other dependencies, e.g. for Alpine:
`apk add lua-socket lua-sec`
- Copy the `itte*.lua` files to the `examples/` directory. Copy the
`examples/sample.servers.lua` as `ramenkan.servers.lua` and change the server
settings as applicable.
- Run: `nohup lua /path/to/examples/ramenkan.lua >/dev/null 2>&1 &`
Please see the [docs](docs/README.md).
## License
BSD-3.0
[BSD-3.0](https://opensource.org/licenses/BSD-3-Clause)

149
docs/README.md 100644
View File

@ -0,0 +1,149 @@
# Itte Documentation
## Requirements
- [Lua 5.x](https://www.lua.org/)
- [luasocket](https://w3.impa.br/~diego/software/luasocket/)
- [luasec](https://github.com/brunoos/luasec)
## Installation
- Install Lua and other dependencies. Example:
- Debian/Ubuntu-based distributions: `apt-get install lua-socket lua-sec`
- Alpine Linux: `apk add lua-socket lua-sec`
- Copy the `itte*.lua` files to the project directory.
## Usage
- Create two config files, or copy the sample files from `examples/` to the
same directory level as the `itte*.lua` files. Replace instances of
`[prefix]` hereafter with the name of your choice without spaces or special
characters, e.g. the project name.
- `[prefix].servers.lua`: list servers and global admins here.
- `[prefix].config.lua`: add bot settings and handlers here.
- There are two variables in `*.servers.lua`:
- `itte_servers` (required): add a server to `itte_server` following the
example configurations.
- `itte_admins` (optional): this is a table of global admins who can access
bot-wide handlers like connecting servers. If left undefined, global
admin-only handlers will not be run. In some cases this may be desirable to
reduce incidents of bot tampering.
- There are two optional variables in `*.config.lua`:
- `itte_config`: customise bot settings, such as the default response and
error messages shown to users.
- `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.
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
handler after the code word that will be used to trigger the function and
add it to `itte_handlers` so it can be automatically picked up by the
module. For example, if the code users will type is `!hello` (where `!` is
a prefix to mark it as a bot code), name the function
`itte_handlers.hello()`. Handler names cannot start with numbers or special
characters.
- Initialise the module in a project file named `[prefix].lua` and add the
following to the file:
```
-- Import the module
local itte = require("itte")
-- Set the prefix for the module to find the config files
-- The prefix is the name preceding `.servers.lua` and `.config.lua`
itte.confs.prefix = "[prefix]"
-- Call the run function
itte.run()
```
- Start the bot: `lua ./[prefix].lua`
## Built-in service codes
The module includes a number of built-in codes that bots can respond to. The
full list can be viewed on IRC by issuing the code `!help` in a private
message with a bot or in a channel where the bot is present. Admin users will
also see admin-only codes to manage a bot.
Global admin-only codes:
- `connect [server] [user] [password]`: connect to server listed in the server
config, where `[server]` is the name given in the servers table (or use
`servers` to get the names).
- `quit [server] [user] [password]`: disconnect from a server. The bot process
will automatically exit if it is not connected to any server.
- `reload [user] [password]`: reload the bot config.
- `servers [user] [password]`: list known/active servers from the config.
Server admin-only codes:
- `channels [password]`: list known channels from the server config.
- `join [channel] [password]`: join channel(s), e.g. `!join #bots #programming
password`.
- `part [channel] [passowrd]`: leave channel(s), e.g. `!part #bots password`.
Codes accessible by all users:
- `help`: list available service codes.
- `ping`: send a pong message.
## Running a bot as a background process
There are multiple ways to have a bot run in the background.
### nohup
Using the `nohup` command is quick and simple, though it does not support
restarting if the bot exits due to a network issue.
To run the bot and discard any output by redirecting it to `/dev/null`:
`nohup lua /path/to/[prefix].lua >/dev/null 2>&1 &`
### Init scripts, e.g. systemd
Startup scripts enable services to start automatically and relaunch in the
event of an unexpected exit (unless expressly stopped through an init system
command). The instructions below describe setting up and running a [systemd]
service for a bot under a non-privileged user.
- Create a systemd directory for the user running the bot if it does not
already exist: `mkdir -p /home/[user]/.config/systemd/user`
- Copy the sample `*.service` to the user systemd directory and rename the file
to `[prefix].service`. Edit the service description, `WorkingDirectory` and
`ExecStart` values as needed. Use full paths for the `WorkingDirectory` and
location of the Lua executable, or the service file will not work.
- Start and enable the service:
```
systemctl --user start [prefix]
systemctl --user enable [prefix]
```
[systemd]: https://www.freedesktop.org/software/systemd/man/systemd.service.html

View File

@ -0,0 +1,81 @@
-- ---------------------------------------------------------------------------
-- Custom variables
-- ---------------------------------------------------------------------------
local hello_words = {
"ahoy!", "EHLO", "hai", "hello!", "henlo", "hey!", "hi!", "ACK (TCP)",
"aloha! (Hawaiian)", "ciao! (Italian)", "hallo (Dutch)", "hej (Swedish)",
"hola (Spanish)", "salut! (French)", "saluton (Esperanto)", "tag! (German)",
"toki! (Toki Pona)", "هلا (pron: hala)", "مااس (Arabic, pron: salaam)",
"สวัสดี (Thai, pron: sawatdee)", "你好 (Chinese, pron: ni hao)",
"こんにちわ (Japanese, pron: konnichiwa)", "안녕 (Korean, pron: annyeong)",
"नमस्ते (Hindi, pron: namaste)", "ᐊᐃ (Ai)", "Привет (Russian, pron: preevyet)",
"שָׁלוֹם (Hebrew, pron: shalom)", "kia ora (Māori)"
}
-- ---------------------------------------------------------------------------
-- Config
-- ---------------------------------------------------------------------------
-- [[
-- `itte_config`
-- Add custom settings and override the default config settings here (see
-- `itte.lua` for the full list). Settings need to be appended to this variable
-- to be collected by the reload function.
-- ]]
itte_config = {
debug = true,
}
-- ---------------------------------------------------------------------------
-- Handlers
-- ---------------------------------------------------------------------------
-- Load the main module.
local irc = require("itte")
-- Load the util module to use some helper functions.
local util = require("itteutil")
--[[
-- `itte_handlers`
-- Define any custom handlers in a new table, then assign the table to it.
-- Handlers can also be added to it directly e.g. `itte_handlers.hello()`.
-- Handler names should correspond to the service code name, e.g. a function
-- `itte_handlers.hello()` will be called within chat as
-- `[command_prefix]hello`, as in `!hello`.
--
-- Here an object called `h` is created for convenience, and functions will be
-- added to it.
--]]
local h = {}
-- Reply with a random greeting.
-- The handler takes two parameters, `cxt` and `msg`.
--
-- The first is a context table that includes server details and a reference to
-- the socket connection. The other is a message table, which is the IRC user
-- message broken down into parts such as the sender, recipient, reply_to, and
-- the code trigger used.
--
-- The context and message tables are required for the `message()` function to
-- know which server and user to direct a response.
--
-- The `message()` function takes the server connection from the context table,
-- a table of users or channels to reply to, and the text string, then formats
-- the string into an IRC command and sends it to the server.
--
-- `util.pick()` is a helper function that takes a table of items and picks one
-- randomly by default if the number of items is unspecified. It will return
-- the items in a table, so `[1]` will get the one (and in this case, only)
-- value in the table.
function h.hello(cxt, msg)
irc.message(cxt, { msg.reply_to }, util.pick(hello_words)[1])
end
-- Hook up the handlers.
itte_handlers = h

View File

@ -1,5 +1,5 @@
-- Initialise module.
local ramenkan = require("itte")
local hellobot = require("itte")
-- [[
-- Set the config file paths.
@ -20,7 +20,7 @@ local ramenkan = require("itte")
-- Either set confs.prefix, *or* a combination of confs.config and
-- confs.server, but not both.
-- ]]
ramenkan.confs.prefix = "ramenkan"
hellobot.confs.prefix = "hellobot"
-- Call the run function.
ramenkan.run()
hellobot.run()

View File

@ -9,7 +9,7 @@
--
-- This variable is optional, but certain handlers like server disconnection
-- will not work if it is unset.
itte_admins = { globaladmin = "password", }
itte_admins = { demo = "password", }
-- `itte_servers`
@ -22,13 +22,13 @@ itte_servers = {
server_name = {
host = "irc.example.tld",
port = 6667,
channels = { "#channel-name" },
cap = { "sasl" },
channels = { "#channel1", "#channel2" },
cap = nil,
nick = "botnick",
auth_type = nil,
auth_user = "botuser",
auth_pass = "password",
auth_pass = nil,
code_prefix = "!",
admins = { adminuser = "password", },
admins = { demouser = "password", },
},
}

View File

@ -0,0 +1,11 @@
[Unit]
Description=hellobot — an IRC bot
[Service]
WorkingDirectory=/path/to/hellobot
ExecStart=/usr/bin/lua ./hellobot.lua
Restart=always
RestartSec=300
[Install]
WantedBy=default.target

View File

@ -2,10 +2,6 @@
-- Ramen data
-- ---------------------------------------------------------------------------
-- [[
-- This is sample data and could be split into another file.
-- ]]
local ramen = {}
ramen.noodle_broth_types = {
@ -259,7 +255,6 @@ ramen.links = {
-- Ramen functions
-- ---------------------------------------------------------------------------
-- Load the util module early to use some helper functions.
local util = require("itteutil")
@ -373,12 +368,6 @@ end
-- Config
-- ---------------------------------------------------------------------------
-- [[
-- `itte_config`
-- Add custom settings and override the default config settings here.
-- Settings need to be appened to this variable to be collected by the reload
-- function.
-- ]]
itte_config = {
debug = true,
messages = {
@ -428,18 +417,7 @@ itte_config.messages.water = {
-- Handlers
-- ---------------------------------------------------------------------------
-- Load the module.
local irc = require("itte")
--[[
-- `itte_handlers`
-- Define any custom handlers in a new table, then assign the table to it.
-- Handlers can also be added to it directly e.g. `itte_handlers.hello()`.
-- Handler names should correspond to the service code name, e.g. a function
-- `itte_handlers.hello()` will be called within chat as
-- `[command_prefix]hello`, as in `!hello`.
--]]
local h = {}

View File

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

View File

@ -0,0 +1,34 @@
itte_admins = { USER = "PASSWORD" }
itte_servers = {
town = {
host = "localhost",
port = 6697,
channels = { "#ramenkan", "#bots" },
nick = "ramenkan",
auth_user = "USER",
code_prefix = "!",
admins = { USER = "PASSWORD" },
},
casa = {
host = "m455.casa",
port = 6697,
channels = { "#kitchen", "#siliconpals" },
cap = { "sasl", "draft/chathistory" },
nick = "ramenkan",
auth_type = "sasl",
auth_user = "USER",
auth_pass = "PASSWORD",
code_prefix = "!",
admins = { USER = "PASSWORD" },
},
tildechat = {
host = "irc.tilde.chat",
port = 6697,
channels = { "#bots" },
nick = "ramenkan",
auth_user = "USER",
code_prefix = "!",
admins = { USER = "PASSWORD" },
},
}

View File

@ -0,0 +1,11 @@
[Unit]
Description=ramenkan — a ramen-themed IRC bot
[Service]
WorkingDirectory=%h/bin/itte/examples/ramenkan
ExecStart=/usr/bin/lua ./ramenkan.lua
Restart=always
RestartSec=300
[Install]
WantedBy=default.target

View File

@ -126,7 +126,18 @@ function itte.get_config(reload)
-- Update config with value overrides from itte_config
if itte_config ~= nil then
for k, v in pairs(itte_config) do
-- Second-level nested tables
if type(v) == "table" then
for kk, vv in pairs(v) do
if (type(vv) == "table") or (type(vv) == "function") then
itte.config[k] = v
else
itte.config[k][kk] = vv
end
end
else
itte.config[k] = v
end
end
end
if itte_handlers ~= nil then itte.handlers = itte_handlers end