diff options
author | jluehrs2 <jluehrs2@uiuc.edu> | 2007-08-26 22:07:38 -0500 |
---|---|---|
committer | jluehrs2 <jluehrs2@uiuc.edu> | 2007-08-26 22:07:38 -0500 |
commit | 86a0d68f9920cbcd382d8bb255639b022991def7 (patch) | |
tree | d9ecb2ec46ababa1654818d06d3015f5da746162 /src/irc | |
parent | 27ea3f3876546997cfc838f6d605b8f4ca5adcd7 (diff) | |
download | luairc-86a0d68f9920cbcd382d8bb255639b022991def7.tar.xz |
add all of the current files
Diffstat (limited to 'src/irc')
-rw-r--r-- | src/irc/channel.lua | 331 | ||||
-rw-r--r-- | src/irc/constants.lua | 189 | ||||
-rw-r--r-- | src/irc/ctcp.lua | 93 | ||||
-rw-r--r-- | src/irc/dcc.lua | 122 | ||||
-rw-r--r-- | src/irc/debug.lua | 64 | ||||
-rw-r--r-- | src/irc/message.lua | 50 | ||||
-rw-r--r-- | src/irc/misc.lua | 227 |
7 files changed, 1076 insertions, 0 deletions
diff --git a/src/irc/channel.lua b/src/irc/channel.lua new file mode 100644 index 0000000..4e77ae9 --- /dev/null +++ b/src/irc/channel.lua @@ -0,0 +1,331 @@ +-- initialization {{{ +local base = _G +local irc = require 'irc' +local misc = require 'irc.misc' +local socket = require 'socket' +local table = require 'table' +-- }}} + +module 'irc.channel' + +-- object metatable {{{ +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 {{{ +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 +-- }}} +-- }}} + +-- constructor {{{ +function new(chan) + return base.setmetatable({_name = chan, _topic = {}, _chanmode = "", + _members = {}}, mt) +end +-- }}} + +-- public methods {{{ +-- iterators {{{ +-- each_op() {{{ +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() {{{ +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() {{{ +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() {{{ +function each_member(self) + return misc.value_iter, self._members, nil +end +-- }}} +-- }}} + +-- return tables of users {{{ +-- ops() {{{ +function ops(self) + local ret = {} + for nick in self:each_op() do + table.insert(ret, nick) + end + return ret +end +-- }}} + +-- voices() {{{ +function voices(self) + local ret = {} + for nick in self:each_voice() do + table.insert(ret, nick) + end + return ret +end +-- }}} + +-- users() {{{ +function users(self) + local ret = {} + for nick in self:each_user() do + table.insert(ret, nick) + end + return ret +end +-- }}} + +-- members() {{{ +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() - ban a user from a channel {{{ +-- TODO: hmmm, this probably needs an appropriate mask, rather than a nick +function ban(self, name) + irc.send("MODE", self.name, "+b", name) +end +-- }}} + +-- unban() - remove a ban on a user {{{ +-- TODO: same here +function unban(self, name) + irc.send("MODE", self.name, "-b", name) +end +-- }}} + +-- voice() - give a user voice on a channel {{{ +function voice(self, name) + irc.send("MODE", self.name, "+v", name) +end +-- }}} + +-- devoice() - remove voice from a user {{{ +function devoice(self, name) + irc.send("MODE", self.name, "-v", name) +end +-- }}} + +-- op() - give a user ops on a channel {{{ +function op(self, name) + irc.send("MODE", self.name, "+o", name) +end +-- }}} + +-- deop() - remove ops from a user {{{ +function deop(self, name) + irc.send("MODE", self.name, "-o", name) +end +-- }}} + +-- set_limit() - set a channel limit {{{ +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 {{{ +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 {{{ +function set_private(self, set) + set_basic_mode(self, set, "p") +end +-- }}} + +-- set_secret() - set the secret state of a channel {{{ +function set_secret(self, set) + set_basic_mode(self, set, "s") +end +-- }}} + +-- set_invite_only() - set whether joining the channel requires an invite {{{ +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 {{{ +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 {{{ +function set_no_outside_messages(self, set) + set_basic_mode(self, set, "n") +end +-- }}} + +-- set moderated() - set whether voice is required to speak {{{ +function set_moderated(self, set) + set_basic_mode(self, set, "m") +end +-- }}} +-- }}} + +-- accessors {{{ +-- add_user() {{{ +function add_user(self, user, mode) + mode = mode or '' + self._members[user] = mode .. user +end +-- }}} + +-- remove_user() {{{ +function remove_user(self, user) + self._members[user] = nil +end +-- }}} + +-- change_status() {{{ +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 +-- }}} + +-- contains() {{{ +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 +-- }}} + +-- change_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 +-- }}} +-- }}} +-- }}} diff --git a/src/irc/constants.lua b/src/irc/constants.lua new file mode 100644 index 0000000..864e4c9 --- /dev/null +++ b/src/irc/constants.lua @@ -0,0 +1,189 @@ +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" +} +-- }}} diff --git a/src/irc/ctcp.lua b/src/irc/ctcp.lua new file mode 100644 index 0000000..6a1877c --- /dev/null +++ b/src/irc/ctcp.lua @@ -0,0 +1,93 @@ +-- initialization {{{ +local base = _G +local table = require "table" +-- }}} + +module "irc.ctcp" + +-- public functions {{{ +-- low_quote {{{ +-- applies low level quoting to a string (escaping characters which +-- are illegal to appear in an irc packet) +function low_quote(str) + 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 +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) +function ctcp_quote(str) + 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) +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 {{{ +-- takes in a mid_level (low level dequoted) string and splits it +-- up into normal text and ctcp messages. it returns an array, where string +-- values correspond to plain text and table values have t[1] as the ctcp +-- message. if dequote is true, each ctcp message will also be ctcp dequoted. +function ctcp_split(str, dequote) + 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, plain_string) + end + if not s then break end + if ctcp_string ~= "" then + if dequote then + table.insert(ret, {ctcp_dequote(ctcp_string)}) + else + table.insert(ret, {ctcp_string}) + end + end + + iter = e + 1 + end + + return ret +end +-- }}} +-- }}} diff --git a/src/irc/dcc.lua b/src/irc/dcc.lua new file mode 100644 index 0000000..f227d4b --- /dev/null +++ b/src/irc/dcc.lua @@ -0,0 +1,122 @@ +-- initialization {{{ +local base = _G +local irc = require 'irc' +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' +-- }}} + +module 'irc.dcc' + +-- defaults {{{ +FIRST_PORT = 1028 +LAST_PORT = 5000 +-- }}} + +-- private functions {{{ +-- send_file {{{ +local function send_file(sock, file, size, 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 + sock:send(packet, index) + local new_bytes = misc.int_to_str(sock:receive(4)) + if new_bytes ~= bytes then + index = packet_size - bytes + new_bytes + 1 + else + break + end + end + if bytes >= size then break end + coroutine.yield(true) + end + file:close() + sock:close() + irc._unregister_socket(sock, 'w') + return true +end +-- }}} + +-- handle_connect {{{ +local function handle_connect(ssock, file, size, packet_size) + 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(sock) + return send_file(sock, file, size, packet_size) + end)) + return true +end +-- }}} + +-- accept_file {{{ +local function accept_file(sock, file, size, 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 + file:close() + sock:close() + irc._unregister_socket(sock, 'r') + return true +end +-- }}} +-- }}} + +-- public functions {{{ +-- send {{{ +function send(nick, filename, port) + port = port or FIRST_PORT + local sock = base.assert(socket.tcp()) + repeat + err, msg = sock:bind('*', port) + port = port + 1 + until msg ~= "address already in use" and port <= LAST_PORT + 1 + base.assert(err, msg) + base.assert(sock:listen(1)) + local ip = misc.ip_str_to_int(irc.get_ip()) + local file = base.assert(io.open(filename)) + local size = file:seek("end") + file:seek("set") + irc._register_socket(sock, 'r', + coroutine.wrap(function(sock) + return handle_connect(sock, file, size) + end)) + filename = misc.basename(filename) + if filename:find(" ") then filename = '"' .. filename .. '"' end + irc.send("PRIVMSG", nick, {"DCC SEND " .. filename .. " " .. + ip .. " " .. port - 1 .. " " .. size}) +end +-- }}} + +-- accept {{{ +function accept(filename, address, port, size, packet_size) + packet_size = packet_size or 1024 + local sock = base.assert(socket.tcp()) + base.assert(sock:connect(misc.ip_int_to_str(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(sock) + return accept_file(sock, file, size, packet_size) + end)) +end +-- }}} +-- }}} diff --git a/src/irc/debug.lua b/src/irc/debug.lua new file mode 100644 index 0000000..2e03d74 --- /dev/null +++ b/src/irc/debug.lua @@ -0,0 +1,64 @@ +-- initialization {{{ +local base = _G +local io = require 'io' +-- }}} + +module 'irc.debug' + +-- defaults {{{ +COLOR = true +-- }}} + +-- local variables {{{ +local ON = false +local outfile = io.output() +-- }}} + +-- public functions {{{ +-- enable {{{ +function enable() + ON = true +end +-- }}} + +-- disable {{{ +function disable() + ON = false +end +-- }}} + +-- set_output {{{ +function set_output(file) + outfile = base.assert(io.open(file)) +end +-- }}} + +-- message {{{ +function message(msg_type, msg, color) + if ON then + local endcolor = "" + if COLOR 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 {{{ +function err(msg) + message("ERR", msg, "\027[0;31m") + base.error(msg, 2) +end +-- }}} + +-- warn {{{ +function warn(msg) + message("WARN", msg, "\027[0;33m") +end +-- }}} +-- }}} diff --git a/src/irc/message.lua b/src/irc/message.lua new file mode 100644 index 0000000..27698d8 --- /dev/null +++ b/src/irc/message.lua @@ -0,0 +1,50 @@ +-- 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' +-- }}} + +module 'irc.message' + +-- local functions {{{ +-- parse() - parse a server command {{{ +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 +-- }}} +-- }}} diff --git a/src/irc/misc.lua b/src/irc/misc.lua new file mode 100644 index 0000000..7f77eea --- /dev/null +++ b/src/irc/misc.lua @@ -0,0 +1,227 @@ +-- 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' +-- }}} + +module 'irc.misc' + +-- defaults {{{ +DELIM = ' ' +PATH_SEP = '/' +ENDIANNESS = "big" +INT_BYTES = 4 +-- }}} + +-- private functions {{{ +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 +-- }}} + +-- public functions {{{ +-- split() - splits str into substrings based on several options {{{ +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 {{{ +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 {{{ +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 {{{ +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 {{{ +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() - converts a string ip address to an int {{{ +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() - converts an int to a string 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 {{{ +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 {{{ +function try_call(fn, ...) + if base.type(fn) == "function" then + return fn(...) + end +end +-- }}} + +-- try_call_warn() - same as try_call, but complain if not {{{ +function try_call_warn(msg, fn, ...) + if base.type(fn) == "function" then + return fn(...) + else + irc_debug.warn(msg) + end +end +-- }}} + +-- parse_user() - gets the various parts of a full username {{{ +-- args: user - usermask (i.e. returned in the from field of a callback) +-- return: nick, username, hostname (these can be nil if nonexistant) +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 +-- }}} + +-- value_iter() - iterate just over 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 +-- }}} +-- }}} |