1
0
mirror of https://github.com/minetest-mods/irc.git synced 2025-06-29 22:50:32 +02:00

63 Commits

Author SHA1 Message Date
s20
c088ba4e40 Add number of connected players to players command (#73) 2023-11-04 18:56:07 +01:00
758e9b6361 Fix error caused by ident check notice (#71)
Fixed the error where it cannot find an user at the notice hook. 
It happened when you get the `NOTICE * :*** Looking up your hostname and checking ident`
Because `*` is not in the `<nick>!<ident>@<host>`
2022-03-23 22:13:51 +01:00
7fbbfd6cdb Fix failing luacheck 2020-07-18 18:00:38 +02:00
cc78f12a4c Display total number of players with /who (#65) 2020-07-18 17:57:42 +02:00
ceb40e2bb3 Add luacheck GitHub integration (#62) 2020-02-19 18:46:29 +01:00
5e3659761b Add extra library search path (#60) 2019-10-17 21:29:02 +02:00
ac0387786c Fix deprecated functions, update to 5.x (#59) 2019-10-05 09:25:05 +02:00
05ab5e9fbd Fix message when a player on another server times out (#58) 2019-09-21 15:22:41 +02:00
1c23f8987b add another lua search path for JIT 2019-02-15 20:32:28 -03:00
66bb7fc47d Prevent players from joining the channel when logged in from IRC 2019-02-12 19:17:09 -03:00
b4fbccd64a Add support for configurable coloring of IRC messages in-game
IRC channel messages are colored green by default
IRC PMs are colored purple by default
2018-11-02 21:54:36 -07:00
c9c57a6f93 Avoid deprecation warnings.
* This change avoids 21 deprecation warnings in debug.txt on every server start

2018-05-29 23:46:10: WARNING[Main]: WARNING: minetest.setting_* functions are deprecated.  Use methods on the minetest.settings object.

* Provide backward compatibility by testing for existence of new methods before using them
2018-06-18 16:45:44 -03:00
f57bdba5e9 Make username and realname configurable. 2017-06-14 19:55:28 -03:00
070eb51236 Remove minetest colour codes before sending messages to IRC. 2017-05-04 18:59:58 -03:00
e80bbe3a62 Fix warning about deprecated call.
Fixes #39.
2017-05-02 21:52:44 -03:00
1d4fd5a162 Better method to avoid impersonation. 2017-03-24 22:33:43 -03:00
6bf4e111c2 Major cleanup.
- Use `irc.foo` notation instead of `irc:foo`. It still
  supports the `irc:foo` usage, but will helpfully issue
  a warning with the location of the offending code.
- Remove unused arguments from functions.
2017-03-15 21:40:12 -03:00
5f8850bc15 Stop saying "try list" on an invalid command.
As the list command is deprecated, this reference to it should be changed.
2017-03-02 01:11:13 -03:00
2f55e0f9a0 Add option to broadcast kick messages.
Default value is false.
2017-02-21 02:07:36 -03:00
78fbe26a2c Fix error if @ with no name is used. 2017-02-16 02:38:44 -03:00
33542b07fe Add .luacheckrc and fix warnings. 2017-02-16 02:36:04 -03:00
6bbb26f9f9 Add description.txt. 2017-02-12 02:58:40 -03:00
ae0cb08e3e Remove leading whitespace from command before processing.
Fixes bug where `Server:  asdf` (note the double space)
gets taken as an empty command name.
2017-01-29 15:05:42 -03:00
bb5f549193 Don't remove characters above 0x80. 2017-01-11 22:37:54 -03:00
4c334e9967 Update README. 2016-12-09 03:16:10 -03:00
a18820356a Fix error in previous help merge. 2016-12-03 18:41:01 -03:00
0c8538bfb8 Fix issue with previous commit.
Mea culpa. It was possible an error caused `require` to
not be reset, causing possible errors in the future.
2016-12-03 03:15:17 -03:00
9f8b4fd4af Fix SASL auth under secure environment. 2016-12-02 21:47:21 -03:00
73cdb58c99 Queue NickServ message for later.
Fixes #35.
2016-12-02 20:43:08 -03:00
3e2d98f9ef Silence warnings about module in ltn12. 2016-12-01 02:57:56 -03:00
12248cc847 Minor changes to botcmds.lua.
- Merge `list` into `help`.
- Use `table.concat` to remove trailing comma.
2016-11-28 22:17:08 -03:00
160088c232 Use return values whenever possible in chatcmds.lua.
Also minor fixes.
2016-11-28 02:02:40 -03:00
03070e41c4 Check nickname using RFC1459 rules in botcmds.lua. 2016-11-28 01:05:46 -03:00
b5786979ab Use return values in player_part.lua.
Also minor style fixes.
2016-11-20 20:53:01 -03:00
tai
d5ad8ffca4 Add IRC messages to log. 2016-11-20 15:21:37 -03:00
0f6e1dacc4 Create mod.conf 2016-11-17 11:23:53 -05:00
ecc1d450ba Add settingtypes.txt.
This allows configuring the mod from the GUI.
2016-11-13 12:13:17 -03:00
18f286f4b5 Reset require function after loading all sub-modules.
Fixes an issue with `hooks.lua` calling the (old, disabled)
`require` function.
2016-11-09 22:47:12 -03:00
0c47e10a90 Update installation instructions to use new repo URL. 2016-10-06 21:09:59 -03:00
43493eb2ea Report when players time out instead of leaving normally. (#25) 2016-06-19 23:20:20 -03:00
f98282818d Print reconnect interval, clean up reconnect comment.
Follows #23
2016-05-21 22:43:14 -07:00
03edbd29ed Add "reconnect" time variable to config. (#23)
Allow server admin to specify how long until a reconnect occurs after a timeout (default 600 seconds) without having to modify the core code.
2016-05-18 15:03:34 -07:00
464f2febee Switch to luarocks installation method for Debian and derivatives
Debian's LuaSocket packages are broken.  Then seem to
package the 5.1 version of the library as simply a copy
of the 5.2 version; however, the libraries are incompatible.
Additionally, the package seems to take a number of
different names depending on the version of the distro.
However, the LuaRocks package seems to be fine, so install
that first and then install LuaSocket through LuaRocks.
2016-01-09 15:47:08 -05:00
0c900dbac2 Add support for mod security 2015-05-16 19:54:24 -04:00
38832b3c55 Improve README 2015-04-03 13:18:38 -04:00
fd1a570794 Remove undefined global warning without LuaJIT 2015-04-03 12:42:57 -04:00
18b80bd127 Remove default values for some settings 2015-03-10 18:43:56 -04:00
43cb77fab8 Send CAP END for SASL 2015-03-10 13:33:41 -04:00
df229cabe6 Update LuaIRC 2015-02-07 13:21:22 -05:00
30fa81a3ab Don't allow a user with the nick "IRC" to impersonate other users 2015-01-27 11:36:29 -05:00
32294995be Fix /irc_names crashing if not connected. 2015-01-17 22:41:24 -03:00
d0ebde3884 Add Minetest version to CTCP VERSION reply 2014-10-01 16:19:10 -04:00
9e15a886b4 Work around missing package search paths of Minetest's built-in Lua 2014-09-10 14:00:49 -04:00
12b1b07afe Remove newlines from replies 2014-07-07 23:22:52 -04:00
e92eec3987 Change load order so that LuaIRC loads first 2014-06-06 11:06:18 -04:00
8aa442e87f Update LuaIRC 2014-06-03 15:00:55 -04:00
5655f75946 Add success and message return values to bot commands 2014-05-28 20:05:26 -04:00
8e6a9a287f Use LuaSocket's base 64 encoding function 2014-05-28 19:38:24 -04:00
26ca287b09 Check connectivity before sending messages 2014-05-28 19:38:23 -04:00
3b35bb1871 Fix leaked globals. 2014-05-28 12:05:08 -03:00
56a5de4a91 Fix typo in connect.
This caused bot to fail to connect to servers protected with passwords.

Reported by reactor.
2014-05-28 11:59:10 -03:00
706a6fbe27 Rename "mt_irc" namespace to "irc"
The underlying IRC library is now available as irc.lib.
2014-05-25 22:52:24 -04:00
e87d2e5bf2 Rename SASLPass and SASLUser settings to sasl.pass and sasl.user 2014-05-15 20:51:29 -04:00
16 changed files with 715 additions and 455 deletions

11
.github/workflows/check-release.yml vendored Normal file
View File

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

14
.luacheckrc Normal file
View File

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

View File

@ -1,63 +1,66 @@
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'
(without the quotes) to your mod's 'depends.txt' file. In order to allow your mod to interface with this mod, you must add `irc`
to your mod's `depends.txt` file.
REFERENCE Reference
--------- ---------
mt_irc:say([name, ]message) 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:
mt_irc:say("Hello, Channel!") irc.say("Hello, Channel!")
mt_irc:say("john1234", "How are you?") irc.say("john1234", "How are you?")
mt_irc:register_bot_command(name, cmdDef) 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, param) func = function(user, args)
-- 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'
-- <param> is a string of parameters to the command (may be "") -- <args> is a string of arguments to the command (may be "")
end, -- This function should return boolean success and a message.
}; end,
};
Example: Example:
mt_irc:register_bot_command("hello", { irc.register_bot_command("hello", {
params = "", params = "",
description = "Greet user", description = "Greet user",
func = function(user, param) func = function(user, param)
mt_irc:say(user.nick, "Hello!") return true, "Hello!"
end, end,
}); });
mt_irc.joined_players[name] 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 mt_irc.joined_players["joe"] then if irc.joined_players["joe"] then
-- Joe is talking on IRC -- Joe is talking on IRC
end end
mt_irc:register_hook(name, func) 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:
mt_irc:register_hook("OnSend", function(line) irc.register_hook("OnSend", function(line)
print("SEND: "..line) print("SEND: "..line)
end) end)
@ -69,18 +72,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 will most likely crash the not modify these settings at runtime or you might crash the server!
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.

182
README.md
View File

@ -1,122 +1,139 @@
[![](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 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 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. Please change `<Mods directory>` to fit your installation of Minetest.
For more information, see [the wiki](http://wiki.minetest.net/Installing_mods). For more information, see [the wiki][wiki].
The Minetest IRC mod uses submodules, therefore you will have to run The IRC mod's git repository uses submodules, therefore you will have to run
`git submodule init` when first installing the mod, and `git submodule update` `git submodule init` when first installing the mod (unless you used
every time that a submodule is updated. These steps can be combined as `--recursive` as above), and `git submodule update` every time that a submodule
`git submodule update --init`. 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 You'll need to install LuaSocket. You can do so with your package manager on
package manager on many distributions, for example on Arch Linux: many distributions, for example:
# # 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, default "irc.freenode.net") * `irc.server` (string):
This is the IRC server the mod connects to. The address of the IRC server to connect to.
* `irc.channel` (string, default "##mt-irc-mod") * `irc.channel` (string):
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, default "MT-FFFFFF") * `irc.nick` (string):
Nickname used as "proxy" for the in-game chat. Nickname the server uses when it connects to IRC.
'F' stands for a random base-16 number.
* `irc.password` (string, default "") * `irc.password` (string, default nil):
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 use this if you use SASL authentication. NickServ password. Don't set this if you use SASL authentication.
* `irc.SASLPass` (string, default nil) * `irc.sasl.pass` (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.SASLUser` (string, default irc.nick) * `irc.sasl.user` (string, default `irc.nick`):
The SASL username. This should normaly be set to your main NickServ account name. The SASL username. This should normaly be set to your
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 using the 'T' or Once the game is connected to the IRC channel, chatting in-game will send
F10 hotkeys will send the messages to the channel, and will be visible messages to the channel, and will be visible by anyone. Also, messages sent
by anyone. Also, when someone sends a message to the channel, that text to the channel will be visible in-game.
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>`:
Sends a private message to a IRC user. Send 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`:
Equivilant to `/irc_disconnect` followed by `/irc_connect`. Equivalent 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`
To do it, you must send a private message to the bot (set with option above), in the following format:
the `irc.nick` option above), in the following format:
@playername message @playername message
@ -125,21 +142,29 @@ a private message from IRC with:
/msg server_nick @mtuser Hello! /msg server_nick @mtuser Hello!
To avoid possible misunderstandings (since all in-game players use the The bot also supports some basic commands, which are invoked by saying
same IRC user to converse with you), the "proxy" user will reject any the bot name followed by either a colon or a comma and the command, or
private messages that are not in that format, and will send back a sending a private message to it. For example: `ServerBot: help whereis`.
nice reminder as a private message.
The bot also supports some basic commands, which are invoked by sending * `help [<command>]`:
a private message to it. Use `!list` to get a list of commands, and Prints help about a command, or a list of supported commands if no
`!help <command>` to get help about a specific command. 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 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,
@ -151,10 +176,7 @@ forum topic. Thanks to you all!
License License
------- -------
(C) 2012-2013 Diego Martínez <kaeza@users.sf.net> See `LICENSE.txt` for details.
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,39 +1,59 @@
mt_irc.bot_commands = {} irc.bot_commands = {}
function mt_irc:check_botcmd(msg) -- From RFC1459:
local prefix = mt_irc.config.command_prefix -- "Because of IRCs scandanavian origin, the characters {}| are
local nick = mt_irc.conn.nick:lower() -- 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
local text = msg.args[2] 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 -- First check for a nick prefix
if nickpart == nick..": " or if nickequals(nickpart, nick)
nickpart == nick..", " then and (suffix == ": " or suffix == ", ") then
self:bot_command(msg, text:sub(#nick + 3)) irc.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
self:bot_command(msg, text:sub(#prefix + 1)) irc.bot_command(msg, text:sub(#prefix + 1))
return true return true
end end
return false return false
end end
function mt_irc:bot_command(msg, text) function 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 found, _, player_to, message = text:find("^.([^%s]+)%s(.+)$") local _, _, player_to, message = text:find("^.([^%s]+)%s(.+)$")
if not minetest.get_player_by_name(player_to) then if not player_to then
mt_irc:reply("User '"..player_to.."' is not in the game.")
return return
elseif not mt_irc.joined_players[player_to] then elseif not minetest.get_player_by_name(player_to) then
mt_irc:reply("User '"..player_to.."' is not using IRC.") 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.")
return return
end end
minetest.chat_send_player(player_to, minetest.chat_send_player(player_to,
"PM from "..msg.user.nick.."@IRC: "..message, false) minetest.colorize(irc.config.pm_color,
mt_irc:reply("Message sent!") "PM from "..msg.user.nick.."@IRC: "..message, false))
irc.reply("Message sent!")
return return
end end
local pos = text:find(" ", 1, true) local pos = text:find(" ", 1, true)
@ -46,110 +66,113 @@ function mt_irc:bot_command(msg, text)
args = "" args = ""
end end
if not self.bot_commands[cmd] then if not irc.bot_commands[cmd] then
self:reply("Unknown command '"..cmd.."'. Try 'list'." irc.reply("Unknown command '"..cmd.."'. Try 'help'."
.." Or use @playername <message> to send a private message") .." Or use @playername <message> to send a private message")
return return
end end
self.bot_commands[cmd].func(msg.user, args) local _, message = irc.bot_commands[cmd].func(msg.user, args)
if message then
irc.reply(message)
end
end end
function mt_irc:register_bot_command(name, def) function 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
self.bot_commands[name] = def irc.bot_commands[name] = def
end end
mt_irc:register_bot_command("help", { irc.register_bot_command("help", {
params = "<command>", params = "<command>",
description = "Get help about a command", description = "Get help about a command",
func = function(user, args) func = function(_, args)
if args == "" then if args == "" then
mt_irc:reply("No command name specified. Use 'list' for a list of commands") local cmdlist = { }
return 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 end
local cmd = mt_irc.bot_commands[args] local cmd = irc.bot_commands[args]
if not cmd then if not cmd then
mt_irc:reply("Unknown command '"..cmdname.."'.") return false, "Unknown command '"..args.."'."
return
end end
mt_irc:reply(("Usage: %c%s %s -- %s"):format( return true, ("Usage: %s%s %s -- %s"):format(
mt_irc.config.command_prefix, irc.config.command_prefix or "",
args, args,
cmd.params or "<no parameters>", cmd.params or "<no parameters>",
cmd.description or "<no description>")) cmd.description or "<no description>")
end end
}) })
mt_irc:register_bot_command("list", { irc.register_bot_command("list", {
params = "", params = "",
description = "List available commands.", description = "List available commands.",
func = function(user, args) func = function()
local cmdlist = "Available commands: " return false, "The `list` command has been merged into `help`."
for name, cmd in pairs(mt_irc.bot_commands) do .." Use `help` with no arguments to get a list."
cmdlist = cmdlist..name..", "
end
mt_irc:reply(cmdlist.." -- Use 'help <command name>' to get"
.." help about a specific command.")
end end
}) })
mt_irc:register_bot_command("whereis", { irc.register_bot_command("whereis", {
params = "<player>", params = "<player>",
description = "Tell the location of <player>", description = "Tell the location of <player>",
func = function(user, args) func = function(_, args)
if args == "" then if args == "" then
mt_irc:bot_help(user, "whereis") return false, "Player name required."
return
end end
local player = minetest.env:get_player_by_name(args) local player = minetest.get_player_by_name(args)
if player then if not player then
local fmt = "Player %s is at (%.2f,%.2f,%.2f)" return false, "There is no player named '"..args.."'"
local pos = player:getpos()
mt_irc:reply(fmt:format(args, pos.x, pos.y, pos.z))
return
end end
mt_irc:reply("There is no player named '"..args.."'") local fmt = "Player %s is at (%.2f,%.2f,%.2f)"
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()
mt_irc:register_bot_command("uptime", { 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(user, args) func = function()
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"
mt_irc:reply(fmt:format( return true, fmt:format(
math.floor(diff / 60 / 60), math.floor(diff / 60 / 60),
math.mod(math.floor(diff / 60), 60), math.floor(diff / 60) % 60,
math.mod(math.floor(diff), 60) math.floor(diff) % 60
)) )
end end
}) })
mt_irc:register_bot_command("players", { irc.register_bot_command("players", {
description = "List the players on the server", description = "List the players on the server",
func = function(user, args) func = function()
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
mt_irc:reply("Connected players: " return true, string.format("%d connected player(s): %s",
..table.concat(names, ", ")) #players,
table.concat(names, ", ")
)
end end
}) })

View File

@ -4,25 +4,26 @@
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 mt_irc.connected and mt_irc.config.send_join_part then if irc.connected and irc.config.send_join_part then
mt_irc:say("*** "..name.." joined the game") irc.say("*** "..name.." joined the game")
end end
end) end)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player, timed_out)
local name = player:get_player_name() local name = player:get_player_name()
if mt_irc.connected and mt_irc.config.send_join_part then if irc.connected and irc.config.send_join_part then
mt_irc:say("*** "..name.." left the game") 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 mt_irc.connected if not 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 mt_irc.joined_players[name] or not 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
@ -30,11 +31,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
mt_irc:say(mt_irc:playerMessage(name, message)) irc.say(irc.playerMessage(name, minetest.strip_colors(message)))
end) end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
mt_irc:disconnect("Game shutting down.") irc.disconnect("Game shutting down.")
end) end)

View File

@ -10,33 +10,31 @@ 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 mt_irc.connected then if not irc.connected then
minetest.chat_send_player(name, "Not connected to IRC. Use /irc_connect to connect.") return false, "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
minetest.chat_send_player(name, "Invalid usage, see /help irc_msg.") return false, "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
for nick, user in pairs(mt_irc.conn.channels[mt_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 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
minetest.chat_send_player(name, return false, "You can not message that user. ("..hint..")"
"You can not message that user. (Hint: They have to be in the channel)")
return
end end
mt_irc:say(toname, mt_irc:playerMessage(name, message)) irc.say(toname, irc.playerMessage(name, message))
minetest.chat_send_player(name, "Message sent!") return true, "Message sent!"
end end
}) })
@ -44,12 +42,15 @@ 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(name, params) func = function()
local users = { } if not irc.connected then
for k, v in pairs(mt_irc.conn.channels[mt_irc.config.channel].users) do return false, "Not connected to IRC. Use /irc_connect to connect."
table.insert(users, k)
end end
minetest.chat_send_player(name, "Users in IRC: "..table.concat(users, ", ")) local users = { }
for nick in pairs(irc.conn.channels[irc.config.channel].users) do
table.insert(users, nick)
end
return true, "Users in IRC: "..table.concat(users, ", ")
end end
}) })
@ -57,13 +58,12 @@ 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, param) func = function(name)
if mt_irc.connected then if irc.connected then
minetest.chat_send_player(name, "You are already connected to IRC.") return false, "You are already connected to IRC."
return
end end
minetest.chat_send_player(name, "IRC: Connecting...") minetest.chat_send_player(name, "IRC: Connecting...")
mt_irc:connect() irc.connect()
end end
}) })
@ -73,14 +73,13 @@ 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 mt_irc.connected then if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.") return false, "Not connected to IRC. Use /irc_connect to connect."
return
end end
if params == "" then if param == "" then
params = "Manual disconnect by "..name param = "Manual disconnect by "..name
end end
mt_irc:disconnect(param) irc.disconnect(param)
end end
}) })
@ -88,13 +87,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, param) func = function(name)
if not mt_irc.connected then if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.") return false, "Not connected to IRC. Use /irc_connect to connect."
return
end end
mt_irc:disconnect("Reconnecting...") minetest.chat_send_player(name, "IRC: Reconnecting...")
mt_irc:connect() irc.disconnect("Reconnecting...")
irc.connect()
end end
}) })
@ -104,19 +103,32 @@ 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 mt_irc.connected then if not irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.") return false, "Not connected to IRC. Use /irc_connect to connect."
return
end end
mt_irc:queue(param) 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, ...)
oldme(name, param, ...) irc.say(("* %s %s"):format(name, param))
mt_irc:say(("* %s %s"):format(name, param)) return oldme(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,36 +2,45 @@
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
mt_irc.config = {} irc.config = {}
local function setting(stype, name, default) local function setting(stype, name, default, required)
local value local value
if stype == "bool" then if minetest.settings and minetest.settings.get and minetest.settings.get_bool then
value = minetest.setting_getbool("irc."..name) if stype == "bool" then
elseif stype == "string" then value = minetest.settings:get_bool("irc."..name)
value = minetest.setting_get("irc."..name) elseif stype == "string" then
elseif stype == "number" then value = minetest.settings:get("irc."..name)
value = tonumber(minetest.setting_get("irc."..name)) elseif stype == "number" then
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
mt_irc.config[name] = value irc.config[name] = value
end end
------------------------- -------------------------
-- BASIC USER SETTINGS -- -- BASIC USER SETTINGS --
------------------------- -------------------------
setting("string", "nick") -- Nickname (default "MT-<hash>", <hash> 6 random hexidecimal characters) setting("string", "nick", nil, true) -- Nickname
setting("string", "server", "irc.freenode.net") -- Server to connect on joinplayer setting("string", "server", nil, true) -- Server address to connect to
setting("number", "port", 6667) -- Port to connect on joinplayer setting("number", "port", 6667) -- Server port to connect to
setting("string", "NSPass") -- NickServ password setting("string", "NSPass") -- NickServ password
setting("string", "SASLUser", mt_irc.config.nick) -- SASL username setting("string", "sasl.user", irc.config.nick) -- SASL username
setting("string", "SASLPass") -- SASL password setting("string", "username", "Minetest") -- Username/ident
setting("string", "channel", "##mt-irc-mod") -- Channel to join setting("string", "realname", "Minetest") -- Real name/GECOS
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 --
@ -40,20 +49,11 @@ setting("bool", "send_join_part", true) -- Whether to send player join and par
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
-- Generate a random nickname if one isn't specified. setting("string", "pm_color", "#8800AA") -- Color of IRC PMs in-game, purple by default
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,17 +1,16 @@
-- 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 = ...
mt_irc.hooks = {} -- MIME is part of LuaSocket
mt_irc.registered_hooks = {} 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]"
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
@ -21,8 +20,8 @@ local function normalize(text)
end end
function mt_irc:doHook(conn) function irc.doHook(conn)
for name, hook in pairs(self.registered_hooks) do for name, hook in pairs(irc.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
@ -30,59 +29,67 @@ function mt_irc:doHook(conn)
end end
function mt_irc:register_hook(name, func) function irc.register_hook(name, func)
self.registered_hooks[name] = self.registered_hooks[name] or {} irc.registered_hooks[name] = irc.registered_hooks[name] or {}
table.insert(self.registered_hooks[name], func) table.insert(irc.registered_hooks[name], func)
end end
function mt_irc.hooks.raw(line) function irc.hooks.raw(line)
if mt_irc.config.debug then if irc.config.debug then
print("RECV: "..line) print("RECV: "..line)
end end
end end
function mt_irc.hooks.send(line) function irc.hooks.send(line)
if mt_irc.config.debug then if irc.config.debug then
print("SEND: "..line) print("SEND: "..line)
end end
end end
function mt_irc.hooks.chat(msg) function 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
mt_irc.conn:invoke("OnCTCP", msg) irc.conn:invoke("OnCTCP", msg)
return return
end end
if channel == mt_irc.conn.nick then if channel == irc.conn.nick then
mt_irc.last_from = msg.user.nick irc.last_from = msg.user.nick
mt_irc.conn:invoke("PrivateMessage", msg) irc.conn:invoke("PrivateMessage", msg)
else else
mt_irc.last_from = channel irc.last_from = channel
mt_irc.conn:invoke("OnChannelChat", msg) irc.conn:invoke("OnChannelChat", msg)
end end
end end
function mt_irc.hooks.ctcp(msg) local function get_core_version()
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)
mt_irc:queue(irc.msgs.notice(msg.user.nick, 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] == mt_irc.config.channel then if command == "ACTION" and msg.args[1] == irc.config.channel then
local action = text:sub(8, -1) local action = text:sub(8, -1)
mt_irc:sendLocal(("* %s@IRC %s"):format(msg.user.nick, action)) irc.sendLocal(("* %s@IRC %s"):format(msg.user.nick, action))
elseif command == "VERSION" then elseif command == "VERSION" then
reply(("Minetest IRC mod version %s.") reply(("Minetest version %s, IRC mod version %s.")
:format(mt_irc.version)) :format(get_core_version(), irc.version))
elseif command == "PING" then elseif command == "PING" then
reply(args[2]) reply(args[2])
elseif command == "TIME" then elseif command == "TIME" then
@ -91,9 +98,18 @@ function mt_irc.hooks.ctcp(msg)
end end
function mt_irc.hooks.channelChat(msg) function 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"
@ -104,61 +120,64 @@ function mt_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
mt_irc:sendLocal(("<%s@%s> %s") irc.sendLocal(("<%s@%s> %s")
:format(chatnick, msg.user.nick, chatmessage)) :format(chatnick, msg.user.nick, chatmessage))
elseif foundjoin then elseif foundjoin then
mt_irc:sendLocal(("*** %s joined %s") irc.sendLocal(("*** %s joined %s")
:format(joinnick, msg.user.nick)) :format(joinnick, msg.user.nick))
elseif foundleave then elseif foundleave then
mt_irc:sendLocal(("*** %s left %s") 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
mt_irc:sendLocal(("* %s@%s %s") irc.sendLocal(("* %s@%s %s")
:format(actionnick, msg.user.nick, actionmessage)) :format(actionnick, msg.user.nick, actionmessage))
else else
mt_irc:sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text)) irc.sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text))
end end
end end
function mt_irc.hooks.pm(msg) function 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 = mt_irc.config.command_prefix local prefix = 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
mt_irc:bot_command(msg, text) irc.bot_command(msg, text)
end end
function mt_irc.hooks.kick(channel, target, prefix, reason) function irc.hooks.kick(channel, target, prefix, reason)
if target == mt_irc.conn.nick then if target == 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..".")
mt_irc:disconnect("Kicked") irc.disconnect("Kicked")
else else
mt_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)) :format(target, channel, prefix.nick, reason))
end end
end end
function mt_irc.hooks.notice(user, target, message) function irc.hooks.notice(user, target, message)
if user.nick and target == mt_irc.config.channel then if user and user.nick and target == irc.config.channel then
mt_irc:sendLocal("-"..user.nick.."@IRC- "..message) irc.sendLocal("-"..user.nick.."@IRC- "..message)
end end
end end
function mt_irc.hooks.mode(user, target, modes, ...) function 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
@ -173,37 +192,37 @@ function mt_irc.hooks.mode(user, target, modes, ...)
end end
function mt_irc.hooks.nick(user, newNick) function irc.hooks.nick(user, newNick)
mt_irc:sendLocal(("-!- %s is now known as %s") irc.sendLocal(("-!- %s is now known as %s")
:format(user.nick, newNick)) :format(user.nick, newNick))
end end
function mt_irc.hooks.join(user, channel) function irc.hooks.join(user, channel)
mt_irc:sendLocal(("-!- %s joined %s") irc.sendLocal(("-!- %s joined %s")
:format(user.nick, channel)) :format(user.nick, channel))
end end
function mt_irc.hooks.part(user, channel, reason) function irc.hooks.part(user, channel, reason)
reason = reason or "" reason = reason or ""
mt_irc:sendLocal(("-!- %s has left %s [%s]") irc.sendLocal(("-!- %s has left %s [%s]")
:format(user.nick, channel, reason)) :format(user.nick, channel, reason))
end end
function mt_irc.hooks.quit(user, reason) function irc.hooks.quit(user, reason)
mt_irc:sendLocal(("-!- %s has quit [%s]") irc.sendLocal(("-!- %s has quit [%s]")
:format(user.nick, reason)) :format(user.nick, reason))
end end
function mt_irc.hooks.disconnect(message, isError) function irc.hooks.disconnect(_, isError)
mt_irc.connected = false 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, mt_irc.connect, mt_irc) minetest.after(60, irc.connect, 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.")
@ -211,34 +230,34 @@ function mt_irc.hooks.disconnect(message, isError)
end end
function mt_irc.hooks.preregister(conn) function irc.hooks.preregister(conn)
if not (mt_irc.config.SASLUser and mt_irc.config.SASLPass) then return end if not (irc.config["sasl.user"] and irc.config["sasl.pass"]) then return end
local authString = mt_irc.b64e( local authString = b64e(
("%s\x00%s\x00%s"):format( ("%s\x00%s\x00%s"):format(
mt_irc.config.SASLUser, irc.config["sasl.user"],
mt_irc.config.SASLUser, irc.config["sasl.user"],
mt_irc.config.SASLPass) irc.config["sasl.pass"])
) )
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)
--LuaIRC will send CAP END conn:send("CAP END")
end end
mt_irc:register_hook("PreRegister", mt_irc.hooks.preregister) irc.register_hook("PreRegister", irc.hooks.preregister)
mt_irc:register_hook("OnRaw", mt_irc.hooks.raw) irc.register_hook("OnRaw", irc.hooks.raw)
mt_irc:register_hook("OnSend", mt_irc.hooks.send) irc.register_hook("OnSend", irc.hooks.send)
mt_irc:register_hook("DoPrivmsg", mt_irc.hooks.chat) irc.register_hook("DoPrivmsg", irc.hooks.chat)
mt_irc:register_hook("OnPart", mt_irc.hooks.part) irc.register_hook("OnPart", irc.hooks.part)
mt_irc:register_hook("OnKick", mt_irc.hooks.kick) irc.register_hook("OnKick", irc.hooks.kick)
mt_irc:register_hook("OnJoin", mt_irc.hooks.join) irc.register_hook("OnJoin", irc.hooks.join)
mt_irc:register_hook("OnQuit", mt_irc.hooks.quit) irc.register_hook("OnQuit", irc.hooks.quit)
mt_irc:register_hook("NickChange", mt_irc.hooks.nick) irc.register_hook("NickChange", irc.hooks.nick)
mt_irc:register_hook("OnCTCP", mt_irc.hooks.ctcp) irc.register_hook("OnCTCP", irc.hooks.ctcp)
mt_irc:register_hook("PrivateMessage", mt_irc.hooks.pm) irc.register_hook("PrivateMessage", irc.hooks.pm)
mt_irc:register_hook("OnNotice", mt_irc.hooks.notice) irc.register_hook("OnNotice", irc.hooks.notice)
mt_irc:register_hook("OnChannelChat", mt_irc.hooks.channelChat) irc.register_hook("OnChannelChat", irc.hooks.channelChat)
mt_irc:register_hook("OnModeChange", mt_irc.hooks.mode) irc.register_hook("OnModeChange", irc.hooks.mode)
mt_irc:register_hook("OnDisconnect", mt_irc.hooks.disconnect) irc.register_hook("OnDisconnect", irc.hooks.disconnect)

200
init.lua
View File

@ -3,14 +3,51 @@
local modpath = minetest.get_modpath(minetest.get_current_modname()) local modpath = minetest.get_modpath(minetest.get_current_modname())
package.path = -- Handle mod security if needed
package.path..";" local ie, req_ie = _G, minetest.request_insecure_environment
-- To find LuaIRC's init.lua if req_ie then ie = req_ie() end
..modpath.."/?/init.lua;" if not ie then
-- For LuaIRC to find its files error("The IRC mod requires access to insecure functions in order "..
..modpath.."/?.lua" "to work. Please add the irc mod to your secure.trusted_mods "..
"setting or disable the irc mod.")
end
mt_irc = { ie.package.path =
-- To find LuaIRC's init.lua
modpath.."/?/init.lua;"
-- For LuaIRC to find its files
..modpath.."/?.lua;"
..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
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,
@ -18,44 +55,82 @@ mt_irc = {
recent_message_count = 0, recent_message_count = 0,
joined_players = {}, joined_players = {},
modpath = modpath, modpath = modpath,
lib = require("irc"), lib = lib,
} }
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")
dofile(modpath.."/hooks.lua") loadfile(modpath.."/hooks.lua")(ie)
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")
if mt_irc.config.enable_player_part then -- Restore old (safe) functions
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(mt_irc.joined_players, {__index = function(index) return true end}) setmetatable(irc.joined_players, {__index = function() 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 mt_irc:step(dtime) end) minetest.register_globalstep(function(dtime) return irc.step(dtime) end)
function mt_irc:step(dtime) function irc.step()
if stepnum == 3 then if stepnum == 3 then
if self.config.auto_connect then if irc.config.auto_connect then
self:connect() irc.connect()
end end
end end
stepnum = stepnum + 1 stepnum = stepnum + 1
if not self.connected then return end if not irc.connected then return end
-- Hooks will manage incoming messages and errors -- 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 if not good then
print(err) print(err)
return return
@ -63,76 +138,89 @@ function mt_irc:step(dtime)
end end
function mt_irc:connect() function irc.connect()
if self.connected then if irc.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
self.conn = irc.new({ irc.conn = irc.lib.new({
nick = self.config.nick, nick = irc.config.nick,
username = "Minetest", username = irc.config.username,
realname = "Minetest", realname = irc.config.realname,
}) })
self:doHook(self.conn) irc.doHook(irc.conn)
good, message = pcall(function()
self.conn:connect({ -- We need to swap the `require` function again since
host = self.config.server, -- LuaIRC `require`s `ssl` if `irc.secure` is true.
port = self.config.port, old_require = require
pass = self.config.password, require = ie.require
timeout = self.config.timeout,
secure = self.config.secure local good, message = pcall(function()
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 ten minutes...") minetest.log("error", ("IRC: Connection error: %s: %s -- Reconnecting in %d seconds...")
:format(self.config.server, message)) :format(irc.config.server, message, irc.config.reconnect))
minetest.after(600, function() self:connect() end) minetest.after(irc.config.reconnect, function() irc.connect() end)
return return
end end
if self.config.NSPass then if irc.config.NSPass then
self:say("NickServ", "IDENTIFY "..self.config.NSPass) irc.conn:queue(irc.msgs.privmsg(
"NickServ", "IDENTIFY "..irc.config.NSPass))
end end
self.conn:join(self.config.channel, self.config.key) irc.conn:join(irc.config.channel, irc.config.key)
self.connected = true irc.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 mt_irc:disconnect(message) function irc.disconnect(message)
if self.connected then if irc.connected then
--The OnDisconnect hook will clear self.connected and print a disconnect message --The OnDisconnect hook will clear irc.connected and print a disconnect message
self.conn:disconnect(message) irc.conn:disconnect(message)
end end
end end
function mt_irc:say(to, message) function irc.say(to, message)
if not message then if not message then
message = to message = to
to = self.config.channel to = irc.config.channel
end 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 end
function mt_irc:reply(message) function irc.reply(message)
if not self.last_from then if not irc.last_from then
return return
end end
self:say(self.last_from, message) message = message:gsub("[\r\n%z]", " \\n ")
irc.say(irc.last_from, message)
end end
function mt_irc:send(msg) function irc.send(msg)
self.conn:send(msg) if not irc.connected then return end
irc.conn:send(msg)
end end
function mt_irc:queue(msg) function irc.queue(msg)
self.conn:queue(msg) if not irc.connected then return end
irc.conn:queue(msg)
end end

2
irc

Submodule irc updated: bc9805a33c...e49a52ede1

View File

@ -1,13 +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.
mt_irc.msgs = mt_irc.lib.msgs irc.msgs = irc.lib.msgs
function mt_irc:sendLocal(message) function irc.logChat(message)
minetest.chat_send_all(message) minetest.log("action", "IRC CHAT: "..message)
end end
function mt_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) return ("<%s> %s"):format(name, message)
end end

5
mod.conf Normal file
View File

@ -0,0 +1,5 @@
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,68 +2,71 @@
-- See LICENSE.txt for details. -- See LICENSE.txt for details.
function mt_irc:player_part(name) function irc.player_part(name)
if not self.joined_players[name] then if not irc.joined_players[name] then
minetest.chat_send_player(name, "IRC: You are not in the channel.") return false, "You are not in the channel"
return
end end
self.joined_players[name] = nil irc.joined_players[name] = nil
minetest.chat_send_player(name, "IRC: You are now out of the channel.") return true, "You left the channel"
end end
function mt_irc:player_join(name) function irc.player_join(name)
if self.joined_players[name] then if irc.joined_players[name] then
minetest.chat_send_player(name, "IRC: You are already in the channel.") return false, "You are already in the channel"
return elseif not minetest.get_player_by_name(name) then
return false, "You need to be in-game to join the channel"
end end
self.joined_players[name] = true irc.joined_players[name] = true
minetest.chat_send_player(name, "IRC: You are now in the channel.") return true, "You joined 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, param) func = function(name)
mt_irc:player_join(name) return 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, param) func = function(name)
mt_irc:player_part(name) return 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(name, param) func = function()
local s = "" local out, n = { }, 0
for name, _ in pairs(mt_irc.joined_players) do for plname in pairs(irc.joined_players) do
s = s..", "..name n = n + 1
out[n] = plname
end end
minetest.chat_send_player(name, "Players On Channel:"..s) table.sort(out)
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()
mt_irc.joined_players[name] = mt_irc.config.auto_join irc.joined_players[name] = 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()
mt_irc.joined_players[name] = nil irc.joined_players[name] = nil
end) end)
function mt_irc:sendLocal(message) function irc.sendLocal(message)
for name, _ in pairs(self.joined_players) do for name, _ in pairs(irc.joined_players) do
minetest.chat_send_player(name, message) minetest.chat_send_player(name,
minetest.colorize(irc.config.chat_color, message))
end end
irc.logChat(message)
end end

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