From: Chris Porter Date: Sun, 28 Mar 2010 00:08:28 +0000 (+0000) Subject: LUA: Add lua stdlib. X-Git-Url: https://jfr.im/git/irc/quakenet/newserv.git/commitdiff_plain/a098bb43e4ea0d6a8794916e5842bbad2b0cb0ac LUA: Add lua stdlib. --- diff --git a/lua/lib/bootstrap.lua b/lua/lib/bootstrap.lua new file mode 100644 index 00000000..6555c969 --- /dev/null +++ b/lua/lib/bootstrap.lua @@ -0,0 +1,32 @@ +require("lib/quakenet") +require("lib/missing") +require("lib/schedule") +require("lib/serialise") +require("lib/country") +require("lib/db") +require("lib/socket") +require("lib/json") +require("lib/class") + +local LASTERROR = 0 +local NOTREPORTING = false + +function scripterror(err) + local cerror = os.time() + if cerror > LASTERROR then + LASTERROR = cerror + NOTREPORTING = false + end + + if not NOTREPORTING then + chanmsgn(traceback(err, 2)) + + LASTERROR = LASTERROR + 1 + if LASTERROR > cerror + 10 then + NOTREPORTING = true + chanmsg("Too many errors for this script -- no longer reporting") + end + end +end + +math.randomseed(os.time()) diff --git a/lua/lib/class.lua b/lua/lib/class.lua new file mode 100644 index 00000000..6ec23cf5 --- /dev/null +++ b/lua/lib/class.lua @@ -0,0 +1,46 @@ +-- class.lua +-- Compatible with Lua 5.1 (not 5.0). +function class(base,ctor) + local c = {} -- a new class instance + if not ctor and type(base) == 'function' then + ctor = base + base = nil + elseif type(base) == 'table' then + -- our new class is a shallow copy of the base class! + for i,v in pairs(base) do + c[i] = v + end + c._base = base + end + -- the class will be the metatable for all its objects, + -- and they will look up their methods in it. + c.__index = c + + -- expose a ctor which can be called by () + local mt = {} + mt.__call = function(class_tbl,...) + local obj = {} + setmetatable(obj,c) + if ctor then + ctor(obj,...) + else + -- make sure that any stuff from the base class is initialized! + if base and base.init then + base.init(obj,...) + end + end + return obj + end + c.init = ctor + c.is_a = function(self,klass) + local m = getmetatable(self) + while m do + if m == klass then return true end + m = m._base + end + return false + end + setmetatable(c,mt) + return c +end + diff --git a/lua/lib/country.lua b/lua/lib/country.lua new file mode 100644 index 00000000..50f65207 --- /dev/null +++ b/lua/lib/country.lua @@ -0,0 +1,33 @@ +local countries = { "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TP","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","YU","ZA","ZM","ZR","ZW","A1","A2","O1" } +local countryhash + +function countrylookup(id) + return countries[id + 1] +end + +function countrynamelookup(country) + if not countryhash then + local i = 0 + countryhash = {} + + for k, v in pairs(countries) do + countryhash[v] = k - 1 + end + end + + return countryhash[country] +end + +function countryindex(country) + local i = 1 + + for v in pairs(countries) do + if countries[i] == country then + return i - 1 + end + i = i + 1 + end + + return -1 +end + diff --git a/lua/lib/db.lua b/lua/lib/db.lua new file mode 100644 index 00000000..6bcf90e6 --- /dev/null +++ b/lua/lib/db.lua @@ -0,0 +1,32 @@ +function db_queryiter() + local c = db_numrows() + local i = -1 + local f = db_numfields() + local gb = db_getvalue + + return function() + i = i + 1 + if i == c then + return nil + end + + local t = {} + for j=0,f do + table.insert(t, gb(i, j)) + end + + return t + end +end + +local realquery = db_query + +function db_query(x, fn, t) + if not fn then + realquery(x) + elseif not t then + realquery(x, fn, nil) + else + realquery(x, fn, t) + end +end diff --git a/lua/lib/jit.lua b/lua/lib/jit.lua new file mode 100644 index 00000000..d81d74bb --- /dev/null +++ b/lua/lib/jit.lua @@ -0,0 +1,2 @@ +--require("lib/jit.opt").start() + diff --git a/lua/lib/json.lua b/lua/lib/json.lua new file mode 100644 index 00000000..1af8e1df --- /dev/null +++ b/lua/lib/json.lua @@ -0,0 +1,377 @@ +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://json.luaforge.net/ +-- Version: 0.9.20 +-- This module is released under the The GNU General Public License (GPL). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Imports and dependencies +----------------------------------------------------------------------------- +local math = require('math') +local string = require("string") +local table = require("table") + +local base = _G + +----------------------------------------------------------------------------- +-- Module declaration +----------------------------------------------------------------------------- +module("json") + +-- Public functions + +-- Private functions +local decode_scanArray +local decode_scanComment +local decode_scanConstant +local decode_scanNumber +local decode_scanObject +local decode_scanString +local decode_scanWhitespace +local encodeString +local isArray +local isEncodable + +----------------------------------------------------------------------------- +-- PUBLIC FUNCTIONS +----------------------------------------------------------------------------- +--- Encodes an arbitrary Lua object / variable. +-- @param v The Lua object / variable to be JSON encoded. +-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) +function encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = base.type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return base.tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, encode(v[i])) + end + else -- An object, not an array + for i,j in base.pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==null then + return 'null' + end + + base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) +end + + +--- Decodes a JSON string and returns the decoded value as a Lua data structure / value. +-- @param s The string to scan. +-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. +-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, +-- and the position of the first character after +-- the scanned JSON object. +function decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) +end + +--- The null function allows one to specify a null value in an associative array (which is otherwise +-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } +function null() + return null -- so json.null() will also return null ;-) +end +----------------------------------------------------------------------------- +-- Internal, PRIVATE functions. +-- Following a Python-like convention, I have prefixed all these 'PRIVATE' +-- functions with an underscore. +----------------------------------------------------------------------------- + +--- Scans an array from JSON into a Lua object +-- startPos begins at the start of the array. +-- Returns the array and the next starting position +-- @param s The string being scanned. +-- @param startPos The starting position for the scan. +-- @return table, int The scanned array as a table, and the position of the next character to scan. +function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + repeat + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + object, startPos = decode(s,startPos) + table.insert(array,object) + until false +end + +--- Scans a comment and discards the comment. +-- Returns the position of the next character following the comment. +-- @param string s The JSON string to scan. +-- @param int startPos The starting position of the comment +function decode_scanComment(s, startPos) + base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 +end + +--- Scans for given constants: true, false or null +-- Returns the appropriate Lua type, and the position of the next character to read. +-- @param s The string being scanned. +-- @param startPos The position in the string at which to start scanning. +-- @return object, int The object (true, false or nil) and the position at which the next character should be +-- scanned. +function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in base.pairs(constNames) do + --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) +end + +--- Scans a number from the JSON encoded string. +-- (in fact, also is able to scan numeric +- eqns, which is not +-- in the JSON spec.) +-- Returns the number, and the position of the next character +-- after the number. +-- @param s The string being scanned. +-- @param startPos The position at which to start scanning. +-- @return number, int The extracted number and the position of the next character to scan. +function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) + local stringEval = base.loadstring(stringValue) + base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + return stringEval(), endPos +end + +--- Scans a JSON object into a Lua object. +-- startPos begins at the start of the object. +-- Returns the object and the next starting position. +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return table, int The scanned object as a table and the position of the next character to scan. +function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = decode(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found +end + +--- Scans a JSON string from the opening inverted comma or single quote to the +-- end of the string. +-- Returns the string extracted as a Lua string, +-- and the position of the next non-string character +-- (after the closing inverted comma or single quote). +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return string, int The extracted string as a Lua string, and the next character to parse. +function decode_scanString(s,startPos) + base.assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') + local escaped = false + local endPos = startPos + 1 + local bEnded = false + local stringLen = string.len(s) + repeat + local curChar = string.sub(s,endPos,endPos) + if not escaped then + if curChar==[[\]] then + escaped = true + else + bEnded = curChar==startChar + end + else + -- If we're escaped, we accept the current character come what may + escaped = false + end + endPos = endPos + 1 + base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) + until bEnded + local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) + local stringEval = base.loadstring(stringValue) + base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) + return stringEval(), endPos +end + +--- Scans a JSON string skipping all whitespace from the current start position. +-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. +-- @param s The string being scanned +-- @param startPos The starting position where we should begin removing whitespace. +-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string +-- was reached. +function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos +end + +--- Encodes a string to be JSON-compatible. +-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) +-- @param s The string to return as a JSON encoded (i.e. backquoted string) +-- @return The string appropriately escaped. +function encodeString(s) + s = string.gsub(s,'\\','\\\\') + s = string.gsub(s,'"','\\"') +-- slug +-- s = string.gsub(s,"'","\\'") + s = string.gsub(s,'\n','\\n') + s = string.gsub(s,'\t','\\t') + return s +end + +-- Determines whether the given Lua type is an array or a table / dictionary. +-- We consider any table an array if it has indexes 1..n for its n items, and no +-- other data in the table. +-- I think this method is currently a little 'flaky', but can't think of a good way around it yet... +-- @param t The table to evaluate as an array +-- @return boolean, number True if the table can be represented as an array, false otherwise. If true, +-- the second returned value is the maximum +-- number of indexed elements in the array. +function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + local maxIndex = 0 + for k,v in base.pairs(t) do + if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex +end + +--- Determines whether the given Lua object / table / variable can be JSON encoded. The only +-- types that are JSON encodable are: string, boolean, number, nil, table and json.null. +-- In this implementation, all other types are ignored. +-- @param o The object to examine. +-- @return boolean True if the object should be JSON encoded, false if it should be ignored. +function isEncodable(o) + local t = base.type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) +end + diff --git a/lua/lib/minute_count.lua b/lua/lib/minute_count.lua new file mode 100644 index 00000000..53d1d8d2 --- /dev/null +++ b/lua/lib/minute_count.lua @@ -0,0 +1,24 @@ +MinuteCount = class(function(obj, minutes) + obj.minutes = minutes + obj.data = {} + obj.current = 0 +end) + +function MinuteCount:add() + self.current = self.current + 1 +end + +function MinuteCount:moveon() + table.insert(self.data, 0, self.current) + table.remove(self.data, self.minutes) + self.current = 0 +end + +function MinuteCount:sum() + local sum = self.current + for _, v in ipairs(self.data) do + sum = sum + v + end + + return sum +end diff --git a/lua/lib/missing.lua b/lua/lib/missing.lua new file mode 100644 index 00000000..2fde7295 --- /dev/null +++ b/lua/lib/missing.lua @@ -0,0 +1,84 @@ +function tablelen(tab) + local count = 0 + + for _, _ in pairs(tab) do + count = count + 1 + end + + return count +end + +function tablesequal(a, b) + local k, v + + for k, v in pairs(a) do + if not b[k] or b[k] ~= v then + return false + end + end + + for k, v in pairs(b) do + if not a[k] or a[k] ~= v then + return false + end + end + + return true +end + +function trim(s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +function explode(d,p,m) + t={} + ll=0 + f=0 + if m and m < 1 then + return { p } + end + + while true do + l=string.find(p,d,ll+1,true) -- find the next d in the string + if l~=nil then -- if "not not" found then.. + table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array. + ll=l+1 -- save just after where we found it for searching next time. + f = f + 1 + if m and f >= m then + table.insert(t, string.sub(p,ll)) + break + end + else + table.insert(t, string.sub(p,ll)) -- Save what's left in our array. + break -- Break at end, as it should be, according to the lua manual. + end + end + return t +end + +function tableempty(table) + for k, v in pairs(table) do + return false + end + + return true +end + +function urlencode(str) + if (str) then + str = string.gsub (str, "\n", "\r\n") + str = string.gsub (str, "([^%w ])", + function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub (str, " ", "+") + end + return str +end + +function matchtoregex(match) + local x = match:gsub("([%^%$%(%)%%%.%[%]%+%-])", "%%%1") + x = x:gsub("%*", ".*") + x = x:gsub("%?", ".") + x = "^" .. x .. "$" + return x +end + diff --git a/lua/lib/quakenet.lua b/lua/lib/quakenet.lua new file mode 100644 index 00000000..f460c7eb --- /dev/null +++ b/lua/lib/quakenet.lua @@ -0,0 +1,51 @@ +require("lib/quakenet/access") +require("lib/quakenet/iterators") +require("lib/quakenet/legacy") +require("lib/quakenet/achievements") + +function chanmsg(a) + irc_smsg(a, "#qnet.keepout") +end + +function crapchanmsg(a) + irc_smsg(a, "#qnet.trj") +end + +function statusmsg(a) + irc_smsg("dd," .. a, "#qnet.keepout") +end + +function chanmsgn(t) + string.gsub(t, "[^\r\n]+", chanmsg) +end + +function noticen(n, t) + string.gsub(t, "[^\r\n]+", function(s) irc_notice(n, s) end); +end + +local irc_localrealovmode = irc_localovmode; + +function irc_localovmode(source, chan, ...) + if type(...) == 'table' then + irc_localrealovmode(source, chan, ...) + else + irc_localrealovmode(source, chan, { ... }) + end +end + +function irc_localaction(n, c, m) + irc_localchanmsg(n, c, string.char(1) .. "ACTION " .. m .. string.char(1)) +end + +function irctolower(l) + l = l:lower() + l = l:gsub("%[", "{") + l = l:gsub("%]", "}") + l = l:gsub("\\", "|") + l = l:gsub("%^", "~") + return l +end + +function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function) + return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function) +end diff --git a/lua/lib/quakenet/access.lua b/lua/lib/quakenet/access.lua new file mode 100644 index 00000000..9ead23bf --- /dev/null +++ b/lua/lib/quakenet/access.lua @@ -0,0 +1,9 @@ +function onstaff(nick) + return irc_nickonchan(nick, "#qnet.staff") +end + +function ontlz(nick) + return irc_nickonchan(nick, "#twilightzone") +end + + diff --git a/lua/lib/quakenet/achievements.lua b/lua/lib/quakenet/achievements.lua new file mode 100644 index 00000000..04f7babe --- /dev/null +++ b/lua/lib/quakenet/achievements.lua @@ -0,0 +1,21 @@ +require "lib/class" + +Achievements = class(function(obj, bot_id, types) + obj.bot_id = bot_id + obj.types = types +end) + +function Achievements:send(nick, type, ...) + local extra = ... + if not extra then + extra = 0 + end + + local ntype = self.types[type] + if ntype then + type = ntype + end + + local user_numeric = irc_numerictobase64(nick.numeric) + irc_privmsg("C", "EXTACH " .. user_numeric .. " " .. self.bot_id .. " " .. type .. " " .. extra) +end diff --git a/lua/lib/quakenet/iterators.lua b/lua/lib/quakenet/iterators.lua new file mode 100644 index 00000000..4ec9c6ab --- /dev/null +++ b/lua/lib/quakenet/iterators.lua @@ -0,0 +1,87 @@ +nickpusher = { nick = 0, ident = 1, hostname = 2, realname = 3, authname = 4, ipaddress = 5, numeric = 6, timestamp = 7, accountts = 8, umodes = 9, country = 10 } +chanpusher = { name = 0, totalusers = 1, topic = 2, realusers = 3, timestamp = 4, modes = 5 } + +function channelusers_iter(channel, items) + local t = { irc_getfirstchannick(channel, items) } + if #t == 0 then + return function() + return nil + end + end + local b = false + + return function() + if not b then + b = true + return t + end + + local t = { irc_getnextchannick() } + if #t == 0 then + return nil + end + return t + end +end + +function channelhash_iter(items) + local t = { irc_getfirstchan(items) } + if #t == 0 then + return function() + return nil + end + end + local b = false + + return function() + if not b then + b = true + return t + end + + local t = { irc_getnextchan() } + if #t == 0 then + return nil + end + return t + end +end + +function nickhash_iter(items) + local t = { irc_getfirstnick(items) } + if #t == 0 then + return function() + return nil + end + end + local b = false + + return function() + if not b then + b = true + return t + end + + local t = { irc_getnextnick() } + if #t == 0 then + return nil + end + return t + end +end + +function nickchannels_iter(nick) + local c = irc_getnickchancount(nick) + local i = -1 + + return function() + i = i + 1 + if i == c then + return nil + end + + return irc_getnickchanindex(nick, i) + end +end + + diff --git a/lua/lib/quakenet/legacy.lua b/lua/lib/quakenet/legacy.lua new file mode 100644 index 00000000..f0fe45d2 --- /dev/null +++ b/lua/lib/quakenet/legacy.lua @@ -0,0 +1,55 @@ +local function flatten(x) + local resulta = {} + local resultb = {} + + for k, v in pairs(x) do + table.insert(resulta, k) + table.insert(resultb, v) + end + return resulta, resultb +end + +local inickpusher, nnickpusher = flatten(nickpusher) +local ichanpusher, nchanpusher = flatten(chanpusher) + +local function __nickpush(fn, input) + local n = { fn(input, nnickpusher) } + if #n == 0 then + return + end + + local result = {} + for k, v in pairs(n) do + result[inickpusher[k]] = v + end + + -- compatibility + result.account = result.authname + return result +end + +function irc_getnickbynick(nick) + return __nickpush(irc_fastgetnickbynick, nick) +end + +function irc_getnickbynumeric(numeric) + return __nickpush(irc_fastgetnickbynumeric, numeric) +end + +function irc_getchaninfo(channel) + local c = { irc_fastgetchaninfo(channel, nchanpusher) } + if #c == 0 then + return + end + + local result = {} + for k, v in pairs(c) do + result[ichanpusher[k]] = v + end + + return result +end + +function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function) + return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function) +end diff --git a/lua/lib/schedule.lua b/lua/lib/schedule.lua new file mode 100644 index 00000000..0bdd8846 --- /dev/null +++ b/lua/lib/schedule.lua @@ -0,0 +1,74 @@ +local sched = {} +local tag = 0 + +local function timesort(a, b) + return a[1] < b[1] +end + +function doschedule() + local ct = os.time() + local out = {} + local callers = {} + + for t, e in pairs(sched) do + if t <= ct then + table.insert(callers, { t, e }) + else + out[t] = e + end + end + table.sort(callers, timesort) + + sched = out + + for _, e in ipairs(callers) do + for _, v in pairs(e[2]) do + if v[1] then + if v[2] then + v[1](unpack(v[2])) + else + v[1]() + end + else + scripterror("schedule.lua: event is nil!") + end + end + end +end + +function schedule(when, callback, ...) + tag = tag + 1 + local n = { callback, { ... }, tag } + local w = os.time() + when + + if sched[w] then + table.insert(sched[w], n) + else + sched[w] = { n } + end + return { w, tag } +end + +function delschedule(tag) + local c = {} + + local w, o = unpack(tag) + if not sched[w] then + return + end + + for i, v in pairs(sched[w]) do + if v[3] == o then + if #sched[w] == 1 then + sched[w] = nil + else + sched[w][i] = nil + end + return + end + end +end + +function scheduleempty() + return tableempty(sched) +end diff --git a/lua/lib/serialise.lua b/lua/lib/serialise.lua new file mode 100644 index 00000000..703d48e7 --- /dev/null +++ b/lua/lib/serialise.lua @@ -0,0 +1,63 @@ +function savetable(filename, t) + local f = assert(io.open(filename, "w")) + + f:write("return\n") + + serialise(f, t) + + f:close() +end + +function loadtable(filename) + local func, err = loadfile(filename) + + if not func then + return nil + end + + return func() +end + +function serialise(f, o) + if type(o) == "number" then + f:write(o) + elseif type(o) == "string" then + f:write(string.format("%q", o)) + elseif type(o) == "boolean" then + if o == true then + f:write("true") + else + f:write("false") + end + elseif type(o) == "table" then + f:write("{\n") + for k,v in pairs(o) do + f:write(" [") + serialise(f, k) + f:write("] = ") + serialise(f, v) + f:write(",\n") + end + f:write("}\n") + elseif type(o) == "nil" then + f:write("nil") + else + error("cannot serialise a " .. type(o)) + end +end + +function loadtextfile(file, fn) + local f = io.open(file, "r") + if not f then + return false + end + + while true do + local line = f:read() + if line == nil then + return true + end + + fn(line) + end +end diff --git a/lua/lib/socket.lua b/lua/lib/socket.lua new file mode 100644 index 00000000..e0459366 --- /dev/null +++ b/lua/lib/socket.lua @@ -0,0 +1,138 @@ +-- transparent non-blocking writes with buffers! + +local socket_write_raw = socket_write +local socket_unix_connect_raw = socket_unix_connect +local socket_unix_bind_raw = socket_unix_bind +local socket_tcp_connect_raw = socket_tcp_connect +local socket_tcp_bind_raw = socket_tcp_bind +local socket_udp_connect_raw = socket_udp_connect +local socket_udp_bind_raw = socket_udp_bind +local socket_close_raw = socket_close + +local sockets = {} + +function socket_handler(socket, event, tag, ...) + if event == "flush" then + local buf = sockets[socket].writebuf + local ret = socket_write_raw(socket, buf) + + if ret == -1 then + return -- close will be called + end + + sockets[socket].writebuf = buf:sub(ret + 1) + + if sockets[socket].closing and sockets[socket].writebuf == "" then + socket_close(socket) + end + + -- no reason to tell the caller + return + elseif event == "accept" then + local newsocket = ... + socket_new(newsocket, sockets[socket].handler) + end + + sockets[socket].handler(socket, event, tag, ...) + + if event == "close" then + sockets[socket] = nil + end +end + +function socket_new(socket, handler) + sockets[socket] = { writebuf = "", handler = handler } +end + +function socket_unix_connect(path, handler, tag) + local connected, socket = socket_unix_connect_raw(path, socket_handler, tag) + if connected == nil then + return nil + end + + socket_new(socket, handler) + if connected then + socket_handler(socket, "connect", tag) + end + + return socket +end + +function socket_unix_bind(path, handler, tag) + local socket = socket_unix_bind_raw(path, socket_handler, tag) + if not socket then + return nil + end + + socket_new(socket, handler) + return socket +end + +local function socket_ip_connect(fn, address, port, handler, tag) + local connected, socket = fn(address, port, socket_handler, tag) + if connected == nil then + return nil + end + + socket_new(socket, handler) + if connected then + socket_handler(socket, "connect", tag) + end + + return socket +end + +local function socket_ip_bind(fn, address, port, handler, tag) + local socket = fn(address, port, socket_handler, tag) + if not socket then + return nil + end + + socket_new(socket, handler) + return socket +end + +function socket_tcp_bind(address, port, handler, tag) + return socket_ip_bind(socket_tcp_bind_raw, address, port, handler, tag) +end + +function socket_tcp_connect(address, port, handler, tag) + return socket_ip_connect(socket_tcp_connect_raw, address, port, handler, tag) +end + +function socket_udp_bind(address, port, handler, tag) + return socket_ip_bind(socket_udp_bind_raw, address, port, handler, tag) +end + +function socket_udp_connect(address, port, handler, tag) + return socket_ip_connect(socket_udp_connect_raw, address, port, handler, tag) +end + +function socket_write(socket, data) + if sockets[socket].writebuf == "" then + local ret = socket_write_raw(socket, data) + if ret == -1 then + return false -- close will be called regardless + end + + if ret == data:len() then + return true + end + + -- lua strings start at 1 + sockets[socket].writebuf = data:sub(ret + 1) + else + sockets[socket].writebuf = sockets[socket].writebuf .. data + end + + return true +end + +function socket_close(socket, flush) + if whenbufferempty and sockets[socket].writebuf ~= "" then + sockets[socket].closing = true + return + end + + return socket_close_raw(socket) +end