]> jfr.im git - irc/quakenet/newserv.git/commitdiff
LUA: Add lua stdlib.
authorChris Porter <redacted>
Sun, 28 Mar 2010 00:08:28 +0000 (00:08 +0000)
committerChris Porter <redacted>
Sun, 28 Mar 2010 00:08:28 +0000 (00:08 +0000)
16 files changed:
lua/lib/bootstrap.lua [new file with mode: 0644]
lua/lib/class.lua [new file with mode: 0644]
lua/lib/country.lua [new file with mode: 0644]
lua/lib/db.lua [new file with mode: 0644]
lua/lib/jit.lua [new file with mode: 0644]
lua/lib/json.lua [new file with mode: 0644]
lua/lib/minute_count.lua [new file with mode: 0644]
lua/lib/missing.lua [new file with mode: 0644]
lua/lib/quakenet.lua [new file with mode: 0644]
lua/lib/quakenet/access.lua [new file with mode: 0644]
lua/lib/quakenet/achievements.lua [new file with mode: 0644]
lua/lib/quakenet/iterators.lua [new file with mode: 0644]
lua/lib/quakenet/legacy.lua [new file with mode: 0644]
lua/lib/schedule.lua [new file with mode: 0644]
lua/lib/serialise.lua [new file with mode: 0644]
lua/lib/socket.lua [new file with mode: 0644]

diff --git a/lua/lib/bootstrap.lua b/lua/lib/bootstrap.lua
new file mode 100644 (file)
index 0000000..6555c96
--- /dev/null
@@ -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 (file)
index 0000000..6ec23cf
--- /dev/null
@@ -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 <classname>(<args>)
+  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 (file)
index 0000000..50f6520
--- /dev/null
@@ -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 (file)
index 0000000..6bcf90e
--- /dev/null
@@ -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 (file)
index 0000000..d81d74b
--- /dev/null
@@ -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 (file)
index 0000000..1af8e1d
--- /dev/null
@@ -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 (file)
index 0000000..53d1d8d
--- /dev/null
@@ -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 (file)
index 0000000..2fde729
--- /dev/null
@@ -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 (file)
index 0000000..f460c7e
--- /dev/null
@@ -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 (file)
index 0000000..9ead23b
--- /dev/null
@@ -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 (file)
index 0000000..04f7bab
--- /dev/null
@@ -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 (file)
index 0000000..4ec9c6a
--- /dev/null
@@ -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 (file)
index 0000000..f0fe45d
--- /dev/null
@@ -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 (file)
index 0000000..0bdd884
--- /dev/null
@@ -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 (file)
index 0000000..703d48e
--- /dev/null
@@ -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 (file)
index 0000000..e045936
--- /dev/null
@@ -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