mirror of
https://github.com/minetest-mods/irc.git
synced 2025-07-01 07:30:30 +02:00
Rewrite
This commit is contained in:
87
src/API.txt
Normal file
87
src/API.txt
Normal 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
22
src/LICENSE.txt
Normal 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
1
src/LuaIRC
Submodule
Submodule src/LuaIRC added at 8a2e47a326
161
src/botcmds.lua
161
src/botcmds.lua
@ -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
|
||||
})
|
||||
|
||||
|
255
src/callback.lua
255
src/callback.lua
@ -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)
|
||||
|
||||
|
214
src/chatcmds.lua
214
src/chatcmds.lua
@ -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
|
||||
|
||||
|
101
src/config.lua
101
src/config.lua
@ -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
|
||||
|
||||
|
@ -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
211
src/hooks.lua
Normal 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)
|
||||
|
221
src/init.lua
221
src/init.lua
@ -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
|
||||
|
||||
|
1023
src/luairc/irc.lua
1023
src/luairc/irc.lua
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
-- }}}
|
||||
-- }}}
|
||||
-- }}}
|
@ -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"
|
||||
}
|
||||
-- }}}
|
@ -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
|
||||
-- }}}
|
||||
-- }}}
|
@ -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
|
||||
-- }}}
|
||||
-- }}}
|
@ -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
|
||||
-- }}}
|
||||
-- }}}
|
@ -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
|
||||
-- }}}
|
||||
-- }}}
|
@ -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
20
src/luasocket/LICENSE.txt
Normal 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
40
src/messages.lua
Normal 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
82
src/player_part.lua
Normal 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
20
src/util.lua
Normal 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
|
Reference in New Issue
Block a user