1
0
mirror of https://github.com/minetest-mods/irc.git synced 2025-07-01 07:30:30 +02:00
This commit is contained in:
ShadowNinja
2013-04-29 18:07:44 -04:00
parent 3aede000b7
commit c7f989dd85
105 changed files with 946 additions and 22249 deletions

87
src/API.txt Normal file
View File

@ -0,0 +1,87 @@
IRC Mod API
-----------
This file documents the Minetest IRC mod API.
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.
REFERENCE
---------
mt_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:
mt_irc:say("Hello, Channel!")
mt_irc:say("john1234", "How are you?")
mt_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.
Here's the format of a command definition (<cmdDef>):
cmdDef = {
params = "<param1> ...", -- A description of the command's parameters
description = "My command", -- A description of what the command does. (one-liner)
func = function(user, param)
-- This function gets called when the command is invoked.
-- <user> is a user table for the user that ran the command.
-- (See the LuaIRC documentation for details.)
-- It contains fields such as 'nick' and 'ident'
-- <param> is a string of parameters to the command (may be "")
end,
};
Example:
mt_irc:register_bot_command("hello", {
params = "",
description = "Greet user",
func = function(user, param)
mt_irc:say(user.nick, "Hello!")
end,
});
mt_irc.joined_players[name]
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
commands.
Example:
if mt_irc.joined_players["joe"] then
-- Joe is talking on IRC
end
mt_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:
mt_irc:register_hook("OnSend", function(line)
print("SEND: "..line)
end)
This mod also supplies some utility functions:
string.expandvars(string, vars)
Expands all occurrences of the pattern "$(varname)" with the value of
'varname' in the <vars> table. Variable names not found on the table
are left verbatim in the string.
Example:
local tpl = "$(foo) $(bar) $(baz)"
local s = tpl:expandvars({ foo=1, bar="Hello" })
assert(s == "1 Hello $(baz)")
In addition, all the configuration options decribed in `README.txt' are
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
server!
HOOKS
---------
The 'mt_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.

22
src/LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013, Diego Martinez (kaeza)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
src/LuaIRC Submodule

Submodule src/LuaIRC added at 8a2e47a326

View File

@ -1,91 +1,106 @@
mt_irc.bot_commands = {}
mt_irc.bot_commands = { };
mt_irc.bot_help = function ( from, cmdname )
local cmd = mt_irc.bot_commands[cmdname];
if (not cmd) then
irc.say(from, "Unknown command `"..cmdname.."'");
return;
function mt_irc:bot_command(user, message)
local pos = message:find(" ", 1, true)
local cmd, args
if pos then
cmd = message:sub(1, pos - 1)
args = message:sub(pos + 1)
else
cmd = message
args = ""
end
local usage = "Usage: !"..cmdname;
if (cmd.params) then usage = usage.." "..cmd.params; end
irc.say(from, usage);
if (cmd.description) then irc.say(from, " "..cmd.description); end
if not self.bot_commands[cmd] then
self:say(user.nick, "Unknown command '"..cmd.."'. Try `!help'."
.." Or use @playername <message> to send a private message")
return
end
self.bot_commands[cmd].func(user, args)
end
mt_irc.register_bot_command = function ( name, def )
if ((not def.func) or (type(def.func) ~= "function")) then
error("Wrong bot command definition", 2);
function mt_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)
end
mt_irc.bot_commands[name] = def;
self.bot_commands[name] = def
end
mt_irc.register_bot_command("help", {
params = "[<command>]";
description = "Get help about a command";
func = function ( from, args )
if (args ~= "") then
mt_irc.bot_help(from, args);
else
local cmdlist = "Available commands:";
for name,cmd in pairs(mt_irc.bot_commands) do
cmdlist = cmdlist.." "..name;
end
irc.say(from, cmdlist);
irc.say(from, "Use `!help <command name>' to get help about a specific command.");
end
end;
});
mt_irc.register_bot_command("who", {
params = nil;
description = "Tell who is playing";
func = function ( from, args )
local s = "";
for k, v in pairs(mt_irc.connected_players) do
if (v) then
s = s.." "..k;
end
mt_irc:register_bot_command("help", {
params = "<command>",
description = "Get help about a command",
func = function(user, args)
if args == "" then
mt_irc:say(user.nick, "No command name specified. Use 'list' for a list of cammands")
return
end
irc.say(from, "Players On Channel:"..s);
end;
});
mt_irc.register_bot_command("whereis", {
params = "<player>";
description = "Tell the location of <player>";
func = function ( from, args )
if (args == "") then
mt_irc.bot_help(from, "whereis");
return;
local cmd = mt_irc.bot_commands[args]
if not cmd then
mt_irc:say(user.nick, "Unknown command '"..cmdname.."'.")
return
end
local list = minetest.env:get_objects_inside_radius({x=0,y=0,z=0}, 100000);
for _, obj in ipairs(list) do
if (obj:is_player() and (obj:get_player_name() == args)) then
local fmt = "Player %s is at (%.2f,%.2f,%.2f)";
local pos = obj:getpos();
irc.say(from, fmt:format(args, pos.x, pos.y, pos.z));
return;
end
local usage = ("Usage: %c%s %s -- %s"):format(
mt_irc.config.command_prefix,
args,
cmd.params or "<no parameters>",
cmd.description or "<no description>")
mt_irc:say(user.nick, usage)
end
})
mt_irc:register_bot_command("list", {
params = "",
description = "List available commands.",
func = function(user, args)
local cmdlist = "Available commands: "
for name, cmd in pairs(mt_irc.bot_commands) do
cmdlist = cmdlist..name..", "
end
irc.say(from, "There's No player named `"..args.."'");
end;
});
mt_irc:say(user.nick, cmdlist
.." -- Use 'help <command name>' to get help about a specific command.")
end
})
local starttime = os.time();
mt_irc.register_bot_command("uptime", {
params = "";
description = "Tell how much time the server has been up";
privs = { shout=true; };
func = function ( name, param )
local t = os.time();
local diff = os.difftime(t, starttime);
local fmt = "Server has been running for %d:%02d:%02d";
irc.say(name, fmt:format(
mt_irc:register_bot_command("whereis", {
params = "<player>",
description = "Tell the location of <player>",
func = function(user, args)
if args == "" then
mt_irc:bot_help(user, "whereis")
return
end
local player = minetest.env:get_player_by_name(args)
if player then
local fmt = "Player %s is at (%.2f,%.2f,%.2f)"
local pos = player:getpos()
mt_irc:say(user.nick, fmt:format(args, pos.x, pos.y, pos.z))
return
end
mt_irc:say(user.nick, "There is No player named '"..args.."'")
end
})
local starttime = os.time()
mt_irc:register_bot_command("uptime", {
description = "Tell how much time the server has been up",
func = function(user, args)
local cur_time = os.time()
local diff = os.difftime(cur_time, starttime)
local fmt = "Server has been running for %d:%02d:%02d"
mt_irc:say(user.nick, fmt:format(
math.floor(diff / 60 / 60),
math.mod(math.floor(diff / 60), 60),
math.mod(math.floor(diff), 60)
));
end;
});
))
end
})

View File

@ -1,232 +1,35 @@
-- IRC Mod for Minetest
-- By Diego Martínez <kaeza@users.sf.net>
--
-- This mod allows to tie a Minetest server to an IRC channel.
--
-- This program is free software. It comes without any warranty, to
-- the extent permitted by applicable law. You can redistribute it
-- and/or modify it under the terms of the Do What The Fuck You Want
-- To Public License, Version 2, as published by Sam Hocevar. See
-- http://sam.zoy.org/wtfpl/COPYING for more details.
--
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
local irc = require("irc");
mt_irc.callbacks = { };
mt_irc._callback = function ( name, breakonreturn, ... )
local list = mt_irc.callbacks[name];
if (not list) then return; end
for n = 1, #list do
local r = list[n](...);
if (breakonreturn and (r ~= nil)) then return r; end
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
if mt_irc.connected then
mt_irc:say("*** "..name.." joined the game")
end
end
mt_irc.register_callback = function ( name, func )
local list = mt_irc.callbacks[name];
if (not list) then
list = { };
mt_irc.callbacks[name] = list;
end
list[#list + 1] = func;
end
minetest.register_on_joinplayer(function ( player )
local name = player:get_player_name();
mt_irc.connected_players[name] = mt_irc.auto_join;
if (not mt_irc.connect_ok) then return; end
mt_irc.say("*** "..name.." joined the game");
end);
minetest.register_on_leaveplayer(function ( player )
local name = player:get_player_name();
mt_irc.connected_players[name] = nil;
if (not mt_irc.connect_ok) then return; end
mt_irc.say("*** "..name.." left the game");
end);
irc.register_callback("connect", function ( )
mt_irc.got_motd = true;
irc.join(mt_irc.channel);
end);
irc.register_callback("channel_msg", function ( channel, from, message )
if (not mt_irc.connect_ok) then return; end
local t = {
name=(from or "<BUG:no one is saying this>");
message=(message or "<BUG:there is no message>");
server=mt_irc.server;
port=mt_irc.port;
channel=mt_irc.channel;
};
local text = mt_irc.message_format_in:gsub("%$%(([^)]+)%)", t)
if (mt_irc._callback("channel_msg", from, message, text)) then return; end
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, text); end
end
end);
local function bot_command ( from, message )
local pos = message:find(" ", 1, true);
local cmd, args;
if (pos) then
cmd = message:sub(1, pos - 1);
args = message:sub(pos + 1);
else
cmd = message;
args = "";
end
if (not mt_irc.bot_commands[cmd]) then
mt_irc.say(from, "Unknown command `"..cmd.."'. Try `!help'.");
return;
end
mt_irc.bot_commands[cmd].func(from, args);
end
irc.register_callback("private_msg", function ( from, message )
if (not mt_irc.connect_ok) then return; end
local player_to;
local msg;
if (message:sub(1, 1) == "@") then
local pos = message:find(" ", 1, true);
if (not pos) then return; end
player_to = message:sub(2, pos - 1);
msg = message:sub(pos + 1);
elseif (message:sub(1, 1) == "!") then
bot_command(from, message:sub(2));
return;
else
irc.say(from, 'Message not sent! Please use "!help" to see possible commands.');
irc.say(from, ' Or use the "@playername Message" syntax to send a private message.');
return;
end
if (not mt_irc.connected_players[player_to]) then
irc.say(from, "User `"..player_to.."' is not connected to IRC.");
return;
end
local t = {
name=(from or "<BUG:no one is saying this>");
message=(msg or "<BUG:there is no message>");
server=mt_irc.server;
port=mt_irc.port;
channel=mt_irc.channel;
};
local text = mt_irc.message_format_in:expandvars(t);
if (mt_irc._callback("private_msg", from, player_to, message, text)) then return; end
minetest.chat_send_player(player_to, "PRIVATE: "..text);
mt_irc.say(from, "Message sent!")
end);
irc.register_callback("kick", function(chaninfo, nick, kicker)
if nick == mt_irc.server_nick then
minetest.chat_send_all("IRC: Bot was kicked by "..kicker..".");
mt_irc.got_motd = false;
mt_irc.connect_ok = false;
irc.quit("Kicked");
end
end);
irc.register_callback("nick_change", function ( from, old_nick )
if (not mt_irc.connect_ok) then return; end
mt_irc._callback("nick_change", false, old_nick, from);
local text = "["..old_nick.." changed his nick to "..from.."]";
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, text); end
end
end);
irc.register_callback("join", function ( servinfo, from )
local text = "*** "..from.." joined "..mt_irc.channel;
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, text); end
end
end);
irc.register_callback("part", function ( servinfo, from, part_msg )
mt_irc._callback("part", false, from, part_msg);
local text
if part_msg then
text = "*** "..from.." left "..mt_irc.channel.." ("..part_msg..")";
else
text = "*** "..from.." left "..mt_irc.channel;
end
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, text); end
end
end);
irc.register_callback("channel_act", function ( servinfo, from, message)
if (not mt_irc.connect_ok) then return; end
local text = "*** "..from.." "..message;
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, text); end
end
end);
minetest.register_on_chat_message(function ( name, message )
if (not mt_irc.connect_ok) then return; end
if (message:sub(1, 1) == "/") then return; end
if (not mt_irc.connected_players[name]) then return; end
if (not minetest.check_player_privs(name, {shout=true})) then
return;
end
if (not mt_irc.buffered_messages) then
mt_irc.buffered_messages = { };
end
mt_irc.buffered_messages[#mt_irc.buffered_messages + 1] = {
name = name;
message = message;
};
end);
minetest.register_on_shutdown(function ( )
irc.quit("Game shutting down.");
for n = 1, 5 do
irc.poll();
end
end);
irc.handlers.on_error = function (...) --( from, respond_to )
for k, v in pairs(mt_irc.connected_players) do
if (v) then minetest.chat_send_player(k, "IRC: Bot had a network error. Reconnecting in 5 seconds..."); end
end
for _, v in ipairs({...}) do
minetest.chat_send_all(dump(v));
end
irc.quit("Network error");
for n = 1, 5 do
irc.poll();
end
mt_irc.got_motd = false;
mt_irc.connect_ok = false;
minetest.after(5, mt_irc.connect);
end
irc.handlers.on_err_nicknameinuse = function ( from, respond_to )
irc.quit("Nick in use");
for n = 1, 5 do
irc.poll();
end
mt_irc.got_motd = false;
mt_irc.connect_ok = false;
local n = (tonumber(mt_irc.server_nick:sub(-1)) or 0) + 1;
if (n == 10) then n = 1; end
mt_irc.server_nick = mt_irc.server_nick:sub(1, -2)..n;
mt_irc.connect();
end
-- TESTING
--[[
mt_irc.register_callback("part", function ( nick, part_msg )
mt_irc.say("TEST: "..nick.." has left the building!");
end)
mt_irc.register_callback("nick_change", function ( old_nick, new_nick )
mt_irc.say("TEST: "..old_nick.." -> "..new_nick);
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
if mt_irc.connected then
mt_irc:say("*** "..name.." left the game")
end
end)
]]
minetest.register_on_chat_message(function(name, message)
if not mt_irc.connected
or message:sub(1, 1) == "/"
or not mt_irc.joined_players[name]
or (not minetest.check_player_privs(name, {shout=true})) then
return
end
mt_irc:queueMsg(mt_irc.msgs.playerMessage(mt_irc.config.channel, name, message))
end)
minetest.register_on_shutdown(function()
mt_irc:disconnect("Game shutting down.")
end)

View File

@ -1,124 +1,104 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
-- IRC Mod for Minetest
-- By Diego Martínez <kaeza@users.sf.net>
--
-- This mod allows to tie a Minetest server to an IRC channel.
--
-- This program is free software. It comes without any warranty, to
-- the extent permitted by applicable law. You can redistribute it
-- and/or modify it under the terms of the Do What The Fuck You Want
-- To Public License, Version 2, as published by Sam Hocevar. See
-- http://sam.zoy.org/wtfpl/COPYING for more details.
--
-- Note: This file does NOT conatin every chat command, only general ones.
-- Feature-specific commands (like /join) are in their own files.
local irc = require("irc");
minetest.register_chatcommand("irc_msg", {
params = "<name> <message>";
description = "Send a private message to an IRC user";
privs = { shout=true; };
func = function ( name, param )
if (not mt_irc.connect_ok) then
minetest.chat_send_player(name, "IRC: You are not connected, use /irc_connect.");
return;
end
local found, _, toname, msg = param:find("^([^%s#]+)%s(.+)");
if not found then
minetest.chat_send_player(name, "Invalid usage, see /help irc_msg.");
return;
end
local t = {name=name, message=msg};
local text = mt_irc.message_format_out:expandvars(t);
mt_irc.say(toname, text);
minetest.chat_send_player(name, "Message sent!")
end;
});
minetest.register_chatcommand("irc_connect", {
params = "";
description = "Connect to the IRC server";
privs = { irc_admin=true; };
func = function ( name, param )
if (mt_irc.connect_ok) then
minetest.chat_send_player(name, "IRC: You are already connected.");
return;
end
mt_irc.connect();
minetest.chat_send_player(name, "IRC: You are now connected.");
irc.say(mt_irc.channel, name.." joined the channel.");
end;
});
minetest.register_chatcommand("irc_disconnect", {
params = "";
description = "Disconnect from the IRC server";
privs = { irc_admin=true; };
func = function ( name, param )
if (not mt_irc.connect_ok) then
minetest.chat_send_player(name, "IRC: You are not connected.");
return;
end
irc.quit("Manual BOT Disconnection");
minetest.chat_send_player(name, "IRC: You are now disconnected.");
mt_irc.connect_ok = false;
end;
});
minetest.register_chatcommand("irc_reconnect", {
params = "";
description = "Reconnect to the IRC server";
privs = { irc_admin=true; };
func = function ( name, param )
if (mt_irc.connect_ok) then
irc.quit("Reconnecting BOT...");
minetest.chat_send_player(name, "IRC: Reconnecting bot...");
mt_irc.got_motd = true;
mt_irc.connect_ok = false;
end
mt_irc.connect();
end;
});
minetest.register_chatcommand("join", {
params = "";
description = "Join the IRC channel";
privs = { shout=true; };
func = function ( name, param )
mt_irc.join(name);
end;
});
minetest.register_chatcommand("part", {
params = "";
description = "Part the IRC channel";
privs = { shout=true; };
func = function ( name, param )
mt_irc.part(name);
end;
});
minetest.register_chatcommand("me", {
params = "<action>";
description = "chat action (eg. /me orders a pizza)";
privs = { shout=true };
params = "<name> <message>",
description = "Send a private message to an IRC user",
privs = {shout=true},
func = function(name, param)
minetest.chat_send_all("* "..name.." "..param);
irc.say(mt_irc.channel, "* "..name.." "..param);
end,
})
minetest.register_chatcommand("who", {
-- TODO: This duplicates code from !who
params = "";
description = "Tell who is currently on the channel";
privs = { shout=true; };
func = function ( name, param )
local s = "";
for k, v in pairs(mt_irc.connected_players) do
if (v) then
s = s.." "..k;
if not mt_irc.connected then
minetest.chat_send_player(name, "Not connected to IRC. Use /irc_connect to connect.")
return
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
end
local validNick = false
for nick, user in pairs(mt_irc.conn.channels[mt_irc.config.channel].users) do
if nick:lower() == toname:lower() then
validNick = true
break
end
end
minetest.chat_send_player(name, "Players On Channel:"..s);
end;
});
if toname:find("Serv|Bot") then
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
end
mt_irc:queueMsg(mt_irc.msgs.playerMessage(toname, name, message))
minetest.chat_send_player(name, "Message sent!")
end
})
minetest.register_chatcommand("irc_connect", {
description = "Connect to the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
if mt_irc.connected then
minetest.chat_send_player(name, "You are already connected to IRC.")
return
end
minetest.chat_send_player(name, "IRC: Connecting...")
mt_irc:connect()
end
})
minetest.register_chatcommand("irc_disconnect", {
description = "Disconnect from the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
if not mt_irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
end
mt_irc:disconnect("Manual disconnect.")
end
})
minetest.register_chatcommand("irc_reconnect", {
description = "Reconnect to the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
if not mt_irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
end
mt_irc:disconnect("Reconnecting...")
mt_irc:connect()
end
})
minetest.register_chatcommand("irc_quote", {
params = "<command>",
description = "Send a raw command to the IRC server.",
privs = {irc_admin=true},
func = function(name, param)
if not mt_irc.connected then
minetest.chat_send_player(name, "You are not connected to IRC.")
return
end
mt_irc:queueMsg(param)
minetest.chat_send_player(name, "Command sent!")
end
})
local oldme = minetest.chatcommands["me"].func
minetest.chatcommands["me"].func = function(name, param)
oldme(name, param)
mt_irc:say(("* %s %s"):format(name, param))
end

View File

@ -1,72 +1,91 @@
-- IRC Mod for Minetest
-- By Diego Martínez <kaeza@users.sf.net>
--
-- This mod allows to tie a Minetest server to an IRC channel.
--
-- This program is free software. It comes without any warranty, to
-- the extent permitted by applicable law. You can redistribute it
-- and/or modify it under the terms of the Do What The Fuck You Want
-- To Public License, Version 2, as published by Sam Hocevar. See
-- http://sam.zoy.org/wtfpl/COPYING for more details.
--
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
-- *************************
-- ** BASIC USER SETTINGS **
-- *************************
local config = {}
-------------------------
-- BASIC USER SETTINGS --
-------------------------
-- Nickname (string, default "minetest-"..<server-id>)
-- (<server-id> is a random string of 6 hexidecimal numbers).
config.nick = minetest.setting_get("irc.nick")
-- Server to connect on joinplayer (string, default "irc.freenode.net")
mt_irc.server = minetest.setting_get("mt_irc.server") or "irc.freenode.net";
config.server = minetest.setting_get("irc.server") or "irc.freenode.net"
-- Port to connect on joinplayer (number, default 6667)
mt_irc.port = tonumber(minetest.setting_get("mt_irc.port")) or 6667;
config.port = tonumber(minetest.setting_get("irc.port")) or 6667
-- NickServ password
config.NSPass = minetest.setting_get("irc.NSPass")
-- SASL password (Blank to disable SASL authentication)
config.SASLPass = minetest.setting_get("irc.SASLPass")
-- Channel to connect on joinplayer (string, default "##mt-irc-mod")
mt_irc.channel = minetest.setting_get("mt_irc.channel") or "##mt-irc-mod";
config.channel = minetest.setting_get("irc.channel") or "##mt-irc-mod"
-- ***********************
-- ** ADVANCED SETTINGS **
-- ***********************
-- Key for the channel (string, default nil)
config.key = minetest.setting_get("irc.key")
-- Time between chat updates in seconds (number, default 0.2).
mt_irc.dtime = tonumber(minetest.setting_get("mt_irc.dtime")) or 0.2;
-----------------------
-- ADVANCED SETTINGS --
-----------------------
-- Server password (string, default "")
config.password = minetest.setting_get("irc.password")
-- SASL username
config.SASLUser = minetest.setting_get("irc.SASLUser") or config.nick
-- Enable a TLS connection, requires LuaSEC (bool, default false)
config.secure = minetest.setting_getbool("irc.secure")
-- Time between chat updates in seconds (number, default 2.1). Setting this too low can cause "Excess flood" disconnects.
config.interval = tonumber(minetest.setting_get("irc.interval")) or 2.0
-- Underlying socket timeout in seconds (number, default 60.0).
mt_irc.timeout = tonumber(minetest.setting_get("mt_irc.timeout")) or 60.0;
config.timeout = tonumber(minetest.setting_get("irc.timeout")) or 60.0
-- Nickname when using single conection (string, default "minetest-"..<server-id>);
-- (<server-id> is a random string of 6 hexidecimal numbers).
mt_irc.server_nick = minetest.setting_get("mt_irc.server_nick");
-- Password to use when using single connection (string, default "")
mt_irc.password = minetest.setting_get("mt_irc.password");
-- Prefix to use for bot commands (char, default '!')
config.command_prefix = minetest.setting_get("irc.command_prefix") or '!'
config.command_prefix = config.command_prefix:sub(1, 1)
-- The format of messages sent to IRC server (string, default "<$(name)> $(message)")
-- See `README.txt' for the macros supported here.
mt_irc.message_format_out = minetest.setting_get("mt_irc.message_format_out") or "<$(name)> $(message)";
config.format_out = minetest.setting_get("irc.format_out") or "<$(name)> $(message)"
-- The format of messages sent to IRC server (string, default "<$(name)@IRC> $(message)")
-- See `README.txt' for the macros supported here.
mt_irc.message_format_in = minetest.setting_get("mt_irc.message_format_in") or "<$(name)@IRC> $(message)";
config.format_in = minetest.setting_get("irc.format_in") or "<$(name)@IRC> $(message)"
-- Enable debug output (boolean, default false)
mt_irc.debug = not minetest.setting_getbool("mt_irc.disable_debug");
config.debug = minetest.setting_getbool("irc.debug")
-- Whether to automatically join the channed when player joins
-- Whether to enable players joining and parting the channel
config.enable_player_part = not minetest.setting_getbool("irc.disable_player_part")
-- Whether to automatically join the channel when player joins
-- (boolean, default true)
mt_irc.auto_join = not minetest.setting_getbool("mt_irc.disable_auto_join");
config.auto_join = not minetest.setting_getbool("irc.disable_auto_join")
-- Whether to automatically connect to the server on mod load
-- (boolean, default true)
mt_irc.auto_connect = not minetest.setting_getbool("mt_irc.disable_auto_connect");
-- (boolean, default true)
config.auto_connect = not minetest.setting_getbool("irc.disable_auto_connect")
-- Set default server nick if not specified.
if (not mt_irc.server_nick) then
local pr = PseudoRandom(os.time());
if not config.nick then
local pr = PseudoRandom(os.time())
-- Workaround for bad distribution in minetest PRNG implementation.
local fmt = "minetest-%02X%02X%02X";
mt_irc.server_nick = fmt:format(
config.nick = ("MT-%02X%02X%02X"):format(
pr:next(0, 255),
pr:next(0, 255),
pr:next(0, 255)
);
)
end
mt_irc.config = config

View File

@ -1,22 +0,0 @@
-- IRC Mod for Minetest
-- By Diego Martínez <kaeza@users.sf.net>
--
-- This mod allows to tie a Minetest server to an IRC channel.
--
-- This program is free software. It comes without any warranty, to
-- the extent permitted by applicable law. You can redistribute it
-- and/or modify it under the terms of the Do What The Fuck You Want
-- To Public License, Version 2, as published by Sam Hocevar. See
-- http://sam.zoy.org/wtfpl/COPYING for more details.
--
-- TODO
--[[
local MODPATH = mt_irc.modpath;
local function load_friends_list ( )
end
]]

211
src/hooks.lua Normal file
View File

@ -0,0 +1,211 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
mt_irc.hooks = {}
mt_irc.registered_hooks = {}
function mt_irc:doHook(conn)
for name, hook in pairs(self.registered_hooks) do
for _, func in pairs(hook) do
conn:hook(name, func)
end
end
end
function mt_irc:register_hook(name, func)
self.registered_hooks[name] = self.registered_hooks[name] or {}
table.insert(self.registered_hooks[name], func)
end
function mt_irc.hooks.raw(line)
if mt_irc.config.debug then
print("RECV: "..line)
end
end
function mt_irc.hooks.send(line)
if mt_irc.config.debug then
print("SEND: "..line)
end
end
function mt_irc.hooks.chat(user, channel, message)
-- Strip bold, underline, and colors
message = message:gsub('\2', '')
message = message:gsub('\31', '')
message = message:gsub('\3[0-9][0-9,]*', '')
if channel == mt_irc.conn.nick then
mt_irc.conn:invoke("PrivateMessage", user, message)
else
local c = string.char(1)
local found, _, action = message:find(("^%sACTION ([^%s]*)%s$"):format(c, c, c))
if found then
mt_irc.conn:invoke("OnChannelAction", user, channel, action)
else
mt_irc.conn:invoke("OnChannelChat", user, channel, message)
end
end
end
function mt_irc.hooks.channelChat(user, channel, message)
local t = {
access=user.access,
name=user.nick,
message=message,
server=mt_irc.conn.host,
port=mt_irc.conn.port,
channel=channel
}
local text = mt_irc.config.format_in:expandvars(t)
mt_irc:sendLocal(text)
end
function mt_irc.hooks.pm(user, message)
local player_to
local msg
if message:sub(1, 1) == "@" then
local found, _, player_to, message = message:find("^.([^%s]+)%s(.+)$")
if not mt_irc.joined_players[player_to] then
mt_irc:say(user.nick, "User '"..player_to.."' has parted.")
return
elseif not minetest.get_player_by_name(player_to) then
mt_irc:say(user.nick, "User '"..player_to.."' is not in the game.")
return
end
local t = {
name=user.nick,
message=message,
server=mt_irc.server,
port=mt_irc.port,
channel=mt_irc.channel
}
local text = mt_irc.config.format_in:expandvars(t)
minetest.chat_send_player(player_to, "PM: "..text, false)
mt_irc:say(user.nick, "Message sent!")
elseif message:sub(1, 1) == "!" then
mt_irc:bot_command(user, message:sub(2))
return
else
mt_irc:say(user.nick, "Invalid command. Use '"
..mt_irc.config.command_prefix
.."list' to see possible commands.")
return
end
end
function mt_irc.hooks.kick(channel, target, prefix, reason)
if target == mt_irc.conn.nick then
minetest.chat_send_all("IRC: kicked from "..channel.." by "..prefix.nick..".")
mt_irc:disconnect("Kicked")
else
mt_irc:sendLocal(("-!- %s was kicked from %s by %s [%s]")
:format(target, channel, prefix.nick, reason))
end
end
function mt_irc.hooks.notice(user, target, message)
if not user.nick then return end --Server NOTICEs
if target == mt_irc.conn.nick then return end
mt_irc:sendLocal("--"..user.nick.."@IRC-- "..message)
end
function mt_irc.hooks.mode(user, target, modes, ...)
local by = ""
if user.nick then
by = " by "..user.nick
end
local options = ""
for _, option in pairs({...}) do
options = options.." "..option
end
minetest.chat_send_all(("-!- mode/%s [%s%s]%s")
:format(target, modes, options, by))
end
function mt_irc.hooks.nick(user, newNick)
mt_irc:sendLocal(("-!- %s is now known as %s")
:format(user.nick, newNick))
end
function mt_irc.hooks.join(user, channel)
mt_irc:sendLocal(("-!- %s joined %s")
:format(user.nick, channel))
end
function mt_irc.hooks.part(user, channel, reason)
reason = reason or ""
mt_irc:sendLocal(("-!- %s has left %s [%s]")
:format(user.nick, channel, reason))
end
function mt_irc.hooks.quit(user, reason)
mt_irc:sendLocal(("-!- %s has quit [%s]")
:format(user.nick, reason))
end
function mt_irc.hooks.action(user, channel, message)
mt_irc:sendLocal(("* %s@IRC %s")
:format(user.nick, message))
end
function mt_irc.hooks.disconnect(message, isError)
mt_irc.connected = false
if isError then
minetest.log("error", "IRC: Error: Disconnected, reconnecting in one minute.")
minetest.chat_send_all("IRC: Error: Disconnected, reconnecting in one minute.")
minetest.after(60, mt_irc.connect)
else
minetest.log("action", "IRC: Disconnected.")
minetest.chat_send_all("IRC: Disconnected.")
end
end
function mt_irc.hooks.preregister(conn)
if not (mt_irc.config.SASLUser and mt_irc.config.SASLPass) then return end
local authString = mt_irc.b64e(
("%s\x00%s\x00%s"):format(
mt_irc.config.SASLUser,
mt_irc.config.SASLUser,
mt_irc.config.SASLPass)
)
conn:send("CAP REQ sasl")
conn:send("AUTHENTICATE PLAIN")
conn:send("AUTHENTICATE "..authString)
--LuaIRC will send CAP END
end
mt_irc:register_hook("PreRegister", mt_irc.hooks.preregister)
mt_irc:register_hook("OnRaw", mt_irc.hooks.raw)
mt_irc:register_hook("OnSend", mt_irc.hooks.send)
mt_irc:register_hook("OnChat", mt_irc.hooks.chat)
mt_irc:register_hook("OnPart", mt_irc.hooks.part)
mt_irc:register_hook("OnKick", mt_irc.hooks.kick)
mt_irc:register_hook("OnJoin", mt_irc.hooks.join)
mt_irc:register_hook("OnQuit", mt_irc.hooks.quit)
mt_irc:register_hook("NickChange", mt_irc.hooks.nick)
mt_irc:register_hook("OnChannelAction", mt_irc.hooks.action)
mt_irc:register_hook("PrivateMessage", mt_irc.hooks.pm)
mt_irc:register_hook("OnNotice", mt_irc.hooks.notice)
mt_irc:register_hook("OnChannelChat", mt_irc.hooks.channelChat)
mt_irc:register_hook("OnModeChange", mt_irc.hooks.mode)
mt_irc:register_hook("OnDisconnect", mt_irc.hooks.disconnect)

View File

@ -1,127 +1,144 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
-- IRC Mod for Minetest
-- By Diego Martínez <kaeza@users.sf.net>
--
-- This mod allows to tie a Minetest server to an IRC channel.
--
-- This program is free software. It comes without any warranty, to
-- the extent permitted by applicable law. You can redistribute it
-- and/or modify it under the terms of the Do What The Fuck You Want
-- To Public License, Version 2, as published by Sam Hocevar. See
-- http://sam.zoy.org/wtfpl/COPYING for more details.
--
local MODPATH = minetest.get_modpath("irc");
mt_irc = {
connected = false,
cur_time = 0,
message_buffer = {},
recent_message_count = 0,
joined_players = {},
modpath = minetest.get_modpath("irc")
}
mt_irc = { };
-- To find LuaIRC and LuaSocket
package.path = mt_irc.modpath.."/?/init.lua;"
..mt_irc.modpath.."/irc/?.lua;"
..mt_irc.modpath.."/?.lua;"
..package.path
package.cpath = mt_irc.modpath.."/lib?.so;"
..mt_irc.modpath.."/?.dll;"
..package.cpath
dofile(MODPATH.."/config.lua");
local irc = require('irc')
mt_irc.cur_time = 0;
mt_irc.buffered_messages = { };
mt_irc.connected_players = { };
mt_irc.modpath = MODPATH;
package.path = MODPATH.."/?.lua;"..package.path;
package.cpath = MODPATH.."/lib?.so;"..MODPATH.."/?.dll;"..package.cpath;
local irc = require 'irc';
irc.DEBUG = ((mt_irc.debug and true) or false);
dofile(mt_irc.modpath.."/config.lua")
dofile(mt_irc.modpath.."/messages.lua")
dofile(mt_irc.modpath.."/hooks.lua")
dofile(mt_irc.modpath.."/callback.lua")
dofile(mt_irc.modpath.."/chatcmds.lua")
dofile(mt_irc.modpath.."/botcmds.lua")
dofile(mt_irc.modpath.."/util.lua")
if mt_irc.config.enable_player_part then
dofile(mt_irc.modpath.."/player_part.lua")
else
setmetatable(mt_irc.joined_players, {__index = function(index) return true end})
end
minetest.register_privilege("irc_admin", {
description = "Allow IRC administrative tasks to be performed.";
give_to_singleplayer = true;
});
description = "Allow IRC administrative tasks to be performed.",
give_to_singleplayer = true
})
minetest.register_globalstep(function(dtime)
if (not mt_irc.connect_ok) then return end
mt_irc.cur_time = mt_irc.cur_time + dtime
if (mt_irc.cur_time >= mt_irc.dtime) then
if (mt_irc.buffered_messages) then
for _, msg in ipairs(mt_irc.buffered_messages) do
local t = {
name=(msg.name or "<BUG:no one is saying this>"),
message=(msg.message or "<BUG:there is no message>")
}
local text = mt_irc.message_format_out:expandvars(t)
irc.say(mt_irc.channel, text)
end
mt_irc.buffered_messages = nil
minetest.register_globalstep(function(dtime) return mt_irc:step(dtime) end)
function mt_irc:step(dtime)
if not self.connected then return end
-- Tick down the recent message count
self.cur_time = self.cur_time + dtime
if self.cur_time >= self.config.interval then
if self.recent_message_count > 0 then
self.recent_message_count = self.recent_message_count - 1
end
irc.poll()
mt_irc.cur_time = mt_irc.cur_time - mt_irc.dtime
self.cur_time = self.cur_time - self.config.interval
end
end)
mt_irc.part = function ( name )
if (not mt_irc.connected_players[name]) then
minetest.chat_send_player(name, "IRC: You are not in the channel.");
return;
-- Hooks will manage incoming messages and errors
if not pcall(function() mt_irc.conn:think() end) then
return
end
mt_irc.connected_players[name] = nil;
minetest.chat_send_player(name, "IRC: You are now out of the channel.");
end
mt_irc.join = function ( name )
if (mt_irc.connected_players[name]) then
minetest.chat_send_player(name, "IRC: You are already in the channel.");
return;
end
mt_irc.connected_players[name] = true;
minetest.chat_send_player(name, "IRC: You are now in the channel.");
end
mt_irc.connect = function ( )
mt_irc.connect_ok = irc.connect({
network = mt_irc.server;
port = mt_irc.port;
nick = mt_irc.server_nick;
pass = mt_irc.password;
timeout = mt_irc.timeout;
channel = mt_irc.channel;
});
if (not mt_irc.connect_ok) then
local s = "DEBUG: irc.connect failed";
minetest.debug(s);
minetest.chat_send_all(s);
return;
end
while (not mt_irc.got_motd) do
irc.poll();
-- Send messages in the buffer
if #self.message_buffer > 10 then
minetest.log("error", "IRC: Message buffer overflow, clearing.")
self.message_buffer = {}
elseif #self.message_buffer > 0 then
for i=1, #self.message_buffer do
if self.recent_message_count > 4 then break end
self.recent_message_count = self.recent_message_count + 1
local msg = table.remove(self.message_buffer, 1) --Pop the first message
self:send(msg)
end
end
end
mt_irc.say = function ( to, msg )
if (not msg) then
msg = to;
to = mt_irc.channel;
function mt_irc:connect()
if self.connected then
minetest.log("error", "IRC: Ignoring attempt to connect when already connected.")
return
end
to = to or mt_irc.channel;
msg = msg or "";
local msg2 = mt_irc._callback("msg_out", true, to, msg);
if ((type(msg2) == "boolean") and (not msg2)) then
return;
elseif (msg2 ~= nil) then
msg = tostring(msg);
self.conn = irc.new({
nick = self.config.nick,
username = "Minetest",
realname = "Minetest",
})
self:doHook(self.conn)
good, message = pcall(function()
mt_irc.conn:connect({
host = mt_irc.config.server,
port = mt_irc.config.port,
pass = mt_irc.config.password,
timeout = mt_irc.config.timeout,
secure = mt_irc.config.secure
})
end)
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() mt_irc:connect() end)
return
end
irc.say(to, msg);
if self.config.NSPass then
self:say("NickServ", "IDENTIFY "..self.config.NSPass)
end
self.conn:join(self.config.channel, self.config.key)
self.connected = true
minetest.log("action", "IRC: Connected!")
minetest.chat_send_all("IRC: Connected!")
end
mt_irc.irc = irc;
-- Misc helpers
-- Requested by Exio
string.expandvars = function ( s, vars )
return s:gsub("%$%(([^)]+)%)", vars);
function mt_irc:disconnect(message)
if self.connected then
--The OnDisconnect hook will clear self.connected and print a disconnect message
self.conn:disconnect(message)
end
end
dofile(MODPATH.."/callback.lua");
dofile(MODPATH.."/chatcmds.lua");
dofile(MODPATH.."/botcmds.lua");
dofile(MODPATH.."/friends.lua");
if (mt_irc.auto_connect) then
mt_irc.connect()
function mt_irc:say(to, message)
if not message then
message = to
to = self.config.channel
end
to = to or self.config.channel
self:queueMsg(self.msgs.privmsg(to, message))
end
function mt_irc:send(line)
self.conn:send(line)
end
if mt_irc.config.auto_connect then
mt_irc:connect()
end

File diff suppressed because it is too large Load Diff

View File

@ -1,475 +0,0 @@
---
-- Implementation of the Channel class
-- initialization {{{
local base = _G
local irc = require 'irc'
local misc = require 'irc.misc'
local socket = require 'socket'
local table = require 'table'
-- }}}
---
-- This module implements a channel object representing a single channel we
-- have joined.
module 'irc.channel'
-- object metatable {{{
-- TODO: this <br /> shouldn't be necessary - bug in luadoc
---
-- An object of the Channel class represents a single joined channel. It has
-- several table fields, and can be used in string contexts (returning the
-- channel name).<br />
-- @class table
-- @name Channel
-- @field name Name of the channel (read only)
-- @field topic Channel topic, if set (read/write, writing to this sends a
-- topic change request to the server for this channel)
-- @field chanmode Channel mode (public/private/secret) (read only)
-- @field members Array of all members of this channel
local mt = {
-- __index() {{{
__index = function(self, key)
if key == "name" then
return self._name
elseif key == "topic" then
return self._topic
elseif key == "chanmode" then
return self._chanmode
else
return _M[key]
end
end,
-- }}}
-- __newindex() {{{
__newindex = function(self, key, value)
if key == "name" then
return
elseif key == "topic" then
irc.send("TOPIC", self._name, value)
elseif key == "chanmode" then
return
else
base.rawset(self, key, value)
end
end,
-- }}}
-- __concat() {{{
__concat = function(first, second)
local first_str, second_str
if base.type(first) == "table" then
first_str = first._name
else
first_str = first
end
if base.type(second) == "table" then
second_str = second._name
else
second_str = second
end
return first_str .. second_str
end,
-- }}}
-- __tostring() {{{
__tostring = function(self)
return self._name
end
-- }}}
}
-- }}}
-- private methods {{{
-- set_basic_mode {{{
--
-- Sets a no-arg mode on a channel.
-- @name chan:set_basic_mode
-- @param self Channel object
-- @param set True to set the mode, false to unset it
-- @param letter Letter of the mode
local function set_basic_mode(self, set, letter)
if set then
irc.send("MODE", self.name, "+" .. letter)
else
irc.send("MODE", self.name, "-" .. letter)
end
end
-- }}}
-- }}}
-- internal methods {{{
-- TODO: is there a better way to do this? also, storing op/voice as initial
-- substrings of the username is just ugly
-- _add_user {{{
--
-- Add a user to the channel's internal user list.
-- @param self Channel object
-- @param user Nick of the user to add
-- @param mode Mode (op/voice) of the user, in symbolic form (@/+)
function _add_user(self, user, mode)
mode = mode or ''
self._members[user] = mode .. user
end
-- }}}
-- _remove_user {{{
--
-- Remove a user from the channel's internal user list.
-- @param self Channel object
-- @param user Nick of the user to remove
function _remove_user(self, user)
self._members[user] = nil
end
-- }}}
-- _change_status {{{
--
-- Change the op/voice status of a user in the channel's internal user list.
-- @param self Channel object
-- @param user Nick of the user to affect
-- @param on True if the mode is being set, false if it's being unset
-- @param mode 'o' for op, 'v' for voice
function _change_status(self, user, on, mode)
if on then
if mode == 'o' then
self._members[user] = '@' .. user
elseif mode == 'v' then
self._members[user] = '+' .. user
end
else
if (mode == 'o' and self._members[user]:sub(1, 1) == '@') or
(mode == 'v' and self._members[user]:sub(1, 1) == '+') then
self._members[user] = user
end
end
end
-- }}}
-- _change_nick {{{
--
-- Change the nick of a user in the channel's internal user list.
-- @param self Channel object
-- @param old_nick User's old nick
-- @param new_nick User's new nick
function _change_nick(self, old_nick, new_nick)
for member in self:each_member() do
local member_nick = member:gsub('@+', '')
if member_nick == old_nick then
local mode = self._members[old_nick]:sub(1, 1)
if mode ~= '@' and mode ~= '+' then mode = "" end
self._members[old_nick] = nil
self._members[new_nick] = mode .. new_nick
break
end
end
end
-- }}}
-- }}}
-- constructor {{{
---
-- Creates a new Channel object.
-- @param chan Name of the new channel
-- @return The new channel instance
function new(chan)
return base.setmetatable({_name = chan, _topic = {}, _chanmode = "",
_members = {}}, mt)
end
-- }}}
-- public methods {{{
-- iterators {{{
-- each_op {{{
---
-- Iterator over the ops in the channel
-- @param self Channel object
function each_op(self)
return function(state, arg)
return misc._value_iter(state, arg,
function(v)
return v:sub(1, 1) == "@"
end)
end,
self._members,
nil
end
-- }}}
-- each_voice {{{
---
-- Iterator over the voiced users in the channel
-- @param self Channel object
function each_voice(self)
return function(state, arg)
return misc._value_iter(state, arg,
function(v)
return v:sub(1, 1) == "+"
end)
end,
self._members,
nil
end
-- }}}
-- each_user {{{
---
-- Iterator over the normal users in the channel
-- @param self Channel object
function each_user(self)
return function(state, arg)
return misc._value_iter(state, arg,
function(v)
return v:sub(1, 1) ~= "@" and
v:sub(1, 1) ~= "+"
end)
end,
self._members,
nil
end
-- }}}
-- each_member {{{
---
-- Iterator over all users in the channel
-- @param self Channel object
function each_member(self)
return misc._value_iter, self._members, nil
end
-- }}}
-- }}}
-- return tables of users {{{
-- ops {{{
---
-- Gets an array of all the ops in the channel.
-- @param self Channel object
-- @return Array of channel ops
function ops(self)
local ret = {}
for nick in self:each_op() do
table.insert(ret, nick)
end
return ret
end
-- }}}
-- voices {{{
---
-- Gets an array of all the voiced users in the channel.
-- @param self Channel object
-- @return Array of channel voiced users
function voices(self)
local ret = {}
for nick in self:each_voice() do
table.insert(ret, nick)
end
return ret
end
-- }}}
-- users {{{
---
-- Gets an array of all the normal users in the channel.
-- @param self Channel object
-- @return Array of channel normal users
function users(self)
local ret = {}
for nick in self:each_user() do
table.insert(ret, nick)
end
return ret
end
-- }}}
-- members {{{
---
-- Gets an array of all the users in the channel.
-- @param self Channel object
-- @return Array of channel users
function members(self)
local ret = {}
-- not just returning self._members, since the return value shouldn't be
-- modifiable
for nick in self:each_member() do
table.insert(ret, nick)
end
return ret
end
-- }}}
-- }}}
-- setting modes {{{
-- ban {{{
-- TODO: hmmm, this probably needs an appropriate mask, rather than a nick
---
-- Ban a user from a channel.
-- @param self Channel object
-- @param name User to ban
function ban(self, name)
irc.send("MODE", self.name, "+b", name)
end
-- }}}
-- unban {{{
-- TODO: same here
---
-- Remove a ban on a user.
-- @param self Channel object
-- @param name User to unban
function unban(self, name)
irc.send("MODE", self.name, "-b", name)
end
-- }}}
-- voice {{{
---
-- Give a user voice on a channel.
-- @param self Channel object
-- @param name User to give voice to
function voice(self, name)
irc.send("MODE", self.name, "+v", name)
end
-- }}}
-- devoice {{{
---
-- Remove voice from a user.
-- @param self Channel object
-- @param name User to remove voice from
function devoice(self, name)
irc.send("MODE", self.name, "-v", name)
end
-- }}}
-- op {{{
---
-- Give a user ops on a channel.
-- @param self Channel object
-- @param name User to op
function op(self, name)
irc.send("MODE", self.name, "+o", name)
end
-- }}}
-- deop {{{
---
-- Remove ops from a user.
-- @param self Channel object
-- @param name User to remove ops from
function deop(self, name)
irc.send("MODE", self.name, "-o", name)
end
-- }}}
-- set_limit {{{
---
-- Set a channel limit.
-- @param self Channel object
-- @param new_limit New value for the channel limit (optional; limit is unset
-- if this argument isn't passed)
function set_limit(self, new_limit)
if new_limit then
irc.send("MODE", self.name, "+l", new_limit)
else
irc.send("MODE", self.name, "-l")
end
end
-- }}}
-- set_key {{{
---
-- Set a channel password.
-- @param self Channel object
-- @param key New channel password (optional; password is unset if this
-- argument isn't passed)
function set_key(self, key)
if key then
irc.send("MODE", self.name, "+k", key)
else
irc.send("MODE", self.name, "-k")
end
end
-- }}}
-- set_private {{{
---
-- Set the private state of a channel.
-- @param self Channel object
-- @param set True to set the channel as private, false to unset it
function set_private(self, set)
set_basic_mode(self, set, "p")
end
-- }}}
-- set_secret {{{
---
-- Set the secret state of a channel.
-- @param self Channel object
-- @param set True to set the channel as secret, false to unset it
function set_secret(self, set)
set_basic_mode(self, set, "s")
end
-- }}}
-- set_invite_only {{{
---
-- Set whether joining the channel requires an invite.
-- @param self Channel object
-- @param set True to set the channel invite only, false to unset it
function set_invite_only(self, set)
set_basic_mode(self, set, "i")
end
-- }}}
-- set_topic_lock {{{
---
-- If true, the topic can only be changed by an op.
-- @param self Channel object
-- @param set True to lock the topic, false to unlock it
function set_topic_lock(self, set)
set_basic_mode(self, set, "t")
end
-- }}}
-- set_no_outside_messages {{{
---
-- If true, users must be in the channel to send messages to it.
-- @param self Channel object
-- @param set True to require users to be in the channel to send messages to
-- it, false to remove this restriction
function set_no_outside_messages(self, set)
set_basic_mode(self, set, "n")
end
-- }}}
-- set moderated {{{
---
-- Set whether voice is required to speak.
-- @param self Channel object
-- @param set True to set the channel as moderated, false to unset it
function set_moderated(self, set)
set_basic_mode(self, set, "m")
end
-- }}}
-- }}}
-- accessors {{{
-- contains {{{
---
-- Test if a user is in the channel.
-- @param self Channel object
-- @param nick Nick to search for
-- @return True if the nick is in the channel, false otherwise
function contains(self, nick)
for member in self:each_member() do
local member_nick = member:gsub('@+', '')
if member_nick == nick then
return true
end
end
return false
end
-- }}}
-- }}}
-- }}}

View File

@ -1,191 +0,0 @@
---
-- This module holds various constants used by the IRC protocol.
module "irc.constants"
-- protocol constants {{{
IRC_MAX_MSG = 512
-- }}}
-- server replies {{{
replies = {
-- Command responses {{{
[001] = "RPL_WELCOME",
[002] = "RPL_YOURHOST",
[003] = "RPL_CREATED",
[004] = "RPL_MYINFO",
[005] = "RPL_BOUNCE",
[302] = "RPL_USERHOST",
[303] = "RPL_ISON",
[301] = "RPL_AWAY",
[305] = "RPL_UNAWAY",
[306] = "RPL_NOWAWAY",
[311] = "RPL_WHOISUSER",
[312] = "RPL_WHOISSERVER",
[313] = "RPL_WHOISOPERATOR",
[317] = "RPL_WHOISIDLE",
[318] = "RPL_ENDOFWHOIS",
[319] = "RPL_WHOISCHANNELS",
[314] = "RPL_WHOWASUSER",
[369] = "RPL_ENDOFWHOWAS",
[321] = "RPL_LISTSTART",
[322] = "RPL_LIST",
[323] = "RPL_LISTEND",
[325] = "RPL_UNIQOPIS",
[324] = "RPL_CHANNELMODEIS",
[331] = "RPL_NOTOPIC",
[332] = "RPL_TOPIC",
[341] = "RPL_INVITING",
[342] = "RPL_SUMMONING",
[346] = "RPL_INVITELIST",
[347] = "RPL_ENDOFINVITELIST",
[348] = "RPL_EXCEPTLIST",
[349] = "RPL_ENDOFEXCEPTLIST",
[351] = "RPL_VERSION",
[352] = "RPL_WHOREPLY",
[315] = "RPL_ENDOFWHO",
[353] = "RPL_NAMREPLY",
[366] = "RPL_ENDOFNAMES",
[364] = "RPL_LINKS",
[365] = "RPL_ENDOFLINKS",
[367] = "RPL_BANLIST",
[368] = "RPL_ENDOFBANLIST",
[371] = "RPL_INFO",
[374] = "RPL_ENDOFINFO",
[375] = "RPL_MOTDSTART",
[372] = "RPL_MOTD",
[376] = "RPL_ENDOFMOTD",
[381] = "RPL_YOUREOPER",
[382] = "RPL_REHASHING",
[383] = "RPL_YOURESERVICE",
[391] = "RPL_TIME",
[392] = "RPL_USERSSTART",
[393] = "RPL_USERS",
[394] = "RPL_ENDOFUSERS",
[395] = "RPL_NOUSERS",
[200] = "RPL_TRACELINK",
[201] = "RPL_TRACECONNECTING",
[202] = "RPL_TRACEHANDSHAKE",
[203] = "RPL_TRACEUNKNOWN",
[204] = "RPL_TRACEOPERATOR",
[205] = "RPL_TRACEUSER",
[206] = "RPL_TRACESERVER",
[207] = "RPL_TRACESERVICE",
[208] = "RPL_TRACENEWTYPE",
[209] = "RPL_TRACECLASS",
[210] = "RPL_TRACERECONNECT",
[261] = "RPL_TRACELOG",
[262] = "RPL_TRACEEND",
[211] = "RPL_STATSLINKINFO",
[212] = "RPL_STATSCOMMANDS",
[219] = "RPL_ENDOFSTATS",
[242] = "RPL_STATSUPTIME",
[243] = "RPL_STATSOLINE",
[221] = "RPL_UMODEIS",
[234] = "RPL_SERVLIST",
[235] = "RPL_SERVLISTEND",
[221] = "RPL_UMODEIS",
[251] = "RPL_LUSERCLIENT",
[252] = "RPL_LUSEROP",
[253] = "RPL_LUSERUNKNOWN",
[254] = "RPL_LUSERCHANNELS",
[255] = "RPL_LUSERME",
[256] = "RPL_ADMINME",
[257] = "RPL_ADMINLOC1",
[258] = "RPL_ADMINLOC2",
[259] = "RPL_ADMINEMAIL",
[263] = "RPL_TRYAGAIN",
-- }}}
-- Error codes {{{
[401] = "ERR_NOSUCHNICK", -- No such nick/channel
[402] = "ERR_NOSUCHSERVER", -- No such server
[403] = "ERR_NOSUCHCHANNEL", -- No such channel
[404] = "ERR_CANNOTSENDTOCHAN", -- Cannot send to channel
[405] = "ERR_TOOMANYCHANNELS", -- You have joined too many channels
[406] = "ERR_WASNOSUCHNICK", -- There was no such nickname
[407] = "ERR_TOOMANYTARGETS", -- Duplicate recipients. No message delivered
[408] = "ERR_NOSUCHSERVICE", -- No such service
[409] = "ERR_NOORIGIN", -- No origin specified
[411] = "ERR_NORECIPIENT", -- No recipient given
[412] = "ERR_NOTEXTTOSEND", -- No text to send
[413] = "ERR_NOTOPLEVEL", -- No toplevel domain specified
[414] = "ERR_WILDTOPLEVEL", -- Wildcard in toplevel domain
[415] = "ERR_BADMASK", -- Bad server/host mask
[421] = "ERR_UNKNOWNCOMMAND", -- Unknown command
[422] = "ERR_NOMOTD", -- MOTD file is missing
[423] = "ERR_NOADMININFO", -- No administrative info available
[424] = "ERR_FILEERROR", -- File error
[431] = "ERR_NONICKNAMEGIVEN", -- No nickname given
[432] = "ERR_ERRONEUSNICKNAME", -- Erroneus nickname
[433] = "ERR_NICKNAMEINUSE", -- Nickname is already in use
[436] = "ERR_NICKCOLLISION", -- Nickname collision KILL
[437] = "ERR_UNAVAILRESOURCE", -- Nick/channel is temporarily unavailable
[441] = "ERR_USERNOTINCHANNEL", -- They aren't on that channel
[442] = "ERR_NOTONCHANNEL", -- You're not on that channel
[443] = "ERR_USERONCHANNEL", -- User is already on channel
[444] = "ERR_NOLOGIN", -- User not logged in
[445] = "ERR_SUMMONDISABLED", -- SUMMON has been disabled
[446] = "ERR_USERSDISABLED", -- USERS has been disabled
[451] = "ERR_NOTREGISTERED", -- You have not registered
[461] = "ERR_NEEDMOREPARAMS", -- Not enough parameters
[462] = "ERR_ALREADYREGISTERED", -- You may not reregister
[463] = "ERR_NOPERMFORHOST", -- Your host isn't among the privileged
[464] = "ERR_PASSWDMISMATCH", -- Password incorrect
[465] = "ERR_YOUREBANNEDCREEP", -- You are banned from this server
[466] = "ERR_YOUWILLBEBANNED",
[467] = "ERR_KEYSET", -- Channel key already set
[471] = "ERR_CHANNELISFULL", -- Cannot join channel (+l)
[472] = "ERR_UNKNOWNMODE", -- Unknown mode char
[473] = "ERR_INVITEONLYCHAN", -- Cannot join channel (+i)
[474] = "ERR_BANNEDFROMCHAN", -- Cannot join channel (+b)
[475] = "ERR_BADCHANNELKEY", -- Cannot join channel (+k)
[476] = "ERR_BADCHANMASK", -- Bad channel mask
[477] = "ERR_NOCHANMODES", -- Channel doesn't support modes
[478] = "ERR_BANLISTFULL", -- Channel list is full
[481] = "ERR_NOPRIVILEGES", -- Permission denied- You're not an IRC operator
[482] = "ERR_CHANOPRIVSNEEDED", -- You're not channel operator
[483] = "ERR_CANTKILLSERVER", -- You can't kill a server!
[484] = "ERR_RESTRICTED", -- Your connection is restricted!
[485] = "ERR_UNIQOPPRIVSNEEDED", -- You're not the original channel operator
[491] = "ERR_NOOPERHOST", -- No O-lines for your host
[501] = "ERR_UMODEUNKNOWNFLAG", -- Unknown MODE flag
[502] = "ERR_USERSDONTMATCH", -- Can't change mode for other users
-- }}}
-- unused {{{
[231] = "RPL_SERVICEINFO",
[232] = "RPL_ENDOFSERVICES",
[233] = "RPL_SERVICE",
[300] = "RPL_NONE",
[316] = "RPL_WHOISCHANOP",
[361] = "RPL_KILLDONE",
[362] = "RPL_CLOSING",
[363] = "RPL_CLOSEEND",
[373] = "RPL_INFOSTART",
[384] = "RPL_MYPORTIS",
[213] = "RPL_STATSCLINE",
[214] = "RPL_STATSNLINE",
[215] = "RPL_STATSILINE",
[216] = "RPL_STATSKLINE",
[217] = "RPL_STATSQLINE",
[218] = "RPL_STATSYLINE",
[240] = "RPL_STATSVLINE",
[241] = "RPL_STATSLLINE",
[244] = "RPL_STATSHLINE",
[246] = "RPL_STATSPING",
[247] = "RPL_STATSBLINE",
[250] = "RPL_STATSDLINE",
[492] = "ERR_NOSERVICEHOST",
-- }}}
-- guesses {{{
[333] = "RPL_TOPICDATE", -- date the topic was set, in seconds since the epoch
[505] = "ERR_NOTREGISTERED" -- freenode blocking privmsg from unreged users
-- }}}
}
-- }}}
-- chanmodes {{{
chanmodes = {
["@"] = "secret",
["*"] = "private",
["="] = "public"
}
-- }}}

View File

@ -1,115 +0,0 @@
---
-- Implementation of the CTCP protocol
-- initialization {{{
local base = _G
local table = require "table"
-- }}}
---
-- This module implements the various quoting and escaping requirements of the
-- CTCP protocol.
module "irc.ctcp"
-- internal functions {{{
-- _low_quote {{{
--
-- Applies low level quoting to a string (escaping characters which are illegal
-- to appear in an IRC packet).
-- @param ... Strings to quote together, space separated
-- @return Quoted string
function _low_quote(...)
local str = table.concat({...}, " ")
return str:gsub("[%z\n\r\020]", {["\000"] = "\0200",
["\n"] = "\020n",
["\r"] = "\020r",
["\020"] = "\020\020"})
end
-- }}}
-- _low_dequote {{{
--
-- Removes low level quoting done by low_quote.
-- @param str String with low level quoting applied to it
-- @return String with those quoting methods stripped off
function _low_dequote(str)
return str:gsub("\020(.?)", function(s)
if s == "0" then return "\000" end
if s == "n" then return "\n" end
if s == "r" then return "\r" end
if s == "\020" then return "\020" end
return ""
end)
end
-- }}}
-- _ctcp_quote {{{
--
-- Applies CTCP quoting to a block of text which has been identified as CTCP
-- data (by the calling program).
-- @param ... Strings to apply CTCP quoting to together, space separated
-- @return String with CTCP quoting applied
function _ctcp_quote(...)
local str = table.concat({...}, " ")
local ret = str:gsub("[\001\\]", {["\001"] = "\\a",
["\\"] = "\\\\"})
return "\001" .. ret .. "\001"
end
-- }}}
-- _ctcp_dequote {{{
--
-- Removes CTCP quoting from a block of text which has been identified as CTCP
-- data (likely by ctcp_split).
-- @param str String with CTCP quoting
-- @return String with all CTCP quoting stripped
function _ctcp_dequote(str)
local ret = str:gsub("^\001", ""):gsub("\001$", "")
return ret:gsub("\\(.?)", function(s)
if s == "a" then return "\001" end
if s == "\\" then return "\\" end
return ""
end)
end
-- }}}
-- _ctcp_split {{{
--
-- Splits a low level dequoted string into normal text and unquoted CTCP
-- messages.
-- @param str Low level dequoted string
-- @return Array of tables, with each entry in the array corresponding to one
-- part of the split message. These tables will have these fields:
-- <ul>
-- <li><i>str:</i> The text of the split section</li>
-- <li><i>ctcp:</i> True if the section was a CTCP message, false
-- otherwise</li>
-- </ul>
function _ctcp_split(str)
local ret = {}
local iter = 1
while true do
local s, e = str:find("\001.*\001", iter)
local plain_string, ctcp_string
if not s then
plain_string = str:sub(iter, -1)
else
plain_string = str:sub(iter, s - 1)
ctcp_string = str:sub(s, e)
end
if plain_string ~= "" then
table.insert(ret, {str = plain_string, ctcp = false})
end
if not s then break end
if ctcp_string ~= "" then
table.insert(ret, {str = _ctcp_dequote(ctcp_string), ctcp = true})
end
iter = e + 1
end
return ret
end
-- }}}
-- }}}

View File

@ -1,196 +0,0 @@
---
-- Implementation of the DCC protocol
-- initialization {{{
local base = _G
local irc = require 'irc'
local ctcp = require 'irc.ctcp'
local c = ctcp._ctcp_quote
local irc_debug = require 'irc.debug'
local misc = require 'irc.misc'
local socket = require 'socket'
local coroutine = require 'coroutine'
local io = require 'io'
local string = require 'string'
-- }}}
---
-- This module implements the DCC protocol. File transfers (DCC SEND) are
-- handled, but DCC CHAT is not, as of yet.
module 'irc.dcc'
-- defaults {{{
FIRST_PORT = 1028
LAST_PORT = 5000
-- }}}
-- private functions {{{
-- debug_dcc {{{
--
-- Prints a debug message about DCC events similar to irc.debug.warn, etc.
-- @param msg Debug message
local function debug_dcc(msg)
irc_debug._message("DCC", msg, "\027[0;32m")
end
-- }}}
-- send_file {{{
--
-- Sends a file to a remote user, after that user has accepted our DCC SEND
-- invitation
-- @param sock Socket to send the file on
-- @param file Lua file object corresponding to the file we want to send
-- @param packet_size Size of the packets to send the file in
local function send_file(sock, file, packet_size)
local bytes = 0
while true do
local packet = file:read(packet_size)
if not packet then break end
bytes = bytes + packet:len()
local index = 1
while true do
local skip = false
sock:send(packet, index)
local new_bytes, err = sock:receive(4)
if not new_bytes then
if err == "timeout" then
skip = true
else
irc_debug._warn(err)
break
end
else
new_bytes = misc._int_to_str(new_bytes)
end
if not skip then
if new_bytes ~= bytes then
index = packet_size - bytes + new_bytes + 1
else
break
end
end
end
coroutine.yield(true)
end
debug_dcc("File completely sent")
file:close()
sock:close()
irc._unregister_socket(sock, 'w')
return true
end
-- }}}
-- handle_connect {{{
--
-- Handle the connection attempt by a remote user to get our file. Basically
-- just swaps out the server socket we were listening on for a client socket
-- that we can send data on
-- @param ssock Server socket that the remote user connected to
-- @param file Lua file object corresponding to the file we want to send
-- @param packet_size Size of the packets to send the file in
local function handle_connect(ssock, file, packet_size)
debug_dcc("Offer accepted, beginning to send")
packet_size = packet_size or 1024
local sock = ssock:accept()
sock:settimeout(0.1)
ssock:close()
irc._unregister_socket(ssock, 'r')
irc._register_socket(sock, 'w',
coroutine.wrap(function(s)
return send_file(s, file, packet_size)
end))
return true
end
-- }}}
-- accept_file {{{
--
-- Accepts a file from a remote user which has offered it to us.
-- @param sock Socket to receive the file on
-- @param file Lua file object corresponding to the file we want to save
-- @param packet_size Size of the packets to receive the file in
local function accept_file(sock, file, packet_size)
local bytes = 0
while true do
local packet, err, partial_packet = sock:receive(packet_size)
if not packet and err == "timeout" then packet = partial_packet end
if not packet then break end
if packet:len() == 0 then break end
bytes = bytes + packet:len()
sock:send(misc._str_to_int(bytes))
file:write(packet)
coroutine.yield(true)
end
debug_dcc("File completely received")
file:close()
sock:close()
irc._unregister_socket(sock, 'r')
return true
end
-- }}}
-- }}}
-- internal functions {{{
-- _accept {{{
--
-- Accepts a file offer from a remote user. Called when the on_dcc callback
-- retuns true.
-- @param filename Name to save the file as
-- @param address IP address of the remote user in low level int form
-- @param port Port to connect to at the remote user
-- @param packet_size Size of the packets the remote user will be sending
function _accept(filename, address, port, packet_size)
debug_dcc("Accepting a DCC SEND request from " .. address .. ":" .. port)
packet_size = packet_size or 1024
local sock = base.assert(socket.tcp())
base.assert(sock:connect(address, port))
sock:settimeout(0.1)
local file = base.assert(io.open(misc._get_unique_filename(filename), "w"))
irc._register_socket(sock, 'r',
coroutine.wrap(function(s)
return accept_file(s, file, packet_size)
end))
end
-- }}}
-- }}}
-- public functions {{{
-- send {{{
---
-- Offers a file to a remote user.
-- @param nick User to offer the file to
-- @param filename Filename to offer
-- @param port Port to accept connections on (optional, defaults to
-- choosing an available port between FIRST_PORT and LAST_PORT
-- above)
function send(nick, filename, port)
port = port or FIRST_PORT
local sock
repeat
sock = base.assert(socket.tcp())
err, msg = sock:bind('*', port)
port = port + 1
until msg ~= "address already in use" and port <= LAST_PORT + 1
port = port - 1
base.assert(err, msg)
base.assert(sock:listen(1))
local ip = misc._ip_str_to_int(irc.get_ip())
local file, err = io.open(filename)
if not file then
irc_debug._warn(err)
sock:close()
return
end
local size = file:seek("end")
file:seek("set")
irc._register_socket(sock, 'r',
coroutine.wrap(function(s)
return handle_connect(s, file)
end))
filename = misc._basename(filename)
if filename:find(" ") then filename = '"' .. filename .. '"' end
debug_dcc("Offering " .. filename .. " to " .. nick .. " from " ..
irc.get_ip() .. ":" .. port)
irc.send("PRIVMSG", nick, c("DCC", "SEND", filename, ip, port, size))
end
-- }}}
-- }}}

View File

@ -1,92 +0,0 @@
---
-- Basic debug output
-- initialization {{{
local base = _G
local io = require 'io'
-- }}}
---
-- This module implements a few useful debug functions for use throughout the
-- rest of the code.
module 'irc.debug'
-- defaults {{{
COLOR = true
-- }}}
-- local variables {{{
local ON = false
local outfile = io.output()
-- }}}
-- internal functions {{{
-- _message {{{
--
-- Output a debug message.
-- @param msg_type Arbitrary string corresponding to the type of message
-- @param msg Message text
-- @param color Which terminal code to use for color output (defaults to
-- dark gray)
function _message(msg_type, msg, color)
if ON then
local endcolor = ""
if COLOR and outfile == io.stdout then
color = color or "\027[1;30m"
endcolor = "\027[0m"
else
color = ""
endcolor = ""
end
outfile:write(color .. msg_type .. ": " .. msg .. endcolor .. "\n")
end
end
-- }}}
-- _err {{{
--
-- Signal an error. Writes the error message to the screen in red and calls
-- error().
-- @param msg Error message
-- @see error
function _err(msg)
_message("ERR", msg, "\027[0;31m")
base.error(msg, 2)
end
-- }}}
-- _warn {{{
--
-- Signal a warning. Writes the warning message to the screen in yellow.
-- @param msg Warning message
function _warn(msg)
_message("WARN", msg, "\027[0;33m")
end
-- }}}
-- }}}
-- public functions {{{
-- enable {{{
---
-- Turns on debug output.
function enable()
ON = true
end
-- }}}
-- disable {{{
---
-- Turns off debug output.
function disable()
ON = false
end
-- }}}
-- set_output {{{
---
-- Redirects output to a file rather than stdout.
-- @param file File to write debug output to
function set_output(file)
outfile = base.assert(io.open(file))
end
-- }}}
-- }}}

View File

@ -1,69 +0,0 @@
---
-- Implementation of IRC server message parsing
-- initialization {{{
local base = _G
local constants = require 'irc.constants'
local ctcp = require 'irc.ctcp'
local irc_debug = require 'irc.debug'
local misc = require 'irc.misc'
local socket = require 'socket'
local string = require 'string'
local table = require 'table'
-- }}}
---
-- This module contains parsing functions for IRC server messages.
module 'irc.message'
-- internal functions {{{
-- _parse {{{
--
-- Parse a server command.
-- @param str Command to parse
-- @return Table containing the parsed message. It contains:
-- <ul>
-- <li><i>from:</i> The source of this message, in full usermask
-- form (nick!user@host) for messages originating
-- from users, and as a hostname for messages from
-- servers</li>
-- <li><i>command:</i> The command sent, in name form if possible,
-- otherwise as a numeric code</li>
-- <li><i>args:</i> Array of strings corresponding to the arguments
-- to the received command</li>
--
-- </ul>
function _parse(str)
-- low-level ctcp quoting {{{
str = ctcp._low_dequote(str)
-- }}}
-- parse the from field, if it exists (leading :) {{{
local from = ""
if str:sub(1, 1) == ":" then
local e
e, from = socket.skip(1, str:find("^:([^ ]*) "))
str = str:sub(e + 1)
end
-- }}}
-- get the command name or numerical reply value {{{
local command, argstr = socket.skip(2, str:find("^([^ ]*) ?(.*)"))
local reply = false
if command:find("^%d%d%d$") then
reply = true
if constants.replies[base.tonumber(command)] then
command = constants.replies[base.tonumber(command)]
else
irc_debug._warn("Unknown server reply: " .. command)
end
end
-- }}}
-- get the args {{{
local args = misc._split(argstr, " ", ":")
-- the first arg in a reply is always your nick
if reply then table.remove(args, 1) end
-- }}}
-- return the parsed message {{{
return {from = from, command = command, args = args}
-- }}}
end
-- }}}
-- }}}

View File

@ -1,303 +0,0 @@
---
-- Various useful functions that didn't fit anywhere else
-- initialization {{{
local base = _G
local irc_debug = require 'irc.debug'
local socket = require 'socket'
local math = require 'math'
local os = require 'os'
local string = require 'string'
local table = require 'table'
-- }}}
---
-- This module contains various useful functions which didn't fit in any of the
-- other modules.
module 'irc.misc'
-- defaults {{{
DELIM = ' '
PATH_SEP = '/'
ENDIANNESS = "big"
INT_BYTES = 4
-- }}}
-- private functions {{{
--
-- Check for existence of a file. This returns true if renaming a file to
-- itself succeeds. This isn't ideal (I think anyway) but it works here, and
-- lets me not have to bring in LFS as a dependency.
-- @param filename File to check for existence
-- @return True if the file exists, false otherwise
local function exists(filename)
local _, err = os.rename(filename, filename)
if not err then return true end
return not err:find("No such file or directory")
end
-- }}}
-- internal functions {{{
-- _split {{{
--
-- Splits str into substrings based on several options.
-- @param str String to split
-- @param delim String of characters to use as the beginning of substring
-- delimiter
-- @param end_delim String of characters to use as the end of substring
-- delimiter
-- @param lquotes String of characters to use as opening quotes (quoted strings
-- in str will be considered one substring)
-- @param rquotes String of characters to use as closing quotes
-- @return Array of strings, one for each substring that was separated out
function _split(str, delim, end_delim, lquotes, rquotes)
-- handle arguments {{{
delim = "["..(delim or DELIM).."]"
if end_delim then end_delim = "["..end_delim.."]" end
if lquotes then lquotes = "["..lquotes.."]" end
if rquotes then rquotes = "["..rquotes.."]" end
local optdelim = delim .. "?"
-- }}}
local ret = {}
local instring = false
while str:len() > 0 do
-- handle case for not currently in a string {{{
if not instring then
local end_delim_ind, lquote_ind, delim_ind
if end_delim then end_delim_ind = str:find(optdelim..end_delim) end
if lquotes then lquote_ind = str:find(optdelim..lquotes) end
local delim_ind = str:find(delim)
if not end_delim_ind then end_delim_ind = str:len() + 1 end
if not lquote_ind then lquote_ind = str:len() + 1 end
if not delim_ind then delim_ind = str:len() + 1 end
local next_ind = math.min(end_delim_ind, lquote_ind, delim_ind)
if next_ind == str:len() + 1 then
table.insert(ret, str)
break
elseif next_ind == end_delim_ind then
-- TODO: hackish here
if str:sub(next_ind, next_ind) == end_delim:gsub('[%[%]]', '') then
table.insert(ret, str:sub(next_ind + 1))
else
table.insert(ret, str:sub(1, next_ind - 1))
table.insert(ret, str:sub(next_ind + 2))
end
break
elseif next_ind == lquote_ind then
table.insert(ret, str:sub(1, next_ind - 1))
str = str:sub(next_ind + 2)
instring = true
else -- last because the top two contain it
table.insert(ret, str:sub(1, next_ind - 1))
str = str:sub(next_ind + 1)
end
-- }}}
-- handle case for currently in a string {{{
else
local endstr = str:find(rquotes..optdelim)
table.insert(ret, str:sub(1, endstr - 1))
str = str:sub(endstr + 2)
instring = false
end
-- }}}
end
return ret
end
-- }}}
-- _basename {{{
--
-- Returns the basename of a file (the part after the last directory separator).
-- @param path Path to the file
-- @param sep Directory separator (optional, defaults to PATH_SEP)
-- @return The basename of the file
function _basename(path, sep)
sep = sep or PATH_SEP
if not path:find(sep) then return path end
return socket.skip(2, path:find(".*" .. sep .. "(.*)"))
end
-- }}}
-- _dirname {{{
--
-- Returns the dirname of a file (the part before the last directory separator).
-- @param path Path to the file
-- @param sep Directory separator (optional, defaults to PATH_SEP)
-- @return The dirname of the file
function _dirname(path, sep)
sep = sep or PATH_SEP
if not path:find(sep) then return "." end
return socket.skip(2, path:find("(.*)" .. sep .. ".*"))
end
-- }}}
-- _str_to_int {{{
--
-- Converts a number to a low-level int.
-- @param str String representation of the int
-- @param bytes Number of bytes in an int (defaults to INT_BYTES)
-- @param endian Which endianness to use (big, little, host, network) (defaultsi
-- to ENDIANNESS)
-- @return A string whose first INT_BYTES characters make a low-level int
function _str_to_int(str, bytes, endian)
bytes = bytes or INT_BYTES
endian = endian or ENDIANNESS
local ret = ""
for i = 0, bytes - 1 do
local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256))
if endian == "big" or endian == "network" then ret = new_byte .. ret
else ret = ret .. new_byte
end
end
return ret
end
-- }}}
-- _int_to_str {{{
--
-- Converts a low-level int to a number.
-- @param int String whose bytes correspond to the bytes of a low-level int
-- @param endian Endianness of the int argument (defaults to ENDIANNESS)
-- @return String representation of the low-level int argument
function _int_to_str(int, endian)
endian = endian or ENDIANNESS
local ret = 0
for i = 1, int:len() do
if endian == "big" or endian == "network" then ind = int:len() - i + 1
else ind = i
end
ret = ret + string.byte(int:sub(ind, ind)) * 2^(8 * (i - 1))
end
return ret
end
-- }}}
-- _ip_str_to_int {{{
-- TODO: handle endianness here
--
-- Converts a string IP address to a low-level int.
-- @param ip_str String representation of an IP address
-- @return Low-level int representation of that IP address
function _ip_str_to_int(ip_str)
local i = 3
local ret = 0
for num in ip_str:gmatch("%d+") do
ret = ret + num * 2^(i * 8)
i = i - 1
end
return ret
end
-- }}}
-- _ip_int_to_str {{{
-- TODO: handle endianness here
--
-- Converts an int to a string IP address.
-- @param ip_int Low-level int representation of an IP address
-- @return String representation of that IP address
function _ip_int_to_str(ip_int)
local ip = {}
for i = 3, 0, -1 do
local new_num = math.floor(ip_int / 2^(i * 8))
table.insert(ip, new_num)
ip_int = ip_int - new_num * 2^(i * 8)
end
return table.concat(ip, ".")
end
-- }}}
-- _get_unique_filename {{{
--
-- Returns a unique filename.
-- @param filename Filename to start with
-- @return Filename (same as the one we started with, except possibly with some
-- numbers appended) which does not currently exist on the filesystem
function _get_unique_filename(filename)
if not exists(filename) then return filename end
local count = 1
while true do
if not exists(filename .. "." .. count) then
return filename .. "." .. count
end
count = count + 1
end
end
-- }}}
-- _try_call {{{
--
-- Call a function, if it exists.
-- @param fn Function to try to call
-- @param ... Arguments to fn
-- @return The return values of fn, if it was successfully called
function _try_call(fn, ...)
if base.type(fn) == "function" then
return fn(...)
end
end
-- }}}
-- _try_call_warn {{{
--
-- Same as try_call, but complain if the function doesn't exist.
-- @param msg Warning message to use if the function doesn't exist
-- @param fn Function to try to call
-- @param ... Arguments to fn
-- @return The return values of fn, if it was successfully called
function _try_call_warn(msg, fn, ...)
if base.type(fn) == "function" then
return fn(...)
else
irc_debug._warn(msg)
end
end
-- }}}
-- _value_iter {{{
--
-- Iterator to iterate over just the values of a table.
function _value_iter(state, arg, pred)
for k, v in base.pairs(state) do
if arg == v then arg = k end
end
local key, val = base.next(state, arg)
if not key then return end
if base.type(pred) == "function" then
while not pred(val) do
key, val = base.next(state, key)
if not key then return end
end
end
return val
end
-- }}}
-- _parse_user {{{
--
-- Gets the various parts of a full username.
-- @param user A usermask (i.e. returned in the from field of a callback)
-- @return nick
-- @return username (if it exists)
-- @return hostname (if it exists)
function _parse_user(user)
local found, bang, nick = user:find("^([^!]*)!")
if found then
user = user:sub(bang + 1)
else
return user
end
local found, equals = user:find("^.=")
if found then
user = user:sub(3)
end
local found, at, username = user:find("^([^@]*)@")
if found then
return nick, username, user:sub(at + 1)
else
return nick, user
end
end
-- }}}
-- }}}

20
src/luasocket/LICENSE.txt Normal file
View File

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

40
src/messages.lua Normal file
View File

@ -0,0 +1,40 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
if not mt_irc.sendLocal then
function mt_irc:sendLocal(message)
minetest.chat_send_all(message)
end
end
function mt_irc:queueMsg(message)
table.insert(self.message_buffer, message)
end
function mt_irc:sendMsg(message)
self.conn:send(message)
end
mt_irc.msgs = {}
function mt_irc.msgs.privmsg(to, message)
return ("PRIVMSG %s :%s"):format(to, message)
end
function mt_irc.msgs.notice(to, message)
return ("NOTICE %s :%s"):format(to, message)
end
function mt_irc.msgs.action(to, message)
return ("PRIVMSG %s :%cACTION %s%c")
:format(to, string.char(1), message, string.char(1))
end
function mt_irc.msgs.playerMessage(to, name, message)
local t = {name=name, message=message}
local text = mt_irc.config.format_out:expandvars(t)
return mt_irc.msgs.privmsg(to, text)
end
-- TODO Add more message types
--

82
src/player_part.lua Normal file
View File

@ -0,0 +1,82 @@
-- This file is licensed under the terms of the BSD 2-clause license.
-- See LICENSE.txt for details.
function mt_irc:player_part(name)
if not self.joined_players[name] then
minetest.chat_send_player(name, "IRC: You are not in the channel.")
return
end
self.joined_players[name] = nil
minetest.chat_send_player(name, "IRC: You are now out of the channel.")
end
function mt_irc:player_join(name)
if self.joined_players[name] then
minetest.chat_send_player(name, "IRC: You are already in the channel.")
return
end
self.joined_players[name] = true
minetest.chat_send_player(name, "IRC: You are now in the channel.")
end
minetest.register_chatcommand("join", {
description = "Join the IRC channel",
privs = {shout=true},
func = function(name, param)
mt_irc:player_join(name)
end
})
minetest.register_chatcommand("part", {
description = "Part the IRC channel",
privs = {shout=true},
func = function(name, param)
mt_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(mt_irc.joined_players) do
s = s..", "..name
end
minetest.chat_send_player(name, "Players On Channel:"..s)
end
})
mt_irc:register_bot_command("who", {
description = "Tell who is playing",
func = function(user, args)
local s = ""
for name, _ in pairs(mt_irc.joined_players) do
s = s.." "..name
end
mt_irc:say(user.nick, "Players On Channel:"..s)
end
})
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
mt_irc.joined_players[name] = mt_irc.config.auto_join
end)
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
mt_irc.joined_players[name] = nil
end)
function mt_irc:sendLocal(message)
for name, _ in pairs(self.joined_players) do
minetest.chat_send_player(name, message, false)
end
end

20
src/util.lua Normal file
View File

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