Update whole modpack to 'master' branch

'master' branch from @minetest-mods.
This commit is contained in:
Panquesito7 2019-09-23 19:18:24 -05:00
parent 371e956de8
commit c5720217d7
24 changed files with 1015 additions and 488 deletions

14
irc/.luacheckrc Normal file
View File

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

View File

@ -13,14 +13,14 @@ to your mod's `depends.txt` file.
Reference
---------
irc:say([name,] message)
irc.say([name,] message)
Sends <message> to either the channel (if <name> is nil or not specified),
or to the given user (if <name> is specified).
Example:
irc:say("Hello, Channel!")
irc:say("john1234", "How are you?")
irc.say("Hello, Channel!")
irc.say("john1234", "How are you?")
irc:register_bot_command(name, cmdDef)
irc.register_bot_command(name, cmdDef)
Registers a new bot command named <name>.
When an user sends a private message to the bot with the command name, the
command's function is called.
@ -38,7 +38,7 @@ irc:register_bot_command(name, cmdDef)
end,
};
Example:
irc:register_bot_command("hello", {
irc.register_bot_command("hello", {
params = "",
description = "Greet user",
func = function(user, param)
@ -55,12 +55,12 @@ irc.joined_players[name]
-- Joe is talking on IRC
end
irc:register_hook(name, func)
irc.register_hook(name, func)
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
for more information
Example:
irc:register_hook("OnSend", function(line)
irc.register_hook("OnSend", function(line)
print("SEND: "..line)
end)
@ -83,7 +83,7 @@ not modify these settings at runtime or you might crash the server!
Hooks
-----
The `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
ones with a few added (mostly for internal use).
See src/LuaIRC/doc/irc.luadoc for more information.

View File

@ -1,122 +1,138 @@
IRC Mod for Minetest
====================
Introduction
------------
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.
The forum topic is at http://minetest.net/forum/viewtopic.php?id=3905
The forum topic is [here][forum].
[forum]: https://forum.minetest.net/viewtopic.php?f=11&t=3905
Installing
----------
Quick one line install for linux:
Quick one line install for Linux:
cd <Mod directory> && git clone https://github.com/kaeza/minetest-irc.git irc && cd irc && git submodule update --init
cd <Mods directory> && git clone --recursive https://github.com/minetest-mods/irc.git
Please change `<Mod directory>` to fit your installation of minetest.
For more information, see [the wiki](http://wiki.minetest.net/Installing_mods).
Please change `<Mods directory>` to fit your installation of Minetest.
For more information, see [the wiki][wiki].
The Minetest IRC mod uses submodules, therefore you will have to run
`git submodule init` when first installing the mod, and `git submodule update`
every time that a submodule is updated. These steps can be combined as
`git submodule update --init`.
The IRC mod's git repository uses submodules, therefore you will have to run
`git submodule init` when first installing the mod (unless you used
`--recursive` as above), and `git submodule update` every time that a submodule
is updated. These steps can be combined into `git submodule update --init`.
The Minetest IRC mod also requires LuaSocket. This can be installed using your
package manager on many distributions, for example on Arch Linux:
You'll need to install LuaSocket. You can do so with your package manager on
many distributions, for example:
# # On Arch Linux:
# 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
--------
All settings are changed in `minetest.conf`. If any of these settings
are not set, the default value is used.
* `irc.server` (string, default "irc.freenode.net")
This is the IRC server the mod connects to.
* `irc.server` (string):
The address of the IRC server to connect to.
* `irc.channel` (string, default "##mt-irc-mod")
The IRC channel to join.
* `irc.channel` (string):
The IRC channel to join.
* `irc.interval` (number, default 2.0)
This prevents the server from flooding. It should be at
least 2.0 but can be higher. After four messages this much
time must pass between folowing messages.
* `irc.interval` (number, default 2.0):
This prevents the server from flooding. It should be at
least 2.0 but can be higher. After four messages this much
time must pass between folowing messages.
* `irc.nick` (string, default "MT-FFFFFF")
Nickname used as "proxy" for the in-game chat.
'F' stands for a random base-16 number.
* `irc.nick` (string):
Nickname the server uses when it connects to IRC.
* `irc.password` (string, default "")
Password to use when connecting to the server.
* `irc.password` (string, default nil):
Password to use when connecting to the server.
* `irc.NSPass` (string, default nil)
NickServ password. Don't use this if you use SASL authentication.
* `irc.NSPass` (string, default nil):
NickServ password. Don't set this if you use SASL authentication.
* `irc.sasl.pass` (string, default nil)
SASL password, same as nickserv password.
You should use this instead of NickServ authentication
if the server supports it.
* `irc.sasl.pass` (string, default nil):
SASL password, same as nickserv password.
You should use this instead of NickServ authentication
if the server supports it.
* `irc.sasl.user` (string, default `irc.nick`)
The SASL username. This should normaly be set to your main NickServ account name.
* `irc.sasl.user` (string, default `irc.nick`):
The SASL username. This should normaly be set to your
NickServ account name.
* `irc.debug` (boolean, default false)
Whether to output debug information.
* `irc.debug` (boolean, default false):
Whether to output debug information.
* `irc.disable_auto_connect` (boolean, default false)
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
connect to the server.
* `irc.disable_auto_connect` (boolean, default false):
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
connect to the server.
* `irc.disable_auto_join` (boolean, default false)
If false, players join the channel automatically upon entering the
game. If true, each user must manually use the /join command to
join the channel. In any case, the players may use the /part
command to opt-out of being in the channel.
* `irc.disable_auto_join` (boolean, default false):
If false, players join the channel automatically upon entering the
game. If true, each user must manually use the `/join` command to
join the channel. In any case, the players may use the `/part`
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
-----
Once the game is connected to the IRC channel, chatting using the 'T' or
F10 hotkeys will send the messages to the channel, and will be visible
by anyone. Also, when someone sends a message to the channel, that text
will be visible in-game.
Once the game is connected to the IRC channel, chatting in-game will send
messages to the channel, and will be visible by anyone. Also, messages sent
to the channel will be visible in-game.
Messages that begin with `[off]` from in-game or IRC are not sent to the
other side.
This mod also adds a few chat commands:
* `/irc_msg <nick> <message>`
Sends a private message to a IRC user.
* `/irc_msg <nick> <message>`:
Send a private message to a IRC user.
* `/join`
Join the IRC chat.
* `/join`:
Join the IRC chat.
* `/part`
Part the IRC chat.
* `/part`:
Part the IRC chat.
* `/irc_connect`
Connect the bot manually to the IRC network.
* `/irc_connect`:
Connect the bot manually to the IRC network.
* `/irc_disconnect`
Disconnect the bot manually from the IRC network (this does not
shutdown the game).
* `/irc_disconnect`:
Disconnect the bot manually from the IRC network (this does not
shutdown the game).
* `/irc_reconnect`
Equivilant to `/irc_disconnect` followed by `/irc_connect`.
* `/irc_reconnect`:
Equivalent to `/irc_disconnect` followed by `/irc_connect`.
You can also send private messages from IRC to in-game players.
To do it, you must send a private message to the bot (set with
the `irc.nick` option above), in the following format:
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:
@playername message
@ -125,21 +141,29 @@ a private message from IRC with:
/msg server_nick @mtuser Hello!
To avoid possible misunderstandings (since all in-game players use the
same IRC user to converse with you), the "proxy" user will reject any
private messages that are not in that format, and will send back a
nice reminder as a private message.
The bot also supports some basic commands, which are invoked by saying
the bot name followed by either a colon or a comma and the command, or
sending a private message to it. For example: `ServerBot: help whereis`.
The bot also supports some basic commands, which are invoked by sending
a private message to it. Use `!list` to get a list of commands, and
`!help <command>` to get help about a specific command.
* `help [<command>]`:
Prints help about a command, or a list of supported commands if no
command is given.
* `uptime`:
Prints the server's running time.
* `whereis <player>`:
Prints the coordinates of the given player.
* `players`:
Lists players currently in the server.
Thanks
------
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,
vortexlabs/mrtux, marveidemanis, marktraceur, jmf/john\_minetest,
@ -151,10 +175,7 @@ forum topic. Thanks to you all!
License
-------
(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.
See `LICENSE.txt` for details.
The files in the `irc` directory are part of the LuaIRC project.
See `irc/LICENSE.txt` for details.

View File

@ -1,40 +1,59 @@
irc.whereis_timer = {}
irc.whereis_timer_max_limit = 120
irc.bot_commands = {}
function irc:check_botcmd(msg)
-- From RFC1459:
-- "Because of IRCs scandanavian origin, the characters {}| are
-- considered to be the lower case equivalents of the characters
-- []\, 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:lower()
local nick = irc.conn.nick
local text = msg.args[2]
local nickpart = text:sub(1, #nick + 2):lower()
local nickpart = text:sub(1, #nick)
local suffix = text:sub(#nick+1, #nick+2)
-- First check for a nick prefix
if nickpart == nick..": " or
nickpart == nick..", " then
self:bot_command(msg, text:sub(#nick + 3))
if nickequals(nickpart, nick)
and (suffix == ": " or suffix == ", ") then
irc.bot_command(msg, text:sub(#nick + 3))
return true
-- Then check for the configured prefix
elseif prefix and text:sub(1, #prefix):lower() == prefix:lower() then
self:bot_command(msg, text:sub(#prefix + 1))
irc.bot_command(msg, text:sub(#prefix + 1))
return true
end
return false
end
function irc:bot_command(msg, text)
function irc.bot_command(msg, text)
-- Remove leading whitespace
text = text:match("^%s*(.*)")
if text:sub(1, 1) == "@" then
local found, _, player_to, message = text:find("^.([^%s]+)%s(.+)$")
if not minetest.get_player_by_name(player_to) then
irc:reply("User '"..player_to.."' is not in the game.")
local _, _, player_to, message = text:find("^.([^%s]+)%s(.+)$")
if not player_to then
return
elseif not minetest.get_player_by_name(player_to) then
irc.reply("User '"..player_to.."' is not in the game.")
return
elseif not irc.joined_players[player_to] then
irc:reply("User '"..player_to.."' is not using IRC.")
irc.reply("User '"..player_to.."' is not using IRC.")
return
end
minetest.chat_send_player(player_to,
"PM from "..msg.user.nick.."@IRC: "..message, false)
irc:reply("Message sent!")
minetest.colorize(irc.config.pm_color,
"PM from "..msg.user.nick.."@IRC: "..message, false))
irc.reply("Message sent!")
return
end
local pos = text:find(" ", 1, true)
@ -47,35 +66,41 @@ function irc:bot_command(msg, text)
args = ""
end
if not self.bot_commands[cmd] then
self:reply("Unknown command '"..cmd.."'. Try 'list'."
if not irc.bot_commands[cmd] then
irc.reply("Unknown command '"..cmd.."'. Try 'help'."
.." Or use @playername <message> to send a private message")
return
end
local success, message = self.bot_commands[cmd].func(msg.user, args)
local _, message = irc.bot_commands[cmd].func(msg.user, args)
if message then
self:reply(message)
irc.reply(message)
end
end
function irc:register_bot_command(name, def)
function irc.register_bot_command(name, def)
if (not def.func) or (type(def.func) ~= "function") then
error("Erroneous bot command definition. def.func missing.", 2)
elseif name:sub(1, 1) == "@" then
error("Erroneous bot command name. Command name begins with '@'.", 2)
end
self.bot_commands[name] = def
irc.bot_commands[name] = def
end
irc:register_bot_command("help", {
irc.register_bot_command("help", {
params = "<command>",
description = "Get help about a command",
func = function(user, args)
func = function(_, args)
if args == "" then
return false, "No command name specified. Use 'list' for a list of commands."
local cmdlist = { }
for name in pairs(irc.bot_commands) do
cmdlist[#cmdlist+1] = name
end
return true, "Available commands: "..table.concat(cmdlist, ", ")
.." -- Use 'help <command name>' to get"
.." help about a specific command."
end
local cmd = irc.bot_commands[args]
@ -84,7 +109,7 @@ irc:register_bot_command("help", {
end
return true, ("Usage: %s%s %s -- %s"):format(
irc.config.command_prefix,
irc.config.command_prefix or "",
args,
cmd.params or "<no parameters>",
cmd.description or "<no description>")
@ -92,24 +117,20 @@ irc:register_bot_command("help", {
})
irc:register_bot_command("list", {
irc.register_bot_command("list", {
params = "",
description = "List available commands.",
func = function(user, args)
local cmdlist = "Available commands: "
for name, cmd in pairs(irc.bot_commands) do
cmdlist = cmdlist..name..", "
end
return true, cmdlist.." -- Use 'help <command name>' to get"
.." help about a specific command."
func = function()
return false, "The `list` command has been merged into `help`."
.." Use `help` with no arguments to get a list."
end
})
irc:register_bot_command("whereis", {
irc.register_bot_command("whereis", {
params = "<player>",
description = "Tell the location of <player>",
func = function(user, args)
func = function(_, args)
if args == "" then
return false, "Player name required."
end
@ -117,27 +138,17 @@ irc:register_bot_command("whereis", {
if not player then
return false, "There is no player named '"..args.."'"
end
if irc.whereis_timer[user.nick] ~= nil then
local timer_player = os.difftime(os.time(),irc.whereis_timer[user.nick])
if timer_player < irc.whereis_timer_max_limit then
local answer = "Command used too often, retry in %d seconds."
return false,answer:format(irc.whereis_timer_max_limit - timer_player)
end
end
local fmt = "Player %s is at (%.2f,%.2f,%.2f)"
local pos = player:getpos()
irc.whereis_timer[user.nick] = os.time()
minetest.log("action","IRC user ".. user.nick.."!"..user.username.."@"..user.host.." asked for position of player "..player:get_player_name())
minetest.chat_send_player(player:get_player_name(),"IRC user ".. user.nick.."!"..user.username.."@"..user.host.." asked for your position")
local pos = player:get_pos()
return true, fmt:format(args, pos.x, pos.y, pos.z)
end
})
local starttime = os.time()
irc:register_bot_command("uptime", {
irc.register_bot_command("uptime", {
description = "Tell how much time the server has been up",
func = function(user, args)
func = function()
local cur_time = os.time()
local diff = os.difftime(cur_time, starttime)
local fmt = "Server has been running for %d:%02d:%02d"
@ -150,9 +161,9 @@ irc:register_bot_command("uptime", {
})
irc:register_bot_command("players", {
irc.register_bot_command("players", {
description = "List the players on the server",
func = function(user, args)
func = function()
local players = minetest.get_connected_players()
local names = {}
for _, player in pairs(players) do

View File

@ -5,15 +5,16 @@
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
if irc.connected and irc.config.send_join_part then
irc:say("*** "..name.." joined the game")
irc.say("*** "..name.." joined the game")
end
end)
minetest.register_on_leaveplayer(function(player)
minetest.register_on_leaveplayer(function(player, timed_out)
local name = player:get_player_name()
if irc.connected and irc.config.send_join_part then
irc:say("*** "..name.." left the game")
irc.say("*** "..name.." left the game"..
(timed_out and " (Timed out)" or ""))
end
end)
@ -30,11 +31,11 @@ minetest.register_on_chat_message(function(name, message)
if nl then
message = message:sub(1, nl - 1)
end
irc:say(irc:playerMessage(name, message))
irc.say(irc.playerMessage(name, core.strip_colors(message)))
end)
minetest.register_on_shutdown(function()
irc:disconnect("Game shutting down.")
irc.disconnect("Game shutting down.")
end)

View File

@ -11,32 +11,30 @@ minetest.register_chatcommand("irc_msg", {
privs = {shout=true},
func = function(name, param)
if not irc.connected then
minetest.chat_send_player(name, "Not connected to IRC. Use /irc_connect to connect.")
return
return false, "Not connected to IRC. Use /irc_connect to connect."
end
local found, _, toname, message = param:find("^([^%s]+)%s(.+)")
if not found then
minetest.chat_send_player(name, "Invalid usage, see /help irc_msg.")
return
return false, "Invalid usage, see /help irc_msg."
end
local toname_l = toname:lower()
local validNick = false
for nick, user in pairs(irc.conn.channels[irc.config.channel].users) do
local hint = "They have to be in the channel"
for nick in pairs(irc.conn.channels[irc.config.channel].users) do
if nick:lower() == toname_l then
validNick = true
break
end
end
if toname_l:find("serv$") or toname_l:find("bot$") then
hint = "it looks like a bot or service"
validNick = false
end
if not validNick then
minetest.chat_send_player(name,
"You can not message that user. (Hint: They have to be in the channel)")
return
return false, "You can not message that user. ("..hint..")"
end
irc:say(toname, irc:playerMessage(name, message))
minetest.chat_send_player(name, "Message sent!")
irc.say(toname, irc.playerMessage(name, message))
return true, "Message sent!"
end
})
@ -44,16 +42,15 @@ minetest.register_chatcommand("irc_msg", {
minetest.register_chatcommand("irc_names", {
params = "",
description = "List the users in IRC.",
func = function(name, param)
func = function()
if not irc.connected then
minetest.chat_send_player(name, "Not connected to IRC. Use /irc_connect to connect.")
return
return false, "Not connected to IRC. Use /irc_connect to connect."
end
local users = { }
for k, v in pairs(irc.conn.channels[irc.config.channel].users) do
table.insert(users, k)
for nick in pairs(irc.conn.channels[irc.config.channel].users) do
table.insert(users, nick)
end
minetest.chat_send_player(name, "Users in IRC: "..table.concat(users, ", "))
return true, "Users in IRC: "..table.concat(users, ", ")
end
})
@ -61,13 +58,12 @@ minetest.register_chatcommand("irc_names", {
minetest.register_chatcommand("irc_connect", {
description = "Connect to the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
func = function(name)
if irc.connected then
minetest.chat_send_player(name, "You are already connected to IRC.")
return
return false, "You are already connected to IRC."
end
minetest.chat_send_player(name, "IRC: Connecting...")
irc:connect()
irc.connect()
end
})
@ -78,13 +74,12 @@ minetest.register_chatcommand("irc_disconnect", {
privs = {irc_admin=true},
func = function(name, param)
if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
return false, "Not connected to IRC. Use /irc_connect to connect."
end
if param == "" then
param = "Manual disconnect by "..name
end
irc:disconnect(param)
irc.disconnect(param)
end
})
@ -92,13 +87,13 @@ minetest.register_chatcommand("irc_disconnect", {
minetest.register_chatcommand("irc_reconnect", {
description = "Reconnect to the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
func = function(name)
if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
return false, "Not connected to IRC. Use /irc_connect to connect."
end
irc:disconnect("Reconnecting...")
irc:connect()
minetest.chat_send_player(name, "IRC: Reconnecting...")
irc.disconnect("Reconnecting...")
irc.connect()
end
})
@ -109,34 +104,31 @@ minetest.register_chatcommand("irc_quote", {
privs = {irc_admin=true},
func = function(name, param)
if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
return false, "Not connected to IRC. Use /irc_connect to connect."
end
irc:queue(param)
irc.queue(param)
minetest.chat_send_player(name, "Command sent!")
end
})
local oldme = minetest.chatcommands["me"].func
-- luacheck: ignore
minetest.chatcommands["me"].func = function(name, param, ...)
oldme(name, param, ...)
irc:say(("* %s %s"):format(name, param))
irc.say(("* %s %s"):format(name, param))
return oldme(name, param, ...)
end
minetest.register_chatcommand("irc_change_server", {
params = "<servername>",
description = "Change the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
if param == "" then
minetest.chat_send_player(name, "Missing argument servername")
return
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.config.server = param
irc.save_config()
minetest.chat_send_player(name, "New server IRC is \"".. param.."\".")
minetest.chat_send_player(name, "type \"/irc_reconnect\" to reconnect IRC.")
irc.say(("*** Kicked %s.%s"):format(name,
reason~="" and " Reason: "..reason or ""))
return oldkick(name, param, ...)
end
})
end

View File

@ -4,16 +4,22 @@
irc.config = {}
local function setting(stype, name, default)
local function setting(stype, name, default, required)
local value
if stype == "bool" then
value = minetest.setting_getbool("irc."..name)
elseif stype == "string" then
value = minetest.setting_get("irc."..name)
elseif stype == "number" then
value = tonumber(minetest.setting_get("irc."..name))
if minetest.settings and minetest.settings.get and minetest.settings.get_bool then
if stype == "bool" then
value = minetest.settings:get_bool("irc."..name)
elseif stype == "string" then
value = minetest.settings:get("irc."..name)
elseif stype == "number" then
value = tonumber(minetest.settings:get("irc."..name))
end
end
if value == nil then
if required then
error("Required configuration option irc."..
name.." missing.")
end
value = default
end
irc.config[name] = value
@ -23,15 +29,18 @@ end
-- BASIC USER SETTINGS --
-------------------------
setting("string", "nick") -- Nickname (default "MT-<hash>", <hash> 6 random hexidecimal characters)
setting("string", "server", "irc.freenode.net") -- Server to connect on joinplayer
setting("number", "port", 6667) -- Port to connect on joinplayer
setting("string", "nick", nil, true) -- Nickname
setting("string", "server", nil, true) -- Server address to connect to
setting("number", "port", 6667) -- Server port to connect to
setting("string", "NSPass") -- NickServ password
setting("string", "sasl.user", irc.config.nick) -- SASL username
setting("string", "username", "Minetest") -- Username/ident
setting("string", "realname", "Minetest") -- Real name/GECOS
setting("string", "sasl.pass") -- SASL password
setting("string", "channel", "##mt-irc-mod") -- Channel to join
setting("string", "channel", nil, true) -- Channel to join
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_kicks", false) -- Whether to send player kicked messages to the channel
-----------------------
-- ADVANCED SETTINGS --
@ -40,50 +49,11 @@ setting("bool", "send_join_part", true) -- Whether to send player join and par
setting("string", "password") -- Server password
setting("bool", "secure", false) -- Enable a TLS connection, requires LuaSEC
setting("number", "timeout", 60) -- Underlying socket timeout in seconds.
setting("string", "command_prefix", "/") -- Prefix to use for bot commands
setting("number", "reconnect", 600) -- Time between reconnection attempts, in seconds.
setting("string", "command_prefix") -- Prefix to use for bot commands
setting("bool", "debug", false) -- Enable debug output
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_connect", true) -- Whether to automatically connect to the server on mod load
-- Generate a random nickname if one isn't specified.
if not irc.config.nick then
local pr = PseudoRandom(os.time())
-- Workaround for bad distribution in minetest PRNG implementation.
irc.config.nick = ("MT-%02X%02X%02X"):format(
pr:next(0, 255),
pr:next(0, 255),
pr:next(0, 255)
)
end
local config_file = minetest.get_worldpath() .. "/irc.txt"
function irc.save_config()
local input, err = io.open(config_file, "w")
if input then
local conf = {server = irc.config.server}
input:write(minetest.serialize(conf))
input:close()
else
minetest.log("error", "open(" .. config_file .. ", 'w') failed: " .. err)
end
end
function irc.load_config()
local file = io.open(config_file, "r")
local settings = {}
if file then
settings = minetest.deserialize(file:read("*all"))
file:close()
if settings and type(settings) == "table" then
if settings["server"] ~= nil then
irc.config.server = settings["server"]
end
end
end
end
irc.load_config()
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

View File

@ -1,30 +1,27 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
local ie = ...
-- MIME is part of LuaSocket
local b64e = require("mime").b64
local b64e = ie.require("mime").b64
irc.hooks = {}
irc.registered_hooks = {}
-- 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 stripped_chars = "[\2\31]"
local function normalize(text)
-- Strip colors
text = text:gsub("\3[0-9][0-9,]*", "")
return text --:gsub(stripped_chars, "")
return text:gsub(stripped_chars, "")
end
function irc:doHook(conn)
for name, hook in pairs(self.registered_hooks) do
function irc.doHook(conn)
for name, hook in pairs(irc.registered_hooks) do
for _, func in pairs(hook) do
conn:hook(name, func)
end
@ -32,9 +29,9 @@ function irc:doHook(conn)
end
function irc:register_hook(name, func)
self.registered_hooks[name] = self.registered_hooks[name] or {}
table.insert(self.registered_hooks[name], func)
function irc.register_hook(name, func)
irc.registered_hooks[name] = irc.registered_hooks[name] or {}
table.insert(irc.registered_hooks[name], func)
end
@ -83,13 +80,13 @@ function irc.hooks.ctcp(msg)
local command = args[1]:upper()
local function reply(s)
irc:queue(irc.msgs.notice(msg.user.nick,
irc.queue(irc.msgs.notice(msg.user.nick,
("\1%s %s\1"):format(command, s)))
end
if command == "ACTION" and msg.args[1] == irc.config.channel then
local action = text:sub(8, -1)
irc:sendLocal(("* %s@IRC %s"):format(msg.user.nick, action))
irc.sendLocal(("* %s@IRC %s"):format(msg.user.nick, action))
elseif command == "VERSION" then
reply(("Minetest version %s, IRC mod version %s.")
:format(get_core_version(), irc.version))
@ -104,11 +101,12 @@ end
function irc.hooks.channelChat(msg)
local text = normalize(msg.args[2])
irc:check_botcmd(msg)
irc.check_botcmd(msg)
-- Don't let a user impersonate someone else by using the nick "IRC"
if msg.user.nick == "IRC" then
irc:sendLocal("<IRC@IRC> "..text)
local fake = msg.user.nick:lower():match("^[il|]rc$")
if fake then
irc.sendLocal("<"..msg.user.nick.."@IRC> "..text)
return
end
@ -122,25 +120,30 @@ function irc.hooks.channelChat(msg)
text:find("^%*%*%* ([^%s]+) joined the game$")
local foundleave, _, leavenick =
text:find("^%*%*%* ([^%s]+) left the game$")
local foundtimedout, _, timedoutnick =
text:find("^%*%*%* ([^%s]+) left the game %(Timed out%)$")
local foundaction, _, actionnick, actionmessage =
text:find("^%* ([^%s]+) (.*)$")
if text:sub(1, 5) == "[off]" then
return
elseif foundchat then
irc:sendLocal(("<%s@%s> %s")
irc.sendLocal(("<%s@%s> %s")
:format(chatnick, msg.user.nick, chatmessage))
elseif foundjoin then
irc:sendLocal(("*** %s joined %s")
irc.sendLocal(("*** %s joined %s")
:format(joinnick, msg.user.nick))
elseif foundleave then
irc:sendLocal(("*** %s left %s")
irc.sendLocal(("*** %s left %s")
:format(leavenick, msg.user.nick))
elseif foundtimedout then
irc.sendLocal(("*** %s left %s (Timed out)")
:format(timedoutnick, msg.user.nick))
elseif foundaction then
irc:sendLocal(("* %s@%s %s")
irc.sendLocal(("* %s@%s %s")
:format(actionnick, msg.user.nick, actionmessage))
else
irc:sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text))
irc.sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text))
end
end
@ -152,16 +155,16 @@ function irc.hooks.pm(msg)
if prefix and text:sub(1, #prefix) == prefix then
text = text:sub(#prefix + 1)
end
irc:bot_command(msg, text)
irc.bot_command(msg, text)
end
function irc.hooks.kick(channel, target, prefix, reason)
if target == irc.conn.nick then
minetest.chat_send_all("IRC: kicked from "..channel.." by "..prefix.nick..".")
irc:disconnect("Kicked")
irc.disconnect("Kicked")
else
irc:sendLocal(("-!- %s was kicked from %s by %s [%s]")
irc.sendLocal(("-!- %s was kicked from %s by %s [%s]")
:format(target, channel, prefix.nick, reason))
end
end
@ -169,7 +172,7 @@ end
function irc.hooks.notice(user, target, message)
if user.nick and target == irc.config.channel then
irc:sendLocal("-"..user.nick.."@IRC- "..message)
irc.sendLocal("-"..user.nick.."@IRC- "..message)
end
end
@ -190,31 +193,31 @@ end
function irc.hooks.nick(user, newNick)
irc:sendLocal(("-!- %s is now known as %s")
irc.sendLocal(("-!- %s is now known as %s")
:format(user.nick, newNick))
end
function irc.hooks.join(user, channel)
irc:sendLocal(("-!- %s joined %s")
irc.sendLocal(("-!- %s joined %s")
:format(user.nick, channel))
end
function irc.hooks.part(user, channel, reason)
reason = reason or ""
irc:sendLocal(("-!- %s has left %s [%s]")
irc.sendLocal(("-!- %s has left %s [%s]")
:format(user.nick, channel, reason))
end
function irc.hooks.quit(user, reason)
irc:sendLocal(("-!- %s has quit [%s]")
irc.sendLocal(("-!- %s has quit [%s]")
:format(user.nick, reason))
end
function irc.hooks.disconnect(message, isError)
function irc.hooks.disconnect(_, isError)
irc.connected = false
if isError then
minetest.log("error", "IRC: Error: Disconnected, reconnecting in one minute.")
@ -238,23 +241,23 @@ function irc.hooks.preregister(conn)
conn:send("CAP REQ sasl")
conn:send("AUTHENTICATE PLAIN")
conn:send("AUTHENTICATE "..authString)
--LuaIRC will send CAP END
conn:send("CAP END")
end
irc:register_hook("PreRegister", irc.hooks.preregister)
irc:register_hook("OnRaw", irc.hooks.raw)
irc:register_hook("OnSend", irc.hooks.send)
irc:register_hook("DoPrivmsg", irc.hooks.chat)
irc:register_hook("OnPart", irc.hooks.part)
irc:register_hook("OnKick", irc.hooks.kick)
irc:register_hook("OnJoin", irc.hooks.join)
irc:register_hook("OnQuit", irc.hooks.quit)
irc:register_hook("NickChange", irc.hooks.nick)
irc:register_hook("OnCTCP", irc.hooks.ctcp)
irc:register_hook("PrivateMessage", irc.hooks.pm)
irc:register_hook("OnNotice", irc.hooks.notice)
irc:register_hook("OnChannelChat", irc.hooks.channelChat)
irc:register_hook("OnModeChange", irc.hooks.mode)
irc:register_hook("OnDisconnect", irc.hooks.disconnect)
irc.register_hook("PreRegister", irc.hooks.preregister)
irc.register_hook("OnRaw", irc.hooks.raw)
irc.register_hook("OnSend", irc.hooks.send)
irc.register_hook("DoPrivmsg", irc.hooks.chat)
irc.register_hook("OnPart", irc.hooks.part)
irc.register_hook("OnKick", irc.hooks.kick)
irc.register_hook("OnJoin", irc.hooks.join)
irc.register_hook("OnQuit", irc.hooks.quit)
irc.register_hook("NickChange", irc.hooks.nick)
irc.register_hook("OnCTCP", irc.hooks.ctcp)
irc.register_hook("PrivateMessage", irc.hooks.pm)
irc.register_hook("OnNotice", irc.hooks.notice)
irc.register_hook("OnChannelChat", irc.hooks.channelChat)
irc.register_hook("OnModeChange", irc.hooks.mode)
irc.register_hook("OnDisconnect", irc.hooks.disconnect)

View File

@ -3,25 +3,49 @@
local modpath = minetest.get_modpath(minetest.get_current_modname())
package.path =
-- Handle mod security if needed
local ie, req_ie = _G, minetest.request_insecure_environment
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
modpath.."/?/init.lua;"
-- For LuaIRC to find its files
..modpath.."/?.lua;"
..package.path
..ie.package.path
-- The build of Lua that Minetest comes with only looks for libraries under
-- /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
package.path = package.path..
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"
package.cpath = package.cpath..
-- ";/usr/lib/lua/5.1/?.so"
";/usr/lib/x86_64-linux-gnu/lua/5.1/?.so"
ie.package.cpath = ie.package.cpath..
";/usr/lib/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",
connected = false,
@ -30,45 +54,82 @@ irc = {
recent_message_count = 0,
joined_players = {},
modpath = modpath,
lib = require("irc"),
lib = lib,
}
-- Compatibility
mt_irc = irc
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.."/messages.lua")
dofile(modpath.."/hooks.lua")
loadfile(modpath.."/hooks.lua")(ie)
dofile(modpath.."/callback.lua")
dofile(modpath.."/chatcmds.lua")
dofile(modpath.."/botcmds.lua")
-- Restore old (safe) functions
require = old_require
rawset(_G, "module", old_module)
if irc.config.enable_player_part then
dofile(modpath.."/player_part.lua")
else
setmetatable(irc.joined_players, {__index = function(index) return true end})
setmetatable(irc.joined_players, {__index = function() return true end})
end
minetest.register_privilege("irc_admin", {
description = "Allow IRC administrative tasks to be performed.",
give_to_singleplayer = true
give_to_singleplayer = true,
give_to_admin = true,
})
local stepnum = 0
minetest.register_globalstep(function(dtime) return irc:step(dtime) end)
minetest.register_globalstep(function(dtime) return irc.step(dtime) end)
function irc:step(dtime)
function irc.step()
if stepnum == 3 then
if self.config.auto_connect then
self:connect()
if irc.config.auto_connect then
irc.connect()
end
end
stepnum = stepnum + 1
if not self.connected then return end
if not irc.connected then return end
-- Hooks will manage incoming messages and errors
local good, err = xpcall(function() self.conn:think() end, debug.traceback)
local good, err = xpcall(function() irc.conn:think() end, debug.traceback)
if not good then
print(err)
return
@ -76,79 +137,89 @@ function irc:step(dtime)
end
function irc:connect()
if self.connected then
function irc.connect()
if irc.connected then
minetest.log("error", "IRC: Ignoring attempt to connect when already connected.")
return
end
self.conn = irc.lib.new({
nick = self.config.nick,
username = "Minetest",
realname = "Minetest",
irc.conn = irc.lib.new({
nick = irc.config.nick,
username = irc.config.username,
realname = irc.config.realname,
})
self:doHook(self.conn)
irc.doHook(irc.conn)
-- We need to swap the `require` function again since
-- LuaIRC `require`s `ssl` if `irc.secure` is true.
old_require = require
require = ie.require
local good, message = pcall(function()
self.conn:connect({
host = self.config.server,
port = self.config.port,
password = self.config.password,
timeout = self.config.timeout,
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)
require = old_require
if not good then
minetest.log("error", ("IRC: Connection error: %s: %s -- Reconnecting in ten minutes...")
:format(self.config.server, message))
minetest.after(600, function() self:connect() end)
minetest.log("error", ("IRC: Connection error: %s: %s -- Reconnecting in %d seconds...")
:format(irc.config.server, message, irc.config.reconnect))
minetest.after(irc.config.reconnect, function() irc.connect() end)
return
end
if self.config.NSPass then
self:say("NickServ", "IDENTIFY "..self.config.NSPass)
if irc.config.NSPass then
irc.conn:queue(irc.msgs.privmsg(
"NickServ", "IDENTIFY "..irc.config.NSPass))
end
self.conn:join(self.config.channel, self.config.key)
self.connected = true
irc.conn:join(irc.config.channel, irc.config.key)
irc.connected = true
minetest.log("action", "IRC: Connected!")
minetest.chat_send_all("IRC: Connected!")
end
function irc:disconnect(message)
if self.connected then
--The OnDisconnect hook will clear self.connected and print a disconnect message
self.conn:disconnect(message)
function irc.disconnect(message)
if irc.connected then
--The OnDisconnect hook will clear irc.connected and print a disconnect message
irc.conn:disconnect(message)
end
end
function irc:say(to, message)
function irc.say(to, message)
if not message then
message = to
to = self.config.channel
to = irc.config.channel
end
to = to or self.config.channel
to = to or irc.config.channel
self:queue(irc.msgs.privmsg(to, message))
irc.queue(irc.msgs.privmsg(to, message))
end
function irc:reply(message)
if not self.last_from then
function irc.reply(message)
if not irc.last_from then
return
end
message = message:gsub("[\r\n%z]", " \\n ")
self:say(self.last_from, message)
irc.say(irc.last_from, message)
end
function irc:send(msg)
if not self.connected then return end
self.conn:send(msg)
function irc.send(msg)
if not irc.connected then return end
irc.conn:send(msg)
end
function irc:queue(msg)
if not self.connected then return end
self.conn:queue(msg)
function irc.queue(msg)
if not irc.connected then return end
irc.conn:queue(msg)
end

15
irc/irc/.travis.yml Normal file
View File

@ -0,0 +1,15 @@
language: lua
notifications:
email: false
env:
global:
- secure: "kFhU+DZjhq/KbDt0DIDWnlskXMa12miNelmhhy30fQGgVIdiibDGKMNGyLahWp8CnPu1DARb5AZWK2TDfARdnURT2pgcsG83M7bYIY6cR647BWjL7oAhJ6CYEzTWJTBjeUjpN/o4vIgfXSDR0c7vboDi7Xz8ilfrBujPL2Oi/og="
install:
- sudo apt-get -y update
- sudo apt-get -y install lua5.1 luadoc
script:
- ./push-luadoc.sh

View File

@ -1,7 +1,8 @@
[![Build Status](https://travis-ci.org/JakobOvrum/LuaIRC.svg?branch=master)](https://travis-ci.org/JakobOvrum/LuaIRC)
LuaIRC
============
IRC library for Lua.
IRC client library for Lua.
Dependencies
-------------

View File

@ -1,6 +1,6 @@
local irc = require("irc.main")
local msgs = require("irc.messages")
local meta = irc.meta
local meta = {}
function meta:send(msg, ...)
if type(msg) == "table" then
@ -36,26 +36,26 @@ 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(irc.msgs.privmsg(verify(target, 3), line))
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(irc.msgs.notice(verify(target, 3), line))
self:queue(msgs.notice(verify(target, 3), line))
end
end
function meta:join(channel, key)
self:queue(irc.msgs.join(
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(irc.msgs.part(channel, reason))
self:queue(msgs.part(channel, reason))
if self.track_users then
self.channels[channel] = nil
end
@ -85,6 +85,8 @@ function meta:setMode(t)
mode = table.concat{mode, "-", verify(rem, 3)}
end
self:queue(irc.msgs.mode(verify(target, 3), mode))
self:queue(msgs.mode(verify(target, 3), mode))
end
return meta

View File

@ -1,7 +1,7 @@
--- LuaIRC is a low-level IRC library for Lua.
-- All functions raise Lua exceptions on error.
--
-- Use <code>new</code> to create a new IRC object.<br/>
-- Use <code>new</code> to create a new Connection object.<br/>
-- Example:<br/><br/>
--<code>
--require "irc"<br/>
@ -24,11 +24,12 @@
module "irc"
--- Create a new IRC object. Use <code>irc:connect</code> to connect to a server.
--- 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 <code>irc</code> object.
-- @return Returns a new Connection object.
-- @see Connection
function new(user)
--- Hook a function to an event.
@ -46,10 +47,10 @@ 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(server, port)
function irc:connect(host, port)
-- @param table Table of connection details
-- @see Connection
-- @see ConnectOptions
function irc:connect(table)
--- Disconnect <code>irc</code> from the server.
@ -116,16 +117,26 @@ function irc:handle(msg)
function irc:shutdown()
--- Table with connection information.
-- <ul>
-- <li><code>host</code> - Server host name.</li>
-- <li><code>port</code> - Server port. [defaults to <code>6667</code>]</li>
-- <li><code>timeout</code> - Connect timeout. [defaults to <code>30</code>]</li>
-- <li><code>password</code> - Server password.</li>
-- <li><code>secure</code> - Boolean to enable TLS connection, pass a params table (described, [luasec]) to control</li>
-- </ul>
-- @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
@ -145,8 +156,8 @@ function irc:shutdown()
--- List of hooks you can use with irc:hook.
-- The parameter list describes the parameters passed to the callback function.
-- <ul>
-- <li><code>PreRegister(connection)</code>Useful for CAP commands and SASL.</li>
-- <li><code>OnRaw(line) - (any non false/nil return value assumes line handled and will not be further processed)</code></li>
-- <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>
@ -162,7 +173,10 @@ function irc:shutdown()
-- <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>DoX(msg)</code>'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)</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

View File

@ -1,10 +1,72 @@
local irc = require("irc.main")
local util = require("irc.util")
local msgs = require("irc.messages")
local Message = msgs.Message
irc.handlers = {}
local handlers = irc.handlers
local handlers = {}
handlers["PING"] = function(conn, msg)
conn:send(irc.Message({command="PONG", args=msg.args}))
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)
@ -16,7 +78,6 @@ 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
@ -71,15 +132,13 @@ handlers["NICK"] = function(conn, msg)
conn:invoke("NickChange", msg.user, newNick)
end
if msg.user.nick == conn.nick then
conn.nick = newnick
conn.nick = newNick
end
end
local function needNewNick(conn, msg)
local newnick = conn.nickGenerator(msg.args[2])
if msg.nick then
conn:queue(msg.nick(newnick))
end -- Ugly fix
conn:queue(irc.msgs.nick(newnick))
end
-- ERR_ERRONEUSNICKNAME (Misspelt but remains for historical reasons)
@ -88,6 +147,13 @@ 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
@ -124,7 +190,7 @@ handlers["353"] = function(conn, msg)
local users = conn.channels[channel].users
for nick in names:gmatch("(%S+)") do
local access, name = irc.parseNick(conn, nick)
local access, name = util.parseNick(conn, nick)
users[name] = {access = access}
end
end
@ -181,7 +247,7 @@ handlers["MODE"] = function(conn, msg)
if conn.track_users and target ~= conn.nick then
local add = true
local argNum = 1
irc.updatePrefixModes(conn)
util.updatePrefixModes(conn)
for c in modes:gmatch(".") do
if c == "+" then add = true
elseif c == "-" then add = false
@ -207,3 +273,5 @@ handlers["ERROR"] = function(conn, msg)
error(msg.args[1], 3)
end
return handlers

View File

@ -1,9 +1,257 @@
local socket = require("socket")
local util = require("irc.util")
local handlers = require("irc.handlers")
local msgs = require("irc.messages")
local Message = msgs.Message
local irc = require("irc.main")
require("irc.util")
require("irc.asyncoperations")
require("irc.handlers")
require("irc.messages")
local meta = {}
meta.__index = meta
return irc
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 = "tlsv1"}
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,12 +1,11 @@
local irc = require("irc.main")
irc.msgs = {}
local msgs = irc.msgs
-- Module table
local m = {}
local msg_meta = {}
msg_meta.__index = msg_meta
function irc.Message(opts)
local function Message(opts)
opts = opts or {}
setmetatable(opts, msg_meta)
if opts.raw then
@ -15,6 +14,8 @@ function irc.Message(opts)
return opts
end
m.Message = Message
local tag_escapes = {
[";"] = "\\:",
[" "] = "\\s",
@ -47,7 +48,7 @@ function msg_meta:toRFC1459()
s = s..self.command
local argnum = #self.args
local argnum = self.args and #self.args or 0
for i = 1, argnum do
local arg = self.args[i]
local startsWithColon = (arg:sub(1, 1) == ":")
@ -116,85 +117,91 @@ function msg_meta:fromRFC1459(line)
end
end
function msgs.privmsg(to, text)
return irc.Message({command="PRIVMSG", args={to, text}})
function m.privmsg(to, text)
return Message({command="PRIVMSG", args={to, text}})
end
function msgs.notice(to, text)
return irc.Message({command="NOTICE", args={to, text}})
function m.notice(to, text)
return Message({command="NOTICE", args={to, text}})
end
function msgs.action(to, text)
return irc.Message({command="PRIVMSG", args={to, ("\x01ACTION %s\x01"):format(text)}})
function m.action(to, text)
return Message({command="PRIVMSG", args={to, ("\x01ACTION %s\x01"):format(text)}})
end
function msgs.ctcp(command, to, args)
function m.ctcp(command, to, args)
s = "\x01"..command
if args then
s = ' '..args
end
s = s..'\x01'
return irc.Message({command="PRIVMSG", args={to, s}})
return Message({command="PRIVMSG", args={to, s}})
end
function msgs.kick(channel, target, reason)
return irc.Message({command="KICK", args={channel, target, reason}})
function m.kick(channel, target, reason)
return Message({command="KICK", args={channel, target, reason}})
end
function msgs.join(channel, key)
return irc.Message({command="JOIN", args={channel, key}})
function m.join(channel, key)
return Message({command="JOIN", args={channel, key}})
end
function msgs.part(channel, reason)
return irc.Message({command="PART", args={channel, reason}})
function m.part(channel, reason)
return Message({command="PART", args={channel, reason}})
end
function msgs.quit(reason)
return irc.Message({command="QUIT", args={reason}})
function m.quit(reason)
return Message({command="QUIT", args={reason}})
end
function msgs.kill(target, reason)
return irc.Message({command="KILL", args={target, reason}})
function m.kill(target, reason)
return Message({command="KILL", args={target, reason}})
end
function msgs.kline(time, mask, reason, operreason)
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 irc.Message({command="KLINE", args=args})
return Message({command="KLINE", args=args})
end
function msgs.whois(nick, server)
function m.whois(nick, server)
local args = nil
if server then
args = {server, nick}
else
args = {nick}
end
return irc.Message({command="WHOIS", args=args})
return Message({command="WHOIS", args=args})
end
function msgs.topic(channel, text)
return irc.Message({command="TOPIC", args={channel, text}})
function m.topic(channel, text)
return Message({command="TOPIC", args={channel, text}})
end
function msgs.invite(channel, target)
return irc.Message({command="INVITE", args={channel, target}})
function m.invite(channel, target)
return Message({command="INVITE", args={channel, target}})
end
function msgs.nick(nick)
return irc.Message({command="NICK", args={nick}})
function m.nick(nick)
return Message({command="NICK", args={nick}})
end
function msgs.mode(target, modes)
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 = irc.split(modes)
return irc.Message({command="MODE", args={target, unpack(mt)}})
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,4 +1,4 @@
local select = require "socket".select
local select = require("socket").select
local m = {}
local set = {}
@ -13,7 +13,7 @@ end
function set:add(connection)
local socket = connection.socket
insert(self.sockets, socket)
self.connections[socket] = connection
insert(self.connections, connection)
end
@ -32,13 +32,13 @@ 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

View File

@ -1,25 +1,8 @@
local irc = require("irc.main")
function irc.parseNick(conn, nick)
local access = {}
irc.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
-- Module table
local m = {}
function irc.updatePrefixModes(conn)
function m.updatePrefixModes(conn)
if conn.prefixmode and conn.modeprefix then
return
end
@ -39,8 +22,27 @@ function irc.updatePrefixModes(conn)
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)
irc.color = {
m.color = {
black = 1,
blue = 2,
green = 3,
@ -60,28 +62,28 @@ irc.color = {
}
local colByte = string.char(3)
setmetatable(irc.color, {__call = function(_, text, colornum)
setmetatable(m.color, {__call = function(_, text, colornum)
colornum = (type(colornum) == "string" and
assert(irc.color[colornum], "Invalid color '"..colornum.."'") or
assert(color[colornum], "Invalid color '"..colornum.."'") or
colornum)
return table.concat{colByte, tostring(colornum), text, colByte}
end})
local boldByte = string.char(2)
function irc.bold(text)
function m.bold(text)
return boldByte..text..boldByte
end
local underlineByte = string.char(31)
function irc.underline(text)
function m.underline(text)
return underlineByte..text..underlineByte
end
function irc.checkNick(nick)
function m.checkNick(nick)
return nick:find("^[a-zA-Z_%-%[|%]%^{|}`][a-zA-Z0-9_%-%[|%]%^{|}`]*$") ~= nil
end
function irc.defaultNickGenerator(nick)
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
@ -100,13 +102,13 @@ function irc.defaultNickGenerator(nick)
return nick
end
function irc.capitalize(text)
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 irc.split(str, sep)
function m.split(str, sep)
local t = {}
for s in str:gmatch("%S+") do
table.insert(t, s)
@ -114,3 +116,5 @@ function irc.split(str, sep)
return t
end
return m

View File

@ -3,11 +3,15 @@
irc.msgs = irc.lib.msgs
function irc:sendLocal(message)
minetest.chat_send_all(message)
function irc.logChat(message)
minetest.log("action", "IRC CHAT: "..message)
end
function irc:playerMessage(name, message)
function irc.sendLocal(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)
end

2
irc/mod.conf Normal file
View File

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

75
irc/settingtypes.txt Normal file
View File

@ -0,0 +1,75 @@
[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

View File

@ -1 +0,0 @@
irc

2
irc_commands/mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = irc_commands
depends = irc