Compare commits

..

No commits in common. "master" and "remove-luasocket" have entirely different histories.

27 changed files with 455 additions and 1978 deletions

View File

@ -1,11 +0,0 @@
on: [push, pull_request]
name: Check & Release
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: lint
uses: Roang-zero1/factorio-mod-luacheck@master
with:
luacheckrc_url: https://raw.githubusercontent.com/minetest-mods/irc/master/.luacheckrc

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/LuaIRC"]
path = irc
url = https://github.com/ShadowNinja/LuaIRC.git

View File

@ -1,14 +0,0 @@
allow_defined_top = true
read_globals = {
"minetest"
}
exclude_files = {
"irc/*",
}
globals = {
"irc",
}

View File

@ -1,66 +1,63 @@
IRC Mod API IRC Mod API
=========== -----------
This file documents the Minetest IRC mod API. This file documents the Minetest IRC mod API.
Basics BASICS
------ ------
In order to allow your mod to interface with this mod, you must add 'irc'
In order to allow your mod to interface with this mod, you must add `irc` (without the quotes) to your mod's 'depends.txt' file.
to your mod's `depends.txt` file.
Reference REFERENCE
--------- ---------
irc.say([name,] message) mt_irc:say([name, ]message)
Sends <message> to either the channel (if <name> is nil or not specified), Sends <message> to either the channel (if <name> is nil or not specified),
or to the given user (if <name> is specified). or to the given user (if <name> is specified).
Example: Example:
irc.say("Hello, Channel!") mt_irc:say("Hello, Channel!")
irc.say("john1234", "How are you?") mt_irc:say("john1234", "How are you?")
irc.register_bot_command(name, cmdDef) mt_irc:register_bot_command(name, cmdDef)
Registers a new bot command named <name>. Registers a new bot command named <name>.
When an user sends a private message to the bot with the command name, the When an user sends a private message to the bot with the command name, the
command's function is called. command's function is called.
Here's the format of a command definition (<cmdDef>): Here's the format of a command definition (<cmdDef>):
cmdDef = { cmdDef = {
params = "<param1> ...", -- A description of the command's parameters params = "<param1> ...", -- A description of the command's parameters
description = "My command", -- A description of what the command does. (one-liner) description = "My command", -- A description of what the command does. (one-liner)
func = function(user, args) func = function(user, param)
-- This function gets called when the command is invoked. -- This function gets called when the command is invoked.
-- <user> is a user table for the user that ran the command. -- <user> is a user table for the user that ran the command.
-- (See the LuaIRC documentation for details.) -- (See the LuaIRC documentation for details.)
-- It contains fields such as 'nick' and 'ident' -- It contains fields such as 'nick' and 'ident'
-- <args> is a string of arguments to the command (may be "") -- <param> is a string of parameters to the command (may be "")
-- This function should return boolean success and a message. end,
end, };
};
Example: Example:
irc.register_bot_command("hello", { mt_irc:register_bot_command("hello", {
params = "", params = "",
description = "Greet user", description = "Greet user",
func = function(user, param) func = function(user, param)
return true, "Hello!" mt_irc:say(user.nick, "Hello!")
end, end,
}); });
irc.joined_players[name] mt_irc.joined_players[name]
This table holds the players who are currently on the channel (may be less This table holds the players who are currently on the channel (may be less
than the players in the game). It is modified by the /part and /join chat than the players in the game). It is modified by the /part and /join chat
commands. commands.
Example: Example:
if irc.joined_players["joe"] then if mt_irc.joined_players["joe"] then
-- Joe is talking on IRC -- Joe is talking on IRC
end end
irc.register_hook(name, func) mt_irc:register_hook(name, func)
Registers a function to be called when an event happens. <name> is the name Registers a function to be called when an event happens. <name> is the name
of the event, and <func> is the function to be called. See HOOKS below of the event, and <func> is the function to be called. See HOOKS below
for more information for more information
Example: Example:
irc.register_hook("OnSend", function(line) mt_irc:register_hook("OnSend", function(line)
print("SEND: "..line) print("SEND: "..line)
end) end)
@ -72,18 +69,18 @@ string.expandvars(string, vars)
are left verbatim in the string. are left verbatim in the string.
Example: Example:
local tpl = "$(foo) $(bar) $(baz)" local tpl = "$(foo) $(bar) $(baz)"
local s = tpl:expandvars({foo=1, bar="Hello"}) local s = tpl:expandvars({ foo=1, bar="Hello" })
assert(s == "1 Hello $(baz)") assert(s == "1 Hello $(baz)")
In addition, all the configuration options decribed in `README.txt` are In addition, all the configuration options decribed in `README.txt' are
available to other mods, though they should be considered read-only. Do available to other mods, though they should be considered "read only". Do
not modify these settings at runtime or you might crash the server! not modify these settings at runtime or you will most likely crash the
server!
Hooks HOOKS
----- ---------
The 'mt_irc:register_hook' function can register functions to be called
The `irc.register_hook` function can register functions to be called
when some events happen. The events supported are the same as the LuaIRC when some events happen. The events supported are the same as the LuaIRC
ones with a few added (mostly for internal use). ones with a few added (mostly for internal use).
See src/LuaIRC/doc/irc.luadoc for more information. See src/LuaIRC/doc/irc.luadoc for more information.

184
README.md
View File

@ -1,139 +1,122 @@
[![](https://github.com/minetest-mods/irc/workflows/Check%20&%20Release/badge.svg)](https://github.com/minetest-mods/irc/actions)
IRC Mod for Minetest IRC Mod for Minetest
==================== ====================
Introduction Introduction
------------ ------------
This mod is just a glue between IRC and Minetest. It provides two-way This mod is just a glue between IRC and Minetest. It provides two-way
communication between the in-game chat, and an arbitrary IRC channel. communication between the in-game chat, and an arbitrary IRC channel.
The forum topic is [here][forum]. The forum topic is at http://minetest.net/forum/viewtopic.php?id=3905
[forum]: https://forum.minetest.net/viewtopic.php?f=11&t=3905
Installing Installing
---------- ----------
Quick one line install for Linux: Quick one line install for linux:
cd <Mods directory> && git clone --recursive https://github.com/minetest-mods/irc.git cd <Mod directory> && git clone https://github.com/kaeza/minetest-irc.git irc && cd irc && git submodule update --init
Please change `<Mods directory>` to fit your installation of Minetest. Please change `<Mod directory>` to fit your installation of minetest.
For more information, see [the wiki][wiki]. For more information, see [the wiki](http://wiki.minetest.net/Installing_mods).
The IRC mod's git repository uses submodules, therefore you will have to run The Minetest IRC mod uses submodules, therefore you will have to run
`git submodule init` when first installing the mod (unless you used `git submodule init` when first installing the mod, and `git submodule update`
`--recursive` as above), and `git submodule update` every time that a submodule every time that a submodule is updated. These steps can be combined as
is updated. These steps can be combined into `git submodule update --init`. `git submodule update --init`.
You'll need to install LuaSocket. You can do so with your package manager on The Minetest IRC mod also requires LuaSocket. This can be installed using your
many distributions, for example: package manager on many distributions, for example on Arch Linux:
# # On Arch Linux:
# pacman -S lua51-socket # pacman -S lua51-socket
# # On Debian/Ubuntu:
# # Debian/Ubuntu's LuaSocket packages are broken, so use LuaRocks.
# apt-get install luarocks
# luarocks install luasocket
You will also need to add IRC to your trusted mods if you haven't disabled mod
security. Here's an example configuration line:
secure.trusted_mods = irc
[wiki]: https://wiki.minetest.net/Installing_mods
Settings Settings
-------- --------
All settings are changed in `minetest.conf`. If any of these settings All settings are changed in `minetest.conf`. If any of these settings
are not set, the default value is used. are not set, the default value is used.
* `irc.server` (string): * `irc.server` (string, default "irc.freenode.net")
The address of the IRC server to connect to. This is the IRC server the mod connects to.
* `irc.channel` (string): * `irc.channel` (string, default "##mt-irc-mod")
The IRC channel to join. The IRC channel to join.
* `irc.interval` (number, default 2.0): * `irc.interval` (number, default 2.0)
This prevents the server from flooding. It should be at This prevents the server from flooding. It should be at
least 2.0 but can be higher. After four messages this much least 2.0 but can be higher. After four messages this much
time must pass between folowing messages. time must pass between folowing messages.
* `irc.nick` (string): * `irc.nick` (string, default "MT-FFFFFF")
Nickname the server uses when it connects to IRC. Nickname used as "proxy" for the in-game chat.
'F' stands for a random base-16 number.
* `irc.password` (string, default nil): * `irc.password` (string, default "")
Password to use when connecting to the server. Password to use when connecting to the server.
* `irc.NSPass` (string, default nil): * `irc.NSPass` (string, default nil)
NickServ password. Don't set this if you use SASL authentication. NickServ password. Don't use this if you use SASL authentication.
* `irc.sasl.pass` (string, default nil): * `irc.SASLPass` (string, default nil)
SASL password, same as nickserv password. SASL password, same as nickserv password.
You should use this instead of NickServ authentication You should use this instead of NickServ authentication
if the server supports it. if the server supports it.
* `irc.sasl.user` (string, default `irc.nick`): * `irc.SASLUser` (string, default irc.nick)
The SASL username. This should normaly be set to your The SASL username. This should normaly be set to your main NickServ account name.
NickServ account name.
* `irc.debug` (boolean, default false): * `irc.debug` (boolean, default false)
Whether to output debug information. Whether to output debug information.
* `irc.disable_auto_connect` (boolean, default false): * `irc.disable_auto_connect` (boolean, default false)
If false, the bot is connected by default. If true, a player with If false, the bot is connected by default. If true, a player with
the 'irc_admin' privilege has to use the `/irc_connect` command to the 'irc_admin' privilege has to use the /irc_connect command to
connect to the server. connect to the server.
* `irc.disable_auto_join` (boolean, default false): * `irc.disable_auto_join` (boolean, default false)
If false, players join the channel automatically upon entering the If false, players join the channel automatically upon entering the
game. If true, each user must manually use the `/join` command to game. If true, each user must manually use the /join command to
join the channel. In any case, the players may use the `/part` join the channel. In any case, the players may use the /part
command to opt-out of being in the channel. command to opt-out of being in the channel.
* `irc.send_join_part` (boolean, default true):
Determines whether to send player join and part messages to the channel.
* `irc.send_join_part` (boolean, default true)
Determines whether to send player join and part messages to the channel.
Usage Usage
----- -----
Once the game is connected to the IRC channel, chatting in-game will send Once the game is connected to the IRC channel, chatting using the 'T' or
messages to the channel, and will be visible by anyone. Also, messages sent F10 hotkeys will send the messages to the channel, and will be visible
to the channel will be visible in-game. by anyone. Also, when someone sends a message to the channel, that text
will be visible in-game.
Messages that begin with `[off]` from in-game or IRC are not sent to the Messages that begin with `[off]` from in-game or IRC are not sent to the
other side. other side.
This mod also adds a few chat commands: This mod also adds a few chat commands:
* `/irc_msg <nick> <message>`: * `/irc_msg <nick> <message>`
Send a private message to a IRC user. Sends a private message to a IRC user.
* `/join`: * `/join`
Join the IRC chat. Join the IRC chat.
* `/part`: * `/part`
Part the IRC chat. Part the IRC chat.
* `/irc_connect`: * `/irc_connect`
Connect the bot manually to the IRC network. Connect the bot manually to the IRC network.
* `/irc_disconnect`: * `/irc_disconnect`
Disconnect the bot manually from the IRC network (this does not Disconnect the bot manually from the IRC network (this does not
shutdown the game). shutdown the game).
* `/irc_reconnect`: * `/irc_reconnect`
Equivalent to `/irc_disconnect` followed by `/irc_connect`. Equivilant to `/irc_disconnect` followed by `/irc_connect`.
You can also send private messages from IRC to in-game players You can also send private messages from IRC to in-game players.
by sending a private message to the bot (set with the `irc.nick`
option above), in the following format: To do it, you must send a private message to the bot (set with
the `irc.nick` option above), in the following format:
@playername message @playername message
@ -142,29 +125,21 @@ a private message from IRC with:
/msg server_nick @mtuser Hello! /msg server_nick @mtuser Hello!
The bot also supports some basic commands, which are invoked by saying To avoid possible misunderstandings (since all in-game players use the
the bot name followed by either a colon or a comma and the command, or same IRC user to converse with you), the "proxy" user will reject any
sending a private message to it. For example: `ServerBot: help whereis`. private messages that are not in that format, and will send back a
nice reminder as a private message.
* `help [<command>]`: The bot also supports some basic commands, which are invoked by sending
Prints help about a command, or a list of supported commands if no a private message to it. Use `!list` to get a list of commands, and
command is given. `!help <command>` to get help about a specific command.
* `uptime`:
Prints the server's running time.
* `whereis <player>`:
Prints the coordinates of the given player.
* `players`:
Lists players currently in the server.
Thanks Thanks
------ ------
I'd like to thank the users who supported this mod both on the Minetest I'd like to thank the users who supported this mod both on the Minetest
Forums and on the `#minetest` channel. In no particular order: Forums and on the #minetest channel. In no particular order:
0gb.us, ShadowNinja, Shaun/kizeren, RAPHAEL, DARGON, Calinou, Exio, 0gb.us, ShadowNinja, Shaun/kizeren, RAPHAEL, DARGON, Calinou, Exio,
vortexlabs/mrtux, marveidemanis, marktraceur, jmf/john\_minetest, vortexlabs/mrtux, marveidemanis, marktraceur, jmf/john\_minetest,
@ -176,7 +151,10 @@ forum topic. Thanks to you all!
License License
------- -------
See `LICENSE.txt` for details. (C) 2012-2013 Diego Martínez <kaeza@users.sf.net>
See LICENSE.txt for licensing information.
The files in the irc directory are part of the LuaIRC project.
See irc/LICENSE.txt for licensing information.
The files in the `irc` directory are part of the LuaIRC project.
See `irc/LICENSE.txt` for details.

View File

@ -1,59 +1,39 @@
irc.bot_commands = {} mt_irc.bot_commands = {}
-- From RFC1459: function mt_irc:check_botcmd(msg)
-- "Because of IRCs scandanavian origin, the characters {}| are local prefix = mt_irc.config.command_prefix
-- considered to be the lower case equivalents of the characters local nick = mt_irc.conn.nick:lower()
-- []\, respectively."
local irctolower = { ["["]="{", ["\\"]="|", ["]"]="}" }
local function irclower(s)
return (s:lower():gsub("[%[%]\\]", irctolower))
end
local function nickequals(nick1, nick2)
return irclower(nick1) == irclower(nick2)
end
function irc.check_botcmd(msg)
local prefix = irc.config.command_prefix
local nick = irc.conn.nick
local text = msg.args[2] local text = msg.args[2]
local nickpart = text:sub(1, #nick) local nickpart = text:sub(1, #nick + 2):lower()
local suffix = text:sub(#nick+1, #nick+2)
-- First check for a nick prefix -- First check for a nick prefix
if nickequals(nickpart, nick) if nickpart == nick..": " or
and (suffix == ": " or suffix == ", ") then nickpart == nick..", " then
irc.bot_command(msg, text:sub(#nick + 3)) self:bot_command(msg, text:sub(#nick + 3))
return true return true
-- Then check for the configured prefix -- Then check for the configured prefix
elseif prefix and text:sub(1, #prefix):lower() == prefix:lower() then elseif prefix and text:sub(1, #prefix):lower() == prefix:lower() then
irc.bot_command(msg, text:sub(#prefix + 1)) self:bot_command(msg, text:sub(#prefix + 1))
return true return true
end end
return false return false
end end
function irc.bot_command(msg, text) function mt_irc:bot_command(msg, text)
-- Remove leading whitespace
text = text:match("^%s*(.*)")
if text:sub(1, 1) == "@" then if text:sub(1, 1) == "@" then
local _, _, player_to, message = text:find("^.([^%s]+)%s(.+)$") local found, _, player_to, message = text:find("^.([^%s]+)%s(.+)$")
if not player_to then if not minetest.get_player_by_name(player_to) then
mt_irc:reply("User '"..player_to.."' is not in the game.")
return return
elseif not minetest.get_player_by_name(player_to) then elseif not mt_irc.joined_players[player_to] then
irc.reply("User '"..player_to.."' is not in the game.") mt_irc:reply("User '"..player_to.."' is not using IRC.")
return
elseif not irc.joined_players[player_to] then
irc.reply("User '"..player_to.."' is not using IRC.")
return return
end end
minetest.chat_send_player(player_to, minetest.chat_send_player(player_to,
minetest.colorize(irc.config.pm_color, "PM from "..msg.user.nick.."@IRC: "..message, false)
"PM from "..msg.user.nick.."@IRC: "..message, false)) mt_irc:reply("Message sent!")
irc.reply("Message sent!")
return return
end end
local pos = text:find(" ", 1, true) local pos = text:find(" ", 1, true)
@ -65,114 +45,111 @@ function irc.bot_command(msg, text)
cmd = text cmd = text
args = "" args = ""
end end
if not irc.bot_commands[cmd] then if not self.bot_commands[cmd] then
irc.reply("Unknown command '"..cmd.."'. Try 'help'." self:reply("Unknown command '"..cmd.."'. Try 'list'."
.." Or use @playername <message> to send a private message") .." Or use @playername <message> to send a private message")
return return
end end
local _, message = irc.bot_commands[cmd].func(msg.user, args) self.bot_commands[cmd].func(msg.user, args)
if message then
irc.reply(message)
end
end end
function irc.register_bot_command(name, def) function mt_irc:register_bot_command(name, def)
if (not def.func) or (type(def.func) ~= "function") then if (not def.func) or (type(def.func) ~= "function") then
error("Erroneous bot command definition. def.func missing.", 2) error("Erroneous bot command definition. def.func missing.", 2)
elseif name:sub(1, 1) == "@" then elseif name:sub(1, 1) == "@" then
error("Erroneous bot command name. Command name begins with '@'.", 2) error("Erroneous bot command name. Command name begins with '@'.", 2)
end end
irc.bot_commands[name] = def self.bot_commands[name] = def
end end
irc.register_bot_command("help", { mt_irc:register_bot_command("help", {
params = "<command>", params = "<command>",
description = "Get help about a command", description = "Get help about a command",
func = function(_, args) func = function(user, args)
if args == "" then if args == "" then
local cmdlist = { } mt_irc:reply("No command name specified. Use 'list' for a list of commands")
for name in pairs(irc.bot_commands) do return
cmdlist[#cmdlist+1] = name
end
return true, "Available commands: "..table.concat(cmdlist, ", ")
.." -- Use 'help <command name>' to get"
.." help about a specific command."
end end
local cmd = irc.bot_commands[args] local cmd = mt_irc.bot_commands[args]
if not cmd then if not cmd then
return false, "Unknown command '"..args.."'." mt_irc:reply("Unknown command '"..cmdname.."'.")
return
end end
return true, ("Usage: %s%s %s -- %s"):format( mt_irc:reply(("Usage: %c%s %s -- %s"):format(
irc.config.command_prefix or "", mt_irc.config.command_prefix,
args, args,
cmd.params or "<no parameters>", cmd.params or "<no parameters>",
cmd.description or "<no description>") cmd.description or "<no description>"))
end end
}) })
irc.register_bot_command("list", { mt_irc:register_bot_command("list", {
params = "", params = "",
description = "List available commands.", description = "List available commands.",
func = function() func = function(user, args)
return false, "The `list` command has been merged into `help`." local cmdlist = "Available commands: "
.." Use `help` with no arguments to get a list." for name, cmd in pairs(mt_irc.bot_commands) do
cmdlist = cmdlist..name..", "
end
mt_irc:reply(cmdlist.." -- Use 'help <command name>' to get"
.." help about a specific command.")
end end
}) })
irc.register_bot_command("whereis", { mt_irc:register_bot_command("whereis", {
params = "<player>", params = "<player>",
description = "Tell the location of <player>", description = "Tell the location of <player>",
func = function(_, args) func = function(user, args)
if args == "" then if args == "" then
return false, "Player name required." mt_irc:bot_help(user, "whereis")
return
end end
local player = minetest.get_player_by_name(args) local player = minetest.env:get_player_by_name(args)
if not player then if player then
return false, "There is no player named '"..args.."'" local fmt = "Player %s is at (%.2f,%.2f,%.2f)"
local pos = player:getpos()
mt_irc:reply(fmt:format(args, pos.x, pos.y, pos.z))
return
end end
local fmt = "Player %s is at (%.2f,%.2f,%.2f)" mt_irc:reply("There is no player named '"..args.."'")
local pos = player:get_pos()
return true, fmt:format(args, pos.x, pos.y, pos.z)
end end
}) })
local starttime = os.time() local starttime = os.time()
irc.register_bot_command("uptime", { mt_irc:register_bot_command("uptime", {
description = "Tell how much time the server has been up", description = "Tell how much time the server has been up",
func = function() func = function(user, args)
local cur_time = os.time() local cur_time = os.time()
local diff = os.difftime(cur_time, starttime) local diff = os.difftime(cur_time, starttime)
local fmt = "Server has been running for %d:%02d:%02d" local fmt = "Server has been running for %d:%02d:%02d"
return true, fmt:format( mt_irc:reply(fmt:format(
math.floor(diff / 60 / 60), math.floor(diff / 60 / 60),
math.floor(diff / 60) % 60, math.mod(math.floor(diff / 60), 60),
math.floor(diff) % 60 math.mod(math.floor(diff), 60)
) ))
end end
}) })
irc.register_bot_command("players", { mt_irc:register_bot_command("players", {
description = "List the players on the server", description = "List the players on the server",
func = function() func = function(user, args)
local players = minetest.get_connected_players() local players = minetest.get_connected_players()
local names = {} local names = {}
for _, player in pairs(players) do for _, player in pairs(players) do
table.insert(names, player:get_player_name()) table.insert(names, player:get_player_name())
end end
return true, string.format("%d connected player(s): %s", mt_irc:reply("Connected players: "
#players, ..table.concat(names, ", "))
table.concat(names, ", ")
)
end end
}) })

View File

@ -4,26 +4,25 @@
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
if irc.connected and irc.config.send_join_part then if mt_irc.connected and mt_irc.config.send_join_part then
irc.say("*** "..name.." joined the game") mt_irc:say("*** "..name.." joined the game")
end end
end) end)
minetest.register_on_leaveplayer(function(player, timed_out) minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
if irc.connected and irc.config.send_join_part then if mt_irc.connected and mt_irc.config.send_join_part then
irc.say("*** "..name.." left the game".. mt_irc:say("*** "..name.." left the game")
(timed_out and " (Timed out)" or ""))
end end
end) end)
minetest.register_on_chat_message(function(name, message) minetest.register_on_chat_message(function(name, message)
if not irc.connected if not mt_irc.connected
or message:sub(1, 1) == "/" or message:sub(1, 1) == "/"
or message:sub(1, 5) == "[off]" or message:sub(1, 5) == "[off]"
or not irc.joined_players[name] or not mt_irc.joined_players[name]
or (not minetest.check_player_privs(name, {shout=true})) then or (not minetest.check_player_privs(name, {shout=true})) then
return return
end end
@ -31,11 +30,11 @@ minetest.register_on_chat_message(function(name, message)
if nl then if nl then
message = message:sub(1, nl - 1) message = message:sub(1, nl - 1)
end end
irc.say(irc.playerMessage(name, minetest.strip_colors(message))) mt_irc:say(mt_irc:playerMessage(name, message))
end) end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
irc.disconnect("Game shutting down.") mt_irc:disconnect("Game shutting down.")
end) end)

View File

@ -10,31 +10,33 @@ minetest.register_chatcommand("irc_msg", {
description = "Send a private message to an IRC user", description = "Send a private message to an IRC user",
privs = {shout=true}, privs = {shout=true},
func = function(name, param) func = function(name, param)
if not irc.connected then if not mt_irc.connected then
return false, "Not connected to IRC. Use /irc_connect to connect." minetest.chat_send_player(name, "Not connected to IRC. Use /irc_connect to connect.")
return
end end
local found, _, toname, message = param:find("^([^%s]+)%s(.+)") local found, _, toname, message = param:find("^([^%s]+)%s(.+)")
if not found then if not found then
return false, "Invalid usage, see /help irc_msg." minetest.chat_send_player(name, "Invalid usage, see /help irc_msg.")
return
end end
local toname_l = toname:lower() local toname_l = toname:lower()
local validNick = false local validNick = false
local hint = "They have to be in the channel" for nick, user in pairs(mt_irc.conn.channels[mt_irc.config.channel].users) do
for nick in pairs(irc.conn.channels[irc.config.channel].users) do
if nick:lower() == toname_l then if nick:lower() == toname_l then
validNick = true validNick = true
break break
end end
end end
if toname_l:find("serv$") or toname_l:find("bot$") then if toname_l:find("serv$") or toname_l:find("bot$") then
hint = "it looks like a bot or service"
validNick = false validNick = false
end end
if not validNick then if not validNick then
return false, "You can not message that user. ("..hint..")" minetest.chat_send_player(name,
"You can not message that user. (Hint: They have to be in the channel)")
return
end end
irc.say(toname, irc.playerMessage(name, message)) mt_irc:say(toname, mt_irc:playerMessage(name, message))
return true, "Message sent!" minetest.chat_send_player(name, "Message sent!")
end end
}) })
@ -42,15 +44,12 @@ minetest.register_chatcommand("irc_msg", {
minetest.register_chatcommand("irc_names", { minetest.register_chatcommand("irc_names", {
params = "", params = "",
description = "List the users in IRC.", description = "List the users in IRC.",
func = function() func = function(name, params)
if not irc.connected then
return false, "Not connected to IRC. Use /irc_connect to connect."
end
local users = { } local users = { }
for nick in pairs(irc.conn.channels[irc.config.channel].users) do for k, v in pairs(mt_irc.conn.channels[mt_irc.config.channel].users) do
table.insert(users, nick) table.insert(users, k)
end end
return true, "Users in IRC: "..table.concat(users, ", ") minetest.chat_send_player(name, "Users in IRC: "..table.concat(users, ", "))
end end
}) })
@ -58,12 +57,13 @@ minetest.register_chatcommand("irc_names", {
minetest.register_chatcommand("irc_connect", { minetest.register_chatcommand("irc_connect", {
description = "Connect to the IRC server.", description = "Connect to the IRC server.",
privs = {irc_admin=true}, privs = {irc_admin=true},
func = function(name) func = function(name, param)
if irc.connected then if mt_irc.connected then
return false, "You are already connected to IRC." minetest.chat_send_player(name, "You are already connected to IRC.")
return
end end
minetest.chat_send_player(name, "IRC: Connecting...") minetest.chat_send_player(name, "IRC: Connecting...")
irc.connect() mt_irc:connect()
end end
}) })
@ -73,13 +73,14 @@ minetest.register_chatcommand("irc_disconnect", {
description = "Disconnect from the IRC server.", description = "Disconnect from the IRC server.",
privs = {irc_admin=true}, privs = {irc_admin=true},
func = function(name, param) func = function(name, param)
if not irc.connected then if not mt_irc.connected then
return false, "Not connected to IRC. Use /irc_connect to connect." minetest.chat_send_player(name, "You are not connected to IRC.")
return
end end
if param == "" then if params == "" then
param = "Manual disconnect by "..name params = "Manual disconnect by "..name
end end
irc.disconnect(param) mt_irc:disconnect(param)
end end
}) })
@ -87,13 +88,13 @@ minetest.register_chatcommand("irc_disconnect", {
minetest.register_chatcommand("irc_reconnect", { minetest.register_chatcommand("irc_reconnect", {
description = "Reconnect to the IRC server.", description = "Reconnect to the IRC server.",
privs = {irc_admin=true}, privs = {irc_admin=true},
func = function(name) func = function(name, param)
if not irc.connected then if not mt_irc.connected then
return false, "Not connected to IRC. Use /irc_connect to connect." minetest.chat_send_player(name, "You are not connected to IRC.")
return
end end
minetest.chat_send_player(name, "IRC: Reconnecting...") mt_irc:disconnect("Reconnecting...")
irc.disconnect("Reconnecting...") mt_irc:connect()
irc.connect()
end end
}) })
@ -103,32 +104,19 @@ minetest.register_chatcommand("irc_quote", {
description = "Send a raw command to the IRC server.", description = "Send a raw command to the IRC server.",
privs = {irc_admin=true}, privs = {irc_admin=true},
func = function(name, param) func = function(name, param)
if not irc.connected then if not mt_irc.connected then
return false, "Not connected to IRC. Use /irc_connect to connect." minetest.chat_send_player(name, "You are not connected to IRC.")
return
end end
irc.queue(param) mt_irc:queue(param)
minetest.chat_send_player(name, "Command sent!") minetest.chat_send_player(name, "Command sent!")
end end
}) })
local oldme = minetest.chatcommands["me"].func local oldme = minetest.chatcommands["me"].func
-- luacheck: ignore
minetest.chatcommands["me"].func = function(name, param, ...) minetest.chatcommands["me"].func = function(name, param, ...)
irc.say(("* %s %s"):format(name, param)) oldme(name, param, ...)
return oldme(name, param, ...) mt_irc:say(("* %s %s"):format(name, param))
end end
if irc.config.send_kicks and minetest.chatcommands["kick"] then
local oldkick = minetest.chatcommands["kick"].func
-- luacheck: ignore
minetest.chatcommands["kick"].func = function(name, param, ...)
local plname, reason = param:match("^(%S+)%s*(.*)$")
if not plname then
return false, "Usage: /kick player [reason]"
end
irc.say(("*** Kicked %s.%s"):format(name,
reason~="" and " Reason: "..reason or ""))
return oldkick(name, param, ...)
end
end

View File

@ -2,45 +2,36 @@
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
irc.config = {} mt_irc.config = {}
local function setting(stype, name, default, required) local function setting(stype, name, default)
local value local value
if minetest.settings and minetest.settings.get and minetest.settings.get_bool then if stype == "bool" then
if stype == "bool" then value = minetest.setting_getbool("irc."..name)
value = minetest.settings:get_bool("irc."..name) elseif stype == "string" then
elseif stype == "string" then value = minetest.setting_get("irc."..name)
value = minetest.settings:get("irc."..name) elseif stype == "number" then
elseif stype == "number" then value = tonumber(minetest.setting_get("irc."..name))
value = tonumber(minetest.settings:get("irc."..name))
end
end end
if value == nil then if value == nil then
if required then
error("Required configuration option irc."..
name.." missing.")
end
value = default value = default
end end
irc.config[name] = value mt_irc.config[name] = value
end end
------------------------- -------------------------
-- BASIC USER SETTINGS -- -- BASIC USER SETTINGS --
------------------------- -------------------------
setting("string", "nick", nil, true) -- Nickname setting("string", "nick") -- Nickname (default "MT-<hash>", <hash> 6 random hexidecimal characters)
setting("string", "server", nil, true) -- Server address to connect to setting("string", "server", "irc.freenode.net") -- Server to connect on joinplayer
setting("number", "port", 6667) -- Server port to connect to setting("number", "port", 6667) -- Port to connect on joinplayer
setting("string", "NSPass") -- NickServ password setting("string", "NSPass") -- NickServ password
setting("string", "sasl.user", irc.config.nick) -- SASL username setting("string", "SASLUser", mt_irc.config.nick) -- SASL username
setting("string", "username", "Minetest") -- Username/ident setting("string", "SASLPass") -- SASL password
setting("string", "realname", "Minetest") -- Real name/GECOS setting("string", "channel", "##mt-irc-mod") -- Channel to join
setting("string", "sasl.pass") -- SASL password
setting("string", "channel", nil, true) -- Channel to join
setting("string", "key") -- Key for the channel setting("string", "key") -- Key for the channel
setting("bool", "send_join_part", true) -- Whether to send player join and part messages to the channel setting("bool", "send_join_part", true) -- Whether to send player join and part messages to the channel
setting("bool", "send_kicks", false) -- Whether to send player kicked messages to the channel
----------------------- -----------------------
-- ADVANCED SETTINGS -- -- ADVANCED SETTINGS --
@ -49,11 +40,20 @@ setting("bool", "send_kicks", false) -- Whether to send player kicked messages
setting("string", "password") -- Server password setting("string", "password") -- Server password
setting("bool", "secure", false) -- Enable a TLS connection, requires LuaSEC setting("bool", "secure", false) -- Enable a TLS connection, requires LuaSEC
setting("number", "timeout", 60) -- Underlying socket timeout in seconds. setting("number", "timeout", 60) -- Underlying socket timeout in seconds.
setting("number", "reconnect", 600) -- Time between reconnection attempts, in seconds.
setting("string", "command_prefix") -- Prefix to use for bot commands setting("string", "command_prefix") -- Prefix to use for bot commands
setting("bool", "debug", false) -- Enable debug output setting("bool", "debug", false) -- Enable debug output
setting("bool", "enable_player_part", true) -- Whether to enable players joining and parting the channel setting("bool", "enable_player_part", true) -- Whether to enable players joining and parting the channel
setting("bool", "auto_join", true) -- Whether to automatically show players in the channel when they join setting("bool", "auto_join", true) -- Whether to automatically show players in the channel when they join
setting("bool", "auto_connect", true) -- Whether to automatically connect to the server on mod load setting("bool", "auto_connect", true) -- Whether to automatically connect to the server on mod load
setting("string", "chat_color", "#339933") -- Color of IRC chat in-game, green by default
setting("string", "pm_color", "#8800AA") -- Color of IRC PMs in-game, purple by default -- Generate a random nickname if one isn't specified.
if not mt_irc.config.nick then
local pr = PseudoRandom(os.time())
-- Workaround for bad distribution in minetest PRNG implementation.
mt_irc.config.nick = ("MT-%02X%02X%02X"):format(
pr:next(0, 255),
pr:next(0, 255),
pr:next(0, 255)
)
end

183
hooks.lua
View File

@ -1,16 +1,17 @@
-- This file is licensed under the terms of the BSD 2-clause license. -- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
local ie = ...
-- MIME is part of LuaSocket mt_irc.hooks = {}
local b64e = ie.require("mime").b64 mt_irc.registered_hooks = {}
irc.hooks = {}
irc.registered_hooks = {}
local stripped_chars = "[\2\31]" -- TODO: Add proper conversion from CP1252 to UTF-8.
local stripped_chars = {"\2", "\31"}
for c = 127, 255 do
table.insert(stripped_chars, string.char(c))
end
stripped_chars = "["..table.concat(stripped_chars, "").."]"
local function normalize(text) local function normalize(text)
-- Strip colors -- Strip colors
@ -20,8 +21,8 @@ local function normalize(text)
end end
function irc.doHook(conn) function mt_irc:doHook(conn)
for name, hook in pairs(irc.registered_hooks) do for name, hook in pairs(self.registered_hooks) do
for _, func in pairs(hook) do for _, func in pairs(hook) do
conn:hook(name, func) conn:hook(name, func)
end end
@ -29,67 +30,59 @@ function irc.doHook(conn)
end end
function irc.register_hook(name, func) function mt_irc:register_hook(name, func)
irc.registered_hooks[name] = irc.registered_hooks[name] or {} self.registered_hooks[name] = self.registered_hooks[name] or {}
table.insert(irc.registered_hooks[name], func) table.insert(self.registered_hooks[name], func)
end end
function irc.hooks.raw(line) function mt_irc.hooks.raw(line)
if irc.config.debug then if mt_irc.config.debug then
print("RECV: "..line) print("RECV: "..line)
end end
end end
function irc.hooks.send(line) function mt_irc.hooks.send(line)
if irc.config.debug then if mt_irc.config.debug then
print("SEND: "..line) print("SEND: "..line)
end end
end end
function irc.hooks.chat(msg) function mt_irc.hooks.chat(msg)
local channel, text = msg.args[1], msg.args[2] local channel, text = msg.args[1], msg.args[2]
if text:sub(1, 1) == string.char(1) then if text:sub(1, 1) == string.char(1) then
irc.conn:invoke("OnCTCP", msg) mt_irc.conn:invoke("OnCTCP", msg)
return return
end end
if channel == irc.conn.nick then if channel == mt_irc.conn.nick then
irc.last_from = msg.user.nick mt_irc.last_from = msg.user.nick
irc.conn:invoke("PrivateMessage", msg) mt_irc.conn:invoke("PrivateMessage", msg)
else else
irc.last_from = channel mt_irc.last_from = channel
irc.conn:invoke("OnChannelChat", msg) mt_irc.conn:invoke("OnChannelChat", msg)
end end
end end
local function get_core_version() function mt_irc.hooks.ctcp(msg)
local status = minetest.get_server_status()
local start_pos = select(2, status:find("version=", 1, true))
local end_pos = status:find(",", start_pos, true)
return status:sub(start_pos + 1, end_pos - 1)
end
function irc.hooks.ctcp(msg)
local text = msg.args[2]:sub(2, -2) -- Remove ^C local text = msg.args[2]:sub(2, -2) -- Remove ^C
local args = text:split(' ') local args = text:split(' ')
local command = args[1]:upper() local command = args[1]:upper()
local function reply(s) local function reply(s)
irc.queue(irc.msgs.notice(msg.user.nick, mt_irc:queue(irc.msgs.notice(msg.user.nick,
("\1%s %s\1"):format(command, s))) ("\1%s %s\1"):format(command, s)))
end end
if command == "ACTION" and msg.args[1] == irc.config.channel then if command == "ACTION" and msg.args[1] == mt_irc.config.channel then
local action = text:sub(8, -1) local action = text:sub(8, -1)
irc.sendLocal(("* %s@IRC %s"):format(msg.user.nick, action)) mt_irc:sendLocal(("* %s@IRC %s"):format(msg.user.nick, action))
elseif command == "VERSION" then elseif command == "VERSION" then
reply(("Minetest version %s, IRC mod version %s.") reply(("Minetest IRC mod version %s.")
:format(get_core_version(), irc.version)) :format(mt_irc.version))
elseif command == "PING" then elseif command == "PING" then
reply(args[2]) reply(args[2])
elseif command == "TIME" then elseif command == "TIME" then
@ -98,18 +91,9 @@ function irc.hooks.ctcp(msg)
end end
function irc.hooks.channelChat(msg) function mt_irc.hooks.channelChat(msg)
local text = normalize(msg.args[2]) local text = normalize(msg.args[2])
irc.check_botcmd(msg)
-- Don't let a user impersonate someone else by using the nick "IRC"
local fake = msg.user.nick:lower():match("^[il|]rc$")
if fake then
irc.sendLocal("<"..msg.user.nick.."@IRC> "..text)
return
end
-- Support multiple servers in a channel better by converting: -- Support multiple servers in a channel better by converting:
-- "<server@IRC> <player> message" into "<player@server> message" -- "<server@IRC> <player> message" into "<player@server> message"
-- "<server@IRC> *** player joined/left the game" into "*** player joined/left server" -- "<server@IRC> *** player joined/left the game" into "*** player joined/left server"
@ -120,64 +104,61 @@ function irc.hooks.channelChat(msg)
text:find("^%*%*%* ([^%s]+) joined the game$") text:find("^%*%*%* ([^%s]+) joined the game$")
local foundleave, _, leavenick = local foundleave, _, leavenick =
text:find("^%*%*%* ([^%s]+) left the game$") text:find("^%*%*%* ([^%s]+) left the game$")
local foundtimedout, _, timedoutnick =
text:find("^%*%*%* ([^%s]+) left the game %(Timed out%)$")
local foundaction, _, actionnick, actionmessage = local foundaction, _, actionnick, actionmessage =
text:find("^%* ([^%s]+) (.*)$") text:find("^%* ([^%s]+) (.*)$")
mt_irc:check_botcmd(msg)
if text:sub(1, 5) == "[off]" then if text:sub(1, 5) == "[off]" then
return return
elseif foundchat then elseif foundchat then
irc.sendLocal(("<%s@%s> %s") mt_irc:sendLocal(("<%s@%s> %s")
:format(chatnick, msg.user.nick, chatmessage)) :format(chatnick, msg.user.nick, chatmessage))
elseif foundjoin then elseif foundjoin then
irc.sendLocal(("*** %s joined %s") mt_irc:sendLocal(("*** %s joined %s")
:format(joinnick, msg.user.nick)) :format(joinnick, msg.user.nick))
elseif foundleave then elseif foundleave then
irc.sendLocal(("*** %s left %s") mt_irc:sendLocal(("*** %s left %s")
:format(leavenick, msg.user.nick)) :format(leavenick, msg.user.nick))
elseif foundtimedout then
irc.sendLocal(("*** %s left %s (Timed out)")
:format(timedoutnick, msg.user.nick))
elseif foundaction then elseif foundaction then
irc.sendLocal(("* %s@%s %s") mt_irc:sendLocal(("* %s@%s %s")
:format(actionnick, msg.user.nick, actionmessage)) :format(actionnick, msg.user.nick, actionmessage))
else else
irc.sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text)) mt_irc:sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text))
end end
end end
function irc.hooks.pm(msg) function mt_irc.hooks.pm(msg)
-- Trim prefix if it is found -- Trim prefix if it is found
local text = msg.args[2] local text = msg.args[2]
local prefix = irc.config.command_prefix local prefix = mt_irc.config.command_prefix
if prefix and text:sub(1, #prefix) == prefix then if prefix and text:sub(1, #prefix) == prefix then
text = text:sub(#prefix + 1) text = text:sub(#prefix + 1)
end end
irc.bot_command(msg, text) mt_irc:bot_command(msg, text)
end end
function irc.hooks.kick(channel, target, prefix, reason) function mt_irc.hooks.kick(channel, target, prefix, reason)
if target == irc.conn.nick then if target == mt_irc.conn.nick then
minetest.chat_send_all("IRC: kicked from "..channel.." by "..prefix.nick..".") minetest.chat_send_all("IRC: kicked from "..channel.." by "..prefix.nick..".")
irc.disconnect("Kicked") mt_irc:disconnect("Kicked")
else else
irc.sendLocal(("-!- %s was kicked from %s by %s [%s]") mt_irc:sendLocal(("-!- %s was kicked from %s by %s [%s]")
:format(target, channel, prefix.nick, reason)) :format(target, channel, prefix.nick, reason))
end end
end end
function irc.hooks.notice(user, target, message) function mt_irc.hooks.notice(user, target, message)
if user and user.nick and target == irc.config.channel then if user.nick and target == mt_irc.config.channel then
irc.sendLocal("-"..user.nick.."@IRC- "..message) mt_irc:sendLocal("-"..user.nick.."@IRC- "..message)
end end
end end
function irc.hooks.mode(user, target, modes, ...) function mt_irc.hooks.mode(user, target, modes, ...)
local by = "" local by = ""
if user.nick then if user.nick then
by = " by "..user.nick by = " by "..user.nick
@ -192,37 +173,37 @@ function irc.hooks.mode(user, target, modes, ...)
end end
function irc.hooks.nick(user, newNick) function mt_irc.hooks.nick(user, newNick)
irc.sendLocal(("-!- %s is now known as %s") mt_irc:sendLocal(("-!- %s is now known as %s")
:format(user.nick, newNick)) :format(user.nick, newNick))
end end
function irc.hooks.join(user, channel) function mt_irc.hooks.join(user, channel)
irc.sendLocal(("-!- %s joined %s") mt_irc:sendLocal(("-!- %s joined %s")
:format(user.nick, channel)) :format(user.nick, channel))
end end
function irc.hooks.part(user, channel, reason) function mt_irc.hooks.part(user, channel, reason)
reason = reason or "" reason = reason or ""
irc.sendLocal(("-!- %s has left %s [%s]") mt_irc:sendLocal(("-!- %s has left %s [%s]")
:format(user.nick, channel, reason)) :format(user.nick, channel, reason))
end end
function irc.hooks.quit(user, reason) function mt_irc.hooks.quit(user, reason)
irc.sendLocal(("-!- %s has quit [%s]") mt_irc:sendLocal(("-!- %s has quit [%s]")
:format(user.nick, reason)) :format(user.nick, reason))
end end
function irc.hooks.disconnect(_, isError) function mt_irc.hooks.disconnect(message, isError)
irc.connected = false mt_irc.connected = false
if isError then if isError then
minetest.log("error", "IRC: Error: Disconnected, reconnecting in one minute.") minetest.log("error", "IRC: Error: Disconnected, reconnecting in one minute.")
minetest.chat_send_all("IRC: Error: Disconnected, reconnecting in one minute.") minetest.chat_send_all("IRC: Error: Disconnected, reconnecting in one minute.")
minetest.after(60, irc.connect, irc) minetest.after(60, mt_irc.connect, mt_irc)
else else
minetest.log("action", "IRC: Disconnected.") minetest.log("action", "IRC: Disconnected.")
minetest.chat_send_all("IRC: Disconnected.") minetest.chat_send_all("IRC: Disconnected.")
@ -230,34 +211,34 @@ function irc.hooks.disconnect(_, isError)
end end
function irc.hooks.preregister(conn) function mt_irc.hooks.preregister(conn)
if not (irc.config["sasl.user"] and irc.config["sasl.pass"]) then return end if not (mt_irc.config.SASLUser and mt_irc.config.SASLPass) then return end
local authString = b64e( local authString = mt_irc.b64e(
("%s\x00%s\x00%s"):format( ("%s\x00%s\x00%s"):format(
irc.config["sasl.user"], mt_irc.config.SASLUser,
irc.config["sasl.user"], mt_irc.config.SASLUser,
irc.config["sasl.pass"]) mt_irc.config.SASLPass)
) )
conn:send("CAP REQ sasl") conn:send("CAP REQ sasl")
conn:send("AUTHENTICATE PLAIN") conn:send("AUTHENTICATE PLAIN")
conn:send("AUTHENTICATE "..authString) conn:send("AUTHENTICATE "..authString)
conn:send("CAP END") --LuaIRC will send CAP END
end end
irc.register_hook("PreRegister", irc.hooks.preregister) mt_irc:register_hook("PreRegister", mt_irc.hooks.preregister)
irc.register_hook("OnRaw", irc.hooks.raw) mt_irc:register_hook("OnRaw", mt_irc.hooks.raw)
irc.register_hook("OnSend", irc.hooks.send) mt_irc:register_hook("OnSend", mt_irc.hooks.send)
irc.register_hook("DoPrivmsg", irc.hooks.chat) mt_irc:register_hook("DoPrivmsg", mt_irc.hooks.chat)
irc.register_hook("OnPart", irc.hooks.part) mt_irc:register_hook("OnPart", mt_irc.hooks.part)
irc.register_hook("OnKick", irc.hooks.kick) mt_irc:register_hook("OnKick", mt_irc.hooks.kick)
irc.register_hook("OnJoin", irc.hooks.join) mt_irc:register_hook("OnJoin", mt_irc.hooks.join)
irc.register_hook("OnQuit", irc.hooks.quit) mt_irc:register_hook("OnQuit", mt_irc.hooks.quit)
irc.register_hook("NickChange", irc.hooks.nick) mt_irc:register_hook("NickChange", mt_irc.hooks.nick)
irc.register_hook("OnCTCP", irc.hooks.ctcp) mt_irc:register_hook("OnCTCP", mt_irc.hooks.ctcp)
irc.register_hook("PrivateMessage", irc.hooks.pm) mt_irc:register_hook("PrivateMessage", mt_irc.hooks.pm)
irc.register_hook("OnNotice", irc.hooks.notice) mt_irc:register_hook("OnNotice", mt_irc.hooks.notice)
irc.register_hook("OnChannelChat", irc.hooks.channelChat) mt_irc:register_hook("OnChannelChat", mt_irc.hooks.channelChat)
irc.register_hook("OnModeChange", irc.hooks.mode) mt_irc:register_hook("OnModeChange", mt_irc.hooks.mode)
irc.register_hook("OnDisconnect", irc.hooks.disconnect) mt_irc:register_hook("OnDisconnect", mt_irc.hooks.disconnect)

196
init.lua
View File

@ -3,51 +3,14 @@
local modpath = minetest.get_modpath(minetest.get_current_modname()) local modpath = minetest.get_modpath(minetest.get_current_modname())
-- Handle mod security if needed package.path =
local ie, req_ie = _G, minetest.request_insecure_environment package.path..";"
if req_ie then ie = req_ie() end
if not ie then
error("The IRC mod requires access to insecure functions in order "..
"to work. Please add the irc mod to your secure.trusted_mods "..
"setting or disable the irc mod.")
end
ie.package.path =
-- To find LuaIRC's init.lua -- To find LuaIRC's init.lua
modpath.."/?/init.lua;" ..modpath.."/?/init.lua;"
-- For LuaIRC to find its files -- For LuaIRC to find its files
..modpath.."/?.lua;" ..modpath.."/?.lua"
..ie.package.path
-- The build of Lua that Minetest comes with only looks for libraries under mt_irc = {
-- /usr/local/share and /usr/local/lib but LuaSocket is often installed under
-- /usr/share and /usr/lib.
if not rawget(_G, "jit") and package.config:sub(1, 1) == "/" then
ie.package.path = ie.package.path..
";/usr/share/lua/5.1/?.lua"..
";/usr/share/lua/5.1/?/init.lua"
ie.package.cpath = ie.package.cpath..
";/usr/lib/lua/5.1/?.so"..
";/usr/lib64/lua/5.1/?.so"
ie.package.cpath = "/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;"..ie.package.cpath
end
-- Temporarily set require so that LuaIRC can access it
local old_require = require
require = ie.require
-- Silence warnings about `module` in `ltn12`.
local old_module = rawget(_G, "module")
rawset(_G, "module", ie.module)
local lib = ie.require("irc")
irc = {
version = "0.2.0", version = "0.2.0",
connected = false, connected = false,
cur_time = 0, cur_time = 0,
@ -55,82 +18,44 @@ irc = {
recent_message_count = 0, recent_message_count = 0,
joined_players = {}, joined_players = {},
modpath = modpath, modpath = modpath,
lib = lib, lib = require("irc"),
} }
local irc = mt_irc.lib
-- Compatibility
rawset(_G, "mt_irc", irc)
local getinfo = debug.getinfo
local warned = { }
local function warn_deprecated(k)
local info = getinfo(3)
local loc = info.source..":"..info.currentline
if warned[loc] then return end
warned[loc] = true
print("COLON: "..tostring(k))
minetest.log("warning", "Deprecated use of colon notation when calling"
.." method `"..tostring(k).."` at "..loc)
end
-- This is a hack.
setmetatable(irc, {
__newindex = function(t, k, v)
if type(v) == "function" then
local f = v
v = function(me, ...)
if rawequal(me, t) then
warn_deprecated(k)
return f(...)
else
return f(me, ...)
end
end
end
rawset(t, k, v)
end,
})
dofile(modpath.."/config.lua") dofile(modpath.."/config.lua")
dofile(modpath.."/messages.lua") dofile(modpath.."/messages.lua")
loadfile(modpath.."/hooks.lua")(ie) dofile(modpath.."/hooks.lua")
dofile(modpath.."/callback.lua") dofile(modpath.."/callback.lua")
dofile(modpath.."/chatcmds.lua") dofile(modpath.."/chatcmds.lua")
dofile(modpath.."/botcmds.lua") dofile(modpath.."/botcmds.lua")
dofile(modpath.."/util.lua")
-- Restore old (safe) functions if mt_irc.config.enable_player_part then
require = old_require
rawset(_G, "module", old_module)
if irc.config.enable_player_part then
dofile(modpath.."/player_part.lua") dofile(modpath.."/player_part.lua")
else else
setmetatable(irc.joined_players, {__index = function() return true end}) setmetatable(mt_irc.joined_players, {__index = function(index) return true end})
end end
minetest.register_privilege("irc_admin", { minetest.register_privilege("irc_admin", {
description = "Allow IRC administrative tasks to be performed.", description = "Allow IRC administrative tasks to be performed.",
give_to_singleplayer = true, give_to_singleplayer = true
give_to_admin = true,
}) })
local stepnum = 0 local stepnum = 0
minetest.register_globalstep(function(dtime) return irc.step(dtime) end) minetest.register_globalstep(function(dtime) return mt_irc:step(dtime) end)
function irc.step() function mt_irc:step(dtime)
if stepnum == 3 then if stepnum == 3 then
if irc.config.auto_connect then if self.config.auto_connect then
irc.connect() self:connect()
end end
end end
stepnum = stepnum + 1 stepnum = stepnum + 1
if not irc.connected then return end if not self.connected then return end
-- Hooks will manage incoming messages and errors -- Hooks will manage incoming messages and errors
local good, err = xpcall(function() irc.conn:think() end, debug.traceback) local good, err = xpcall(function() self.conn:think() end, debug.traceback)
if not good then if not good then
print(err) print(err)
return return
@ -138,89 +63,76 @@ function irc.step()
end end
function irc.connect() function mt_irc:connect()
if irc.connected then if self.connected then
minetest.log("error", "IRC: Ignoring attempt to connect when already connected.") minetest.log("error", "IRC: Ignoring attempt to connect when already connected.")
return return
end end
irc.conn = irc.lib.new({ self.conn = irc.new({
nick = irc.config.nick, nick = self.config.nick,
username = irc.config.username, username = "Minetest",
realname = irc.config.realname, realname = "Minetest",
}) })
irc.doHook(irc.conn) self:doHook(self.conn)
good, message = pcall(function()
-- We need to swap the `require` function again since self.conn:connect({
-- LuaIRC `require`s `ssl` if `irc.secure` is true. host = self.config.server,
old_require = require port = self.config.port,
require = ie.require pass = self.config.password,
timeout = self.config.timeout,
local good, message = pcall(function() secure = self.config.secure
irc.conn:connect({
host = irc.config.server,
port = irc.config.port,
password = irc.config.password,
timeout = irc.config.timeout,
reconnect = irc.config.reconnect,
secure = irc.config.secure
}) })
end) end)
require = old_require
if not good then if not good then
minetest.log("error", ("IRC: Connection error: %s: %s -- Reconnecting in %d seconds...") minetest.log("error", ("IRC: Connection error: %s: %s -- Reconnecting in ten minutes...")
:format(irc.config.server, message, irc.config.reconnect)) :format(self.config.server, message))
minetest.after(irc.config.reconnect, function() irc.connect() end) minetest.after(600, function() self:connect() end)
return return
end end
if irc.config.NSPass then if self.config.NSPass then
irc.conn:queue(irc.msgs.privmsg( self:say("NickServ", "IDENTIFY "..self.config.NSPass)
"NickServ", "IDENTIFY "..irc.config.NSPass))
end end
irc.conn:join(irc.config.channel, irc.config.key) self.conn:join(self.config.channel, self.config.key)
irc.connected = true self.connected = true
minetest.log("action", "IRC: Connected!") minetest.log("action", "IRC: Connected!")
minetest.chat_send_all("IRC: Connected!") minetest.chat_send_all("IRC: Connected!")
end end
function irc.disconnect(message) function mt_irc:disconnect(message)
if irc.connected then if self.connected then
--The OnDisconnect hook will clear irc.connected and print a disconnect message --The OnDisconnect hook will clear self.connected and print a disconnect message
irc.conn:disconnect(message) self.conn:disconnect(message)
end end
end end
function irc.say(to, message) function mt_irc:say(to, message)
if not message then if not message then
message = to message = to
to = irc.config.channel to = self.config.channel
end end
to = to or irc.config.channel to = to or self.config.channel
irc.queue(irc.msgs.privmsg(to, message)) self:queue(irc.msgs.privmsg(to, message))
end end
function irc.reply(message) function mt_irc:reply(message)
if not irc.last_from then if not self.last_from then
return return
end end
message = message:gsub("[\r\n%z]", " \\n ") self:say(self.last_from, message)
irc.say(irc.last_from, message)
end end
function irc.send(msg) function mt_irc:send(msg)
if not irc.connected then return end self.conn:send(msg)
irc.conn:send(msg)
end end
function irc.queue(msg) function mt_irc:queue(msg)
if not irc.connected then return end self.conn:queue(msg)
irc.conn:queue(msg)
end end

1
irc Submodule

@ -0,0 +1 @@
Subproject commit bc9805a33c438431ec64af3e620387f8fd39d2d3

View File

@ -1,26 +0,0 @@
--[[
Lua IRC library
Copyright (c) 2010 Jakob Ovrum
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.]]

View File

@ -1,19 +0,0 @@
[![Build Status](https://travis-ci.org/JakobOvrum/LuaIRC.svg?branch=master)](https://travis-ci.org/JakobOvrum/LuaIRC)
LuaIRC
============
IRC client library for Lua.
Dependencies
-------------
* [LuaSocket](http://w3.impa.br/~diego/software/luasocket/)
**Only required if you want to make use of the TLS support**
* [LuaSec](http://www.inf.puc-rio.br/~brunoos/luasec/)
Documentation
-------------
Documentation can be automatically generated by passing irc.luadoc (in doc/) to [LuaDoc](http://luadoc.luaforge.net/), or pre-generated documentation can be found in the 'gh-pages' branch, which can also be browsed [online](http://jakobovrum.github.com/LuaIRC/doc/modules/irc.html).

View File

@ -1,92 +0,0 @@
local msgs = require("irc.messages")
local meta = {}
function meta:send(msg, ...)
if type(msg) == "table" then
msg = msg:toRFC1459()
else
if select("#", ...) > 0 then
msg = msg:format(...)
end
end
self:invoke("OnSend", msg)
local bytes, err = self.socket:send(msg .. "\r\n")
if not bytes and err ~= "timeout" and err ~= "wantwrite" then
self:invoke("OnDisconnect", err, true)
self:shutdown()
error(err, errlevel)
end
end
function meta:queue(msg)
table.insert(self.messageQueue, msg)
end
local function verify(str, errLevel)
if str:find("^:") or str:find("%s%z") then
error(("malformed parameter '%s' to irc command"):format(str), errLevel)
end
return str
end
function meta:sendChat(target, msg)
-- Split the message into segments if it includes newlines.
for line in msg:gmatch("([^\r\n]+)") do
self:queue(msgs.privmsg(verify(target, 3), line))
end
end
function meta:sendNotice(target, msg)
-- Split the message into segments if it includes newlines.
for line in msg:gmatch("([^\r\n]+)") do
self:queue(msgs.notice(verify(target, 3), line))
end
end
function meta:join(channel, key)
self:queue(msgs.join(
verify(channel, 3),
key and verify(key, 3) or nil))
end
function meta:part(channel, reason)
channel = verify(channel, 3)
self:queue(msgs.part(channel, reason))
if self.track_users then
self.channels[channel] = nil
end
end
function meta:trackUsers(b)
self.track_users = b
if not b then
for k,v in pairs(self.channels) do
self.channels[k] = nil
end
end
end
function meta:setMode(t)
local target = t.target or self.nick
local mode = ""
local add, rem = t.add, t.remove
assert(add or rem, "table contains neither 'add' nor 'remove'")
if add then
mode = table.concat{"+", verify(add, 3)}
end
if rem then
mode = table.concat{mode, "-", verify(rem, 3)}
end
self:queue(msgs.mode(verify(target, 3), mode))
end
return meta

View File

@ -1,198 +0,0 @@
--- LuaIRC is a low-level IRC library for Lua.
-- All functions raise Lua exceptions on error.
--
-- Use <code>new</code> to create a new Connection object.<br/>
-- Example:<br/><br/>
--<code>
--require "irc"<br/>
--local sleep = require "socket".sleep<br/>
--<br/>
--local s = irc.new{nick = "example"}<br/>
--<br/>
--s:hook("OnChat", function(user, channel, message)<br/>
-- print(("[%s] %s: %s"):format(channel, user.nick, message))<br/>
--end)<br/>
--<br/>
--s:connect("irc.example.net")<br/>
--s:join("#example")<br/>
--<br/>
--while true do<br/>
-- s:think()<br/>
-- sleep(0.5)<br/>
--end<br/>
--</code>
module "irc"
--- Create a new Connection object. Use <code>irc:connect</code> to connect to a server.
-- @param user Table with fields <code>nick</code>, <code>username</code> and <code>realname</code>.
-- The <code>nick</code> field is required.
--
-- @return Returns a new Connection object.
-- @see Connection
function new(user)
--- Hook a function to an event.
-- @param name Name of event.
-- @param id Unique tag.
-- @param f Callback function. [defaults to <code>id</code>]
-- @see Hooks
function irc:hook(name, id, f)
--- Remove previous hooked callback.
-- @param name Name of event.
-- @param id Unique tag.
function irc:unhook(name, id)
--- Connect <code>irc</code> to an IRC server.
-- @param host Host address.
-- @param port Server port. [default 6667]
function irc:connect(host, port)
-- @param table Table of connection details
-- @see ConnectOptions
function irc:connect(table)
--- Disconnect <code>irc</code> from the server.
-- @param message Quit message.
function irc:disconnect(message)
--- Handle incoming data for <code>irc</code>, and invoke previously hooked callbacks based on new server input.
-- You should call this in some kind of main loop, or at least often enough to not time out.
function irc:think()
--- Look up user info.
-- @param nick Nick of user to query.
-- @return Table with fields <code>userinfo</code>, <code>node</code>, <code>channels</code> and <code>account</code>.
function irc:whois(nick)
--- Look up topic.
-- Use this to invoke the hooks OnTopic and OnTopicInfo at any time.
-- @param channel Channel to query.
function irc:topic(channel)
--- Send a IRC message to the server.
-- @param msg Message or raw line to send, excluding newline characters.
-- @param ... Format parameters for <code>msg</code>, with <code>string.format</code> semantics. [optional]
function irc:send(msg, ...)
--- Queue Message to be sent to the server.
-- @param msg Message to be sent.
function irc:queue(msg)
--- Send a message to a channel or user.
-- @param target Nick or channel to send to.
-- @param message Message text.
function irc:sendChat(target, message)
--- Send a notice to a channel or user.
-- @param target Nick or channel to send to.
-- @param message Notice text.
function irc:sendNotice(target, message)
--- Join a channel.
-- @param channel Channel to join.
-- @param key Channel key. [optional]
function irc:join(channel, key)
--- Leave a channel.
-- @param channel Channel to leave.
function irc:part(channel)
--- Turn user information tracking on or off. User tracking is enabled by default.
-- @param b Boolean whether or not to track user information.
function irc:trackUsers(b)
--- Add/remove modes for a channel or nick.
-- @param t Table with fields <code>target, nick, add</code> and/or <code>rem</code>. <code>target</code> or <code>nick</code>
-- specifies the user or channel to add/remove modes. <code>add</code> is a list of modes to add to the user or channel.
-- <code>rem</code> is a list of modes to remove from the user or channel.
-- @usage Example which sets +m (moderated) for #channel: <br/>
-- <code>irc:setMode{target = "#channel", add = "m"}</code>
function irc:setMode(t)
--internal
function irc:invoke(name, ...)
function irc:handle(msg)
function irc:shutdown()
--- Table with connection information.
-- @name ConnectOptions
-- @class table
-- @field host Server host name.
-- @field port Server port. [defaults to <code>6667</code>]
-- @field timeout Connect timeout. [defaults to <code>30</code>]
-- @field password Server password.
-- @field secure Boolean to enable TLS connection, pass a params table (described, [luasec]) to control
-- [luasec]: http://www.inf.puc-rio.br/~brunoos/luasec/reference.html
--- Class representing a connection.
-- @name Connection
-- @class table
-- @field authed Boolean indicating whether the connection has completed registration.
-- @field connected Whether the connection is currently connected.
-- @field motd The server's message of the day. Can be nil.
-- @field nick The current nickname.
-- @field realname The real name sent to the server.
-- @field username The username/ident sent to the server.
-- @field socket Raw socket used by the library.
-- @field supports What the server claims to support in it's ISUPPORT message.
--- Class representing an IRC message.
-- @name Message
-- @class table
-- @field args A list of the command arguments
-- @field command The IRC command
-- @field prefix The prefix of the message
-- @field raw A raw IRC line for this message
-- @field tags A table of IRCv3 tags
-- @field user A User object describing the sender of the message
-- Fields may be missing.
-- Messages have the following methods:
-- <ul>
-- <li><code>toRFC1459()</code> - Returns the message serialized in RFC 1459 format.</li>
-- </ul>
--- List of hooks you can use with irc:hook.
-- The parameter list describes the parameters passed to the callback function.
-- <ul>
-- <li><code>PreRegister()</code> - Usefull for requesting capabilities.</li>
-- <li><code>OnRaw(line)</code> - Any non false/nil return value assumes line handled and will not be further processed.</li>
-- <li><code>OnSend(line)</code></li>
-- <li><code>OnDisconnect(message, errorOccurred)</code></li>
-- <li><code>OnChat(user, channel, message)</code></li>
-- <li><code>OnNotice(user, channel, message)</code></li>
-- <li><code>OnJoin(user, channel)</code>*</li>
-- <li><code>OnPart(user, channel)</code>*</li>
-- <li><code>OnQuit(user, message)</code></li>
-- <li><code>NickChange(user, newnick, channel)</code>*†</li>
-- <li><code>NameList(channel, names)</code></li>
-- <li><code>OnTopic(channel, topic)</code></li>
-- <li><code>OnTopicInfo(channel, creator, timeCreated)</code></li>
-- <li><code>OnKick(channel, nick, kicker, reason)</code>* (kicker is a <code>user</code> table)</li>
-- <li><code>OnUserMode(modes)</code></li>
-- <li><code>OnChannelMode(user, channel, modes)</code></li>
-- <li><code>OnModeChange(user, target, modes, ...)</code>* ('...' contains mode options such as banmasks)</li>
-- <li><code>OnCapabilityList(caps)</code></li>
-- <li><code>OnCapabilityAvailable(cap, value)</code> Called only when a capability becomes available or changes.</li>
-- <li><code>OnCapabilitySet(cap, enabled)</code>*</li>
-- <li><code>DoX(msg)</code>* - 'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)</li>
-- </ul>
-- * Event also invoked for yourself.
-- † Channel passed only when user tracking is enabled
-- @name Hooks
-- @class table
--- Table with information about a user.
-- <ul>
-- <li><code>server</code> - Server name.</li>
-- <li><code>nick</code> - User nickname.</li>
-- <li><code>username</code> - User username.</li>
-- <li><code>host</code> - User hostname.</li>
-- <li><code>realname</code> - User real name.</li>
-- <li><code>access</code> - User access, available in channel-oriented callbacks. A table containing boolean fields for each access mode that the server supports. Eg: 'o', and 'v'.</li>
-- </ul>
-- Fields may be missing. To fill them in, enable user tracking and use irc:whois.
-- @name User
-- @class table

View File

@ -1,277 +0,0 @@
local util = require("irc.util")
local msgs = require("irc.messages")
local Message = msgs.Message
local handlers = {}
handlers["PING"] = function(conn, msg)
conn:send(Message({command="PONG", args=msg.args}))
end
local function requestWanted(conn, wanted)
local args = {}
for cap, value in pairs(wanted) do
if type(value) == "string" then
cap = cap .. "=" .. value
end
if not conn.capabilities[cap] then
table.insert(args, cap)
end
end
conn:queue(Message({
command = "CAP",
args = {"REQ", table.concat(args, " ")}
})
)
end
handlers["CAP"] = function(conn, msg)
local cmd = msg.args[2]
if not cmd then
return
end
if cmd == "LS" then
local list = msg.args[3]
local last = false
if list == "*" then
list = msg.args[4]
else
last = true
end
local avail = conn.availableCapabilities
local wanted = conn.wantedCapabilities
for item in list:gmatch("(%S+)") do
local eq = item:find("=", 1, true)
local k, v
if eq then
k, v = item:sub(1, eq - 1), item:sub(eq + 1)
else
k, v = item, true
end
if not avail[k] or avail[k] ~= v then
wanted[k] = conn:invoke("OnCapabilityAvailable", k, v)
end
avail[k] = v
end
if last then
if next(wanted) then
requestWanted(conn, wanted)
end
conn:invoke("OnCapabilityList", conn.availableCapabilities)
end
elseif cmd == "ACK" then
for item in msg.args[3]:gmatch("(%S+)") do
local enabled = (item:sub(1, 1) ~= "-")
local name = enabled and item or item:sub(2)
conn:invoke("OnCapabilitySet", name, enabled)
conn.capabilities[name] = enabled
end
end
end
handlers["001"] = function(conn, msg)
conn.authed = true
conn.nick = msg.args[1]
end
handlers["PRIVMSG"] = function(conn, msg)
conn:invoke("OnChat", msg.user, msg.args[1], msg.args[2])
end
handlers["NOTICE"] = function(conn, msg)
conn:invoke("OnNotice", msg.user, msg.args[1], msg.args[2])
end
handlers["JOIN"] = function(conn, msg)
local channel = msg.args[1]
if conn.track_users then
if msg.user.nick == conn.nick then
conn.channels[channel] = {users = {}}
else
conn.channels[channel].users[msg.user.nick] = msg.user
end
end
conn:invoke("OnJoin", msg.user, msg.args[1])
end
handlers["PART"] = function(conn, msg)
local channel = msg.args[1]
if conn.track_users then
if msg.user.nick == conn.nick then
conn.channels[channel] = nil
else
conn.channels[channel].users[msg.user.nick] = nil
end
end
conn:invoke("OnPart", msg.user, msg.args[1], msg.args[2])
end
handlers["QUIT"] = function(conn, msg)
if conn.track_users then
for chanName, chan in pairs(conn.channels) do
chan.users[msg.user.nick] = nil
end
end
conn:invoke("OnQuit", msg.user, msg.args[1], msg.args[2])
end
handlers["NICK"] = function(conn, msg)
local newNick = msg.args[1]
if conn.track_users then
for chanName, chan in pairs(conn.channels) do
local users = chan.users
local oldinfo = users[msg.user.nick]
if oldinfo then
users[newNick] = oldinfo
users[msg.user.nick] = nil
conn:invoke("NickChange", msg.user, newNick, chanName)
end
end
else
conn:invoke("NickChange", msg.user, newNick)
end
if msg.user.nick == conn.nick then
conn.nick = newNick
end
end
local function needNewNick(conn, msg)
local newnick = conn.nickGenerator(msg.args[2])
conn:queue(irc.msgs.nick(newnick))
end
-- ERR_ERRONEUSNICKNAME (Misspelt but remains for historical reasons)
handlers["432"] = needNewNick
-- ERR_NICKNAMEINUSE
handlers["433"] = needNewNick
-- ERR_UNAVAILRESOURCE
handlers["437"] = function(conn, msg)
if not conn.authed then
needNewNick(conn, msg)
end
end
-- RPL_ISUPPORT
handlers["005"] = function(conn, msg)
local arglen = #msg.args
-- Skip first and last parameters (nick and info)
for i = 2, arglen - 1 do
local item = msg.args[i]
local pos = item:find("=")
if pos then
conn.supports[item:sub(1, pos - 1)] = item:sub(pos + 1)
else
conn.supports[item] = true
end
end
end
-- RPL_MOTDSTART
handlers["375"] = function(conn, msg)
conn.motd = ""
end
-- RPL_MOTD
handlers["372"] = function(conn, msg)
-- MOTD lines have a "- " prefix, strip it.
conn.motd = conn.motd .. msg.args[2]:sub(3) .. '\n'
end
-- NAMES list
handlers["353"] = function(conn, msg)
local chanType = msg.args[2]
local channel = msg.args[3]
local names = msg.args[4]
if conn.track_users then
conn.channels[channel] = conn.channels[channel] or {users = {}, type = chanType}
local users = conn.channels[channel].users
for nick in names:gmatch("(%S+)") do
local access, name = util.parseNick(conn, nick)
users[name] = {access = access}
end
end
end
-- End of NAMES list
handlers["366"] = function(conn, msg)
if conn.track_users then
conn:invoke("NameList", msg.args[2], msg.args[3])
end
end
-- No topic
handlers["331"] = function(conn, msg)
conn:invoke("OnTopic", msg.args[2], nil)
end
handlers["TOPIC"] = function(conn, msg)
conn:invoke("OnTopic", msg.args[1], msg.args[2])
end
handlers["332"] = function(conn, msg)
conn:invoke("OnTopic", msg.args[2], msg.args[3])
end
-- Topic creation info
handlers["333"] = function(conn, msg)
conn:invoke("OnTopicInfo", msg.args[2], msg.args[3], tonumber(msg.args[4]))
end
handlers["KICK"] = function(conn, msg)
conn:invoke("OnKick", msg.args[1], msg.args[2], msg.user, msg.args[3])
end
-- RPL_UMODEIS
-- To answer a query about a client's own mode, RPL_UMODEIS is sent back
handlers["221"] = function(conn, msg)
conn:invoke("OnUserMode", msg.args[2])
end
-- RPL_CHANNELMODEIS
-- The result from common irc servers differs from that defined by the rfc
handlers["324"] = function(conn, msg)
conn:invoke("OnChannelMode", msg.args[2], msg.args[3])
end
handlers["MODE"] = function(conn, msg)
local target = msg.args[1]
local modes = msg.args[2]
local optList = {}
for i = 3, #msg.args do
table.insert(optList, msg.args[i])
end
if conn.track_users and target ~= conn.nick then
local add = true
local argNum = 1
util.updatePrefixModes(conn)
for c in modes:gmatch(".") do
if c == "+" then add = true
elseif c == "-" then add = false
elseif conn.modeprefix[c] then
local nick = optList[argNum]
argNum = argNum + 1
local user = conn.channels[target].users[nick]
user.access = user.access or {}
local access = user.access
access[c] = add
if c == "o" then access.op = add
elseif c == "v" then access.voice = add
end
end
end
end
conn:invoke("OnModeChange", msg.user, target, modes, unpack(optList))
end
handlers["ERROR"] = function(conn, msg)
conn:invoke("OnDisconnect", msg.args[1], true)
conn:shutdown()
error(msg.args[1], 3)
end
return handlers

View File

@ -1,257 +0,0 @@
local socket = require("socket")
local util = require("irc.util")
local handlers = require("irc.handlers")
local msgs = require("irc.messages")
local Message = msgs.Message
local meta = {}
meta.__index = meta
for k, v in pairs(require("irc.asyncoperations")) do
meta[k] = v
end
local meta_preconnect = {}
function meta_preconnect.__index(o, k)
local v = rawget(meta_preconnect, k)
if v == nil and meta[k] ~= nil then
error(("field '%s' is not accessible before connecting"):format(k), 2)
end
return v
end
meta.connected = true
meta_preconnect.connected = false
function new(data)
local o = {
nick = assert(data.nick, "Field 'nick' is required");
username = data.username or "lua";
realname = data.realname or "Lua owns";
nickGenerator = data.nickGenerator or util.defaultNickGenerator;
hooks = {};
track_users = true;
supports = {};
messageQueue = {};
lastThought = 0;
recentMessages = 0;
availableCapabilities = {};
wantedCapabilities = {};
capabilities = {};
}
assert(util.checkNick(o.nick), "Erroneous nickname passed to irc.new")
return setmetatable(o, meta_preconnect)
end
function meta:hook(name, id, f)
f = f or id
self.hooks[name] = self.hooks[name] or {}
self.hooks[name][id] = f
return id or f
end
meta_preconnect.hook = meta.hook
function meta:unhook(name, id)
local hooks = self.hooks[name]
assert(hooks, "no hooks exist for this event")
assert(hooks[id], "hook ID not found")
hooks[id] = nil
end
meta_preconnect.unhook = meta.unhook
function meta:invoke(name, ...)
local hooks = self.hooks[name]
if hooks then
for id, f in pairs(hooks) do
local ret = f(...)
if ret then
return ret
end
end
end
end
function meta_preconnect:connect(_host, _port)
local host, port, password, secure, timeout
if type(_host) == "table" then
host = _host.host
port = _host.port
timeout = _host.timeout
password = _host.password
secure = _host.secure
else
host = _host
port = _port
end
host = host or error("host name required to connect", 2)
port = port or 6667
local s = socket.tcp()
s:settimeout(timeout or 30)
assert(s:connect(host, port))
if secure then
local work, ssl = pcall(require, "ssl")
if not work then
error("LuaSec required for secure connections", 2)
end
local params
if type(secure) == "table" then
params = secure
else
params = {mode = "client", protocol = "any"}
end
s = ssl.wrap(s, params)
local success, errmsg = s:dohandshake()
if not success then
error(("could not make secure connection: %s"):format(errmsg), 2)
end
end
self.socket = s
setmetatable(self, meta)
self:invoke("PreRegister", self)
if password then
self:queue(Message({command="PASS", args={password}}))
end
self:queue(msgs.nick(self.nick))
self:queue(Message({command="USER", args={self.username, "0", "*", self.realname}}))
self.channels = {}
s:settimeout(0)
repeat
self:think()
socket.sleep(0.1)
until self.authed
end
function meta:disconnect(message)
message = message or "Bye!"
self:invoke("OnDisconnect", message, false)
self:send(msgs.quit(message))
self:shutdown()
end
function meta:shutdown()
self.socket:close()
setmetatable(self, meta_preconnect)
end
local function getline(self, errlevel)
local line, err = self.socket:receive("*l")
if not line and err ~= "timeout" and err ~= "wantread" then
self:invoke("OnDisconnect", err, true)
self:shutdown()
error(err, errlevel)
end
return line
end
function meta:think()
while true do
local line = getline(self, 3)
if line and #line > 0 then
if not self:invoke("OnRaw", line) then
self:handle(Message({raw=line}))
end
else
break
end
end
-- Handle outgoing message queue
local diff = socket.gettime() - self.lastThought
self.recentMessages = self.recentMessages - (diff * 2)
if self.recentMessages < 0 then
self.recentMessages = 0
end
for i = 1, #self.messageQueue do
if self.recentMessages > 4 then
break
end
self:send(table.remove(self.messageQueue, 1))
self.recentMessages = self.recentMessages + 1
end
self.lastThought = socket.gettime()
end
function meta:handle(msg)
local handler = handlers[msg.command]
if handler then
handler(self, msg)
end
self:invoke("Do" .. util.capitalize(msg.command), msg)
end
local whoisHandlers = {
["311"] = "userinfo";
["312"] = "node";
["319"] = "channels";
["330"] = "account"; -- Freenode
["307"] = "registered"; -- Unreal
}
function meta:whois(nick)
self:send(msgs.whois(nick))
local result = {}
while true do
local line = getline(self, 3)
if line then
local msg = Message({raw=line})
local handler = whoisHandlers[msg.command]
if handler then
result[handler] = msg.args
elseif msg.command == "318" then
break
else
self:handle(msg)
end
end
end
if result.account then
result.account = result.account[3]
elseif result.registered then
result.account = result.registered[2]
end
return result
end
function meta:topic(channel)
self:queue(msgs.topic(channel))
end
return {
new = new;
Message = Message;
msgs = msgs;
color = util.color;
bold = util.bold;
underline = util.underline;
}

View File

@ -1,207 +0,0 @@
-- Module table
local m = {}
local msg_meta = {}
msg_meta.__index = msg_meta
local function Message(opts)
opts = opts or {}
setmetatable(opts, msg_meta)
if opts.raw then
opts:fromRFC1459(opts.raw)
end
return opts
end
m.Message = Message
local tag_escapes = {
[";"] = "\\:",
[" "] = "\\s",
["\0"] = "\\0",
["\\"] = "\\\\",
["\r"] = "\\r",
["\n"] = "\\n",
}
local tag_unescapes = {}
for x, y in pairs(tag_escapes) do tag_unescapes[y] = x end
function msg_meta:toRFC1459()
local s = ""
if self.tags then
s = s.."@"
for key, value in pairs(self.tags) do
s = s..key
if value ~= true then
value = value:gsub("[; %z\\\r\n]", tag_escapes)
s = s.."="..value
end
s = s..";"
end
-- Strip trailing semicolon
s = s:sub(1, -2)
s = s.." "
end
s = s..self.command
local argnum = #self.args
for i = 1, argnum do
local arg = self.args[i]
local startsWithColon = (arg:sub(1, 1) == ":")
local hasSpace = arg:find(" ")
if i == argnum and (hasSpace or startsWithColon) then
s = s.." :"
else
assert(not hasSpace and not startsWithColon,
"Message arguments can not be "
.."serialized to RFC1459 format")
s = s.." "
end
s = s..arg
end
return s
end
local function parsePrefix(prefix)
local user = {}
user.nick, user.username, user.host = prefix:match("^(.+)!(.+)@(.+)$")
if not user.nick and prefix:find(".", 1, true) then
user.server = prefix
end
return user
end
function msg_meta:fromRFC1459(line)
-- IRCv3 tags
if line:sub(1, 1) == "@" then
self.tags = {}
local space = line:find(" ", 1, true)
-- For each semicolon-delimited section from after
-- the @ character to before the space character.
for tag in line:sub(2, space - 1):gmatch("([^;]+)") do
local eq = tag:find("=", 1, true)
if eq then
self.tags[tag:sub(1, eq - 1)] =
tag:sub(eq + 1):gsub("\\([:s0\\rn])", tag_unescapes)
else
self.tags[tag] = true
end
end
line = line:sub(space + 1)
end
if line:sub(1, 1) == ":" then
local space = line:find(" ", 1, true)
self.prefix = line:sub(2, space - 1)
self.user = parsePrefix(self.prefix)
line = line:sub(space + 1)
end
local pos
self.command, pos = line:match("(%S+)()")
line = line:sub(pos)
self.args = self.args or {}
for pos, param in line:gmatch("()(%S+)") do
if param:sub(1, 1) == ":" then
param = line:sub(pos + 1)
table.insert(self.args, param)
break
end
table.insert(self.args, param)
end
end
function m.privmsg(to, text)
return Message({command="PRIVMSG", args={to, text}})
end
function m.notice(to, text)
return Message({command="NOTICE", args={to, text}})
end
function m.action(to, text)
return Message({command="PRIVMSG", args={to, ("\x01ACTION %s\x01"):format(text)}})
end
function m.ctcp(command, to, args)
s = "\x01"..command
if args then
s = ' '..args
end
s = s..'\x01'
return Message({command="PRIVMSG", args={to, s}})
end
function m.kick(channel, target, reason)
return Message({command="KICK", args={channel, target, reason}})
end
function m.join(channel, key)
return Message({command="JOIN", args={channel, key}})
end
function m.part(channel, reason)
return Message({command="PART", args={channel, reason}})
end
function m.quit(reason)
return Message({command="QUIT", args={reason}})
end
function m.kill(target, reason)
return Message({command="KILL", args={target, reason}})
end
function m.kline(time, mask, reason, operreason)
local args = nil
if time then
args = {time, mask, reason..'|'..operreason}
else
args = {mask, reason..'|'..operreason}
end
return Message({command="KLINE", args=args})
end
function m.whois(nick, server)
local args = nil
if server then
args = {server, nick}
else
args = {nick}
end
return Message({command="WHOIS", args=args})
end
function m.topic(channel, text)
return Message({command="TOPIC", args={channel, text}})
end
function m.invite(channel, target)
return Message({command="INVITE", args={channel, target}})
end
function m.nick(nick)
return Message({command="NICK", args={nick}})
end
function m.mode(target, modes)
-- We have to split the modes parameter because the mode string and
-- each parameter are seperate arguments (The first command is incorrect)
-- MODE foo :+ov Nick1 Nick2
-- MODE foo +ov Nick1 Nick2
local mt = util.split(modes)
return Message({command="MODE", args={target, unpack(mt)}})
end
function m.cap(cmd, ...)
return Message({command="CAP", args={cmd, ...}})
end
return m

View File

@ -1,19 +0,0 @@
#!/bin/bash
if [ "$TRAVIS_REPO_SLUG" == "JakobOvrum/LuaIRC" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then
echo -e "Generating luadoc...\n"
git config --global user.email "travis@travis-ci.org"
git config --global user.name "travis-ci"
git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages > /dev/null
cd gh-pages
git rm -rf ./doc
sh ./generate.sh
git add -f ./doc
git commit -m "Lastest documentation on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages"
git push -fq origin gh-pages > /dev/null
echo -e "Published luadoc to gh-pages.\n"
fi

View File

@ -1,52 +0,0 @@
local select = require("socket").select
local m = {}
local set = {}
set.__index = set
function m.new(t)
t.connections = {}
t.sockets = {}
return setmetatable(t, set)
end
function set:add(connection)
local socket = connection.socket
insert(self.sockets, socket)
self.connections[socket] = connection
insert(self.connections, connection)
end
function set:remove(connection)
local socket = connection.socket
self.connections[socket] = nil
for k, s in ipairs(self.sockets) do
if socket == s then
remove(self.sockets, k)
remove(self.connections, k)
break
end
end
end
function set:select()
local read, write, err = select(self.sockets, nil, self.timeout)
if read then
for k, socket in ipairs(read) do
read[k] = self.connections[socket]
end
end
return read, err
end
-- Select - but if it times out, it returns all connections.
function set:poll()
local read, err = self:select()
return err == "timeout" and self.connections or read
end
return m

View File

@ -1,120 +0,0 @@
-- Module table
local m = {}
function m.updatePrefixModes(conn)
if conn.prefixmode and conn.modeprefix then
return
end
conn.prefixmode = {}
conn.modeprefix = {}
if conn.supports.PREFIX then
local modes, prefixes = conn.supports.PREFIX:match("%(([^%)]*)%)(.*)")
for i = 1, #modes do
conn.prefixmode[prefixes:sub(i, i)] = modes:sub(i, i)
conn.modeprefix[ modes:sub(i, i)] = prefixes:sub(i, i)
end
else
conn.prefixmode['@'] = 'o'
conn.prefixmode['+'] = 'v'
conn.modeprefix['o'] = '@'
conn.modeprefix['v'] = '+'
end
end
function m.parseNick(conn, nick)
local access = {}
m.updatePrefixModes(conn)
local namestart = 1
for i = 1, #nick - 1 do
local c = nick:sub(i, i)
if conn.prefixmode[c] then
access[conn.prefixmode[c]] = true
else
namestart = i
break
end
end
access.op = access.o
access.voice = access.v
local name = nick:sub(namestart)
return access, name
end
-- mIRC markup scheme (de-facto standard)
m.color = {
black = 1,
blue = 2,
green = 3,
red = 4,
lightred = 5,
purple = 6,
brown = 7,
yellow = 8,
lightgreen = 9,
navy = 10,
cyan = 11,
lightblue = 12,
violet = 13,
gray = 14,
lightgray = 15,
white = 16
}
local colByte = string.char(3)
setmetatable(m.color, {__call = function(_, text, colornum)
colornum = (type(colornum) == "string" and
assert(color[colornum], "Invalid color '"..colornum.."'") or
colornum)
return table.concat{colByte, tostring(colornum), text, colByte}
end})
local boldByte = string.char(2)
function m.bold(text)
return boldByte..text..boldByte
end
local underlineByte = string.char(31)
function m.underline(text)
return underlineByte..text..underlineByte
end
function m.checkNick(nick)
return nick:find("^[a-zA-Z_%-%[|%]%^{|}`][a-zA-Z0-9_%-%[|%]%^{|}`]*$") ~= nil
end
function m.defaultNickGenerator(nick)
-- LuaBot -> LuaCot -> LuaCou -> ...
-- We change a random character rather than appending to the
-- nickname as otherwise the new nick could exceed the ircd's
-- maximum nickname length.
local randindex = math.random(1, #nick)
local randchar = string.sub(nick, randindex, randindex)
local b = string.byte(randchar)
b = b + 1
if b < 65 or b > 125 then
b = 65
end
-- Get the halves before and after the changed character
local first = string.sub(nick, 1, randindex - 1)
local last = string.sub(nick, randindex + 1, #nick)
nick = first .. string.char(b) .. last -- Insert the new charachter
return nick
end
function m.capitalize(text)
-- Converts first character to upercase and the rest to lowercase.
-- "PING" -> "Ping" | "hello" -> "Hello" | "123" -> "123"
return text:sub(1, 1):upper()..text:sub(2):lower()
end
function m.split(str, sep)
local t = {}
for s in str:gmatch("%S+") do
table.insert(t, s)
end
return t
end
return m

View File

@ -1,17 +1,13 @@
-- This file is licensed under the terms of the BSD 2-clause license. -- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
irc.msgs = irc.lib.msgs mt_irc.msgs = mt_irc.lib.msgs
function irc.logChat(message) function mt_irc:sendLocal(message)
minetest.log("action", "IRC CHAT: "..message) minetest.chat_send_all(message)
end end
function irc.sendLocal(message) function mt_irc:playerMessage(name, message)
minetest.chat_send_all(minetest.colorize(irc.config.chat_color, message))
irc.logChat(message)
end
function irc.playerMessage(name, message)
return ("<%s> %s"):format(name, message) return ("<%s> %s"):format(name, message)
end end

View File

@ -1,5 +0,0 @@
name = irc
description = """
This mod is just a glue between IRC and Minetest.
It provides two-way communication between the in-game chat, and an arbitrary IRC channel.
"""

View File

@ -2,71 +2,68 @@
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
function irc.player_part(name) function mt_irc:player_part(name)
if not irc.joined_players[name] then if not self.joined_players[name] then
return false, "You are not in the channel" minetest.chat_send_player(name, "IRC: You are not in the channel.")
return
end end
irc.joined_players[name] = nil self.joined_players[name] = nil
return true, "You left the channel" minetest.chat_send_player(name, "IRC: You are now out of the channel.")
end end
function irc.player_join(name) function mt_irc:player_join(name)
if irc.joined_players[name] then if self.joined_players[name] then
return false, "You are already in the channel" minetest.chat_send_player(name, "IRC: You are already in the channel.")
elseif not minetest.get_player_by_name(name) then return
return false, "You need to be in-game to join the channel"
end end
irc.joined_players[name] = true self.joined_players[name] = true
return true, "You joined the channel" minetest.chat_send_player(name, "IRC: You are now in the channel.")
end end
minetest.register_chatcommand("join", { minetest.register_chatcommand("join", {
description = "Join the IRC channel", description = "Join the IRC channel",
privs = {shout=true}, privs = {shout=true},
func = function(name) func = function(name, param)
return irc.player_join(name) mt_irc:player_join(name)
end end
}) })
minetest.register_chatcommand("part", { minetest.register_chatcommand("part", {
description = "Part the IRC channel", description = "Part the IRC channel",
privs = {shout=true}, privs = {shout=true},
func = function(name) func = function(name, param)
return irc.player_part(name) mt_irc:player_part(name)
end end
}) })
minetest.register_chatcommand("who", { minetest.register_chatcommand("who", {
description = "Tell who is currently on the channel", description = "Tell who is currently on the channel",
privs = {}, privs = {},
func = function() func = function(name, param)
local out, n = { }, 0 local s = ""
for plname in pairs(irc.joined_players) do for name, _ in pairs(mt_irc.joined_players) do
n = n + 1 s = s..", "..name
out[n] = plname
end end
table.sort(out) minetest.chat_send_player(name, "Players On Channel:"..s)
return true, n.." player(s) in channel: "..table.concat(out, ", ")
end end
}) })
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
irc.joined_players[name] = irc.config.auto_join mt_irc.joined_players[name] = mt_irc.config.auto_join
end) end)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
irc.joined_players[name] = nil mt_irc.joined_players[name] = nil
end) end)
function irc.sendLocal(message) function mt_irc:sendLocal(message)
for name, _ in pairs(irc.joined_players) do for name, _ in pairs(self.joined_players) do
minetest.chat_send_player(name, minetest.chat_send_player(name, message)
minetest.colorize(irc.config.chat_color, message))
end end
irc.logChat(message)
end end

View File

@ -1,75 +0,0 @@
[Basic]
# Whether to automatically connect to the server on mod load.
# If false, you must use /irc_connect to connect.
irc.auto_connect (Auto-connect on load) bool true
# Nickname for the bot. May only contain characters A-Z, 0-9
# '{', '}', '[', ']', '|', '^', '-', or '_'.
irc.nick (Bot nickname) string Minetest
# Server to connect to.
irc.server (IRC server) string irc.freenode.net
# Server port.
# The standard IRC protocol port is 6667 for regular servers,
# or 6697 for SSL-enabled servers.
# If unsure, leave at 6667.
irc.port (IRC server port) int 6667 1 65535
# Channel the bot joins after connecting.
irc.channel (Channel to join) string ##mt-irc-mod
[Authentication]
# Password for authenticating to NickServ.
# Leave empty to not authenticate with NickServ.
irc.NSPass (NickServ password) string
# IRC server password.
# Leave empty for no password.
irc.password (Server password) string
# Password for joining the channel.
# Leave empty if your channel is not protected.
irc.key (Channel key) string
# Enable TLS connection.
# Requires LuaSEC <https://github.com/brunoos/luasec>.
irc.secure (Use TLS) bool false
# Username for SASL authentication.
# Leave empty to use the nickname.
irc.sasl.user (SASL username) string
# Password for SASL authentication.
# Leave empty to not authenticate via SASL.
irc.sasl.pass (SASL password) string
[Advanced]
# Enable this to make the bot send messages when players join
# or leave the game server.
irc.send_join_part (Send join and part messages) bool true
# Enable this to make the bot send messages when players are kicked.
irc.send_kicks (Send kick messages) bool false
# Underlying socket timeout in seconds.
irc.timeout (Timeout) int 60 1
# Time between reconnection attempts, in seconds.
irc.reconnect (Reconnect delay) int 600 1
# Prefix to use for bot commands.
irc.command_prefix (Command prefix) string
# Enable debug output.
irc.debug (Debug mode) bool false
# Whether to enable players joining and parting the channel.
irc.enable_player_part (Allow player join/part) bool true
# Whether to automatically show players in the channel when they join.
irc.auto_join (Auto join players) bool true

20
util.lua Normal file
View File

@ -0,0 +1,20 @@
--Base 64 encode -- for SASL authentication
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function mt_irc.b64e(data)
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end
-- Requested by Exio
string.expandvars = function(s, vars)
return s:gsub("%$%(([^)]+)%)", vars)
end