Advertisement
Guest User

Untitled

a guest
Aug 6th, 2017
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.98 KB | None | 0 0
  1. -- Copyright 2016-2017 Fingercomp
  2.  
  3. -- Licensed under the Apache License, Version 2.0 (the "License");
  4. -- you may not use this file except in compliance with the License.
  5. -- You may obtain a copy of the License at
  6.  
  7. --     http://www.apache.org/licenses/LICENSE-2.0
  8.  
  9. -- Unless required by applicable law or agreed to in writing, software
  10. -- distributed under the License is distributed on an "AS IS" BASIS,
  11. -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. -- See the License for the specific language governing permissions and
  13. -- limitations under the License.
  14.  
  15. local com = require("component")
  16. local event = require("event")
  17. local unicode = require("unicode")
  18. local fs = require("filesystem")
  19. local text = require("text")
  20. local srl = require("serialization")
  21.  
  22. local modulesPath = "/usr/lib/chat-modules/"
  23. local env = {}
  24. local config = "/etc/opg-chat.json"
  25. local exit = false
  26. local openos = _OSVERSION == "OpenOS 1.6" and "1.6" or (_OSVERSION == "OpenOS 1.5" and "1.5" or (io.stderr:write("Warning: unknown OS! The program may eventually crash or work incorrectly.\n") and "1.5" or "1.5"))
  27. local lua = math.tointeger and "5.3" or (io.stderr:write("Warning: Lua version is not 5.3! This may cause issues with the program.\n") and "5.2" or "5.2")
  28. local guid
  29. if openos == "1.6" then
  30.   local success
  31.   success, guid = pcall(require, "uuid")
  32.   if not success then
  33.     guid = require("guid")
  34.   end
  35. else
  36.   guid = {
  37.     toHex = function(n)
  38.       if type(n) ~= 'number' then
  39.         return nil, string.format("toHex only converts numbers to strings, %s is not a string, but a %s", tostring(n), type(n))
  40.       end
  41.       if n == 0 then
  42.         return '0'
  43.       end
  44.  
  45.       local hexchars = "0123456789abcdef"
  46.       local result = ""
  47.       local prefix = "" -- maybe later allow for arg to request 0x prefix
  48.       if n < 0 then
  49.         prefix = "-"
  50.         n = -n
  51.       end
  52.  
  53.       while n > 0 do
  54.         local next = math.floor(n % 16) + 1 -- lua has 1 based array indices
  55.         n = math.floor(n / 16)
  56.         result = hexchars:sub(next, next) .. result
  57.       end
  58.  
  59.       return prefix .. result
  60.     end,
  61.     next = function()
  62.       -- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
  63.       -- 8-4-4-4-12
  64.       local sets = {8, 4, 4, 4, 12}
  65.       local result = ""
  66.  
  67.       local i
  68.       for _,set in ipairs(sets) do
  69.         if result:len() > 0 then
  70.           result = result .. "-"
  71.         end
  72.         for i = 1,set do
  73.           result = result .. guid.toHex(math.random(0, 15))
  74.         end
  75.       end
  76.  
  77.       return result
  78.     end
  79.   }
  80. end
  81.  
  82. event.push = event.push or require("computer").pushSignal
  83.  
  84. local function reqcom(componentName, req, msg)
  85.   if not com.isAvailable(componentName) then
  86.     if req then
  87.       io.stderr:write((msg or "No such component: " .. componentName .. "!") .. "\n")
  88.       os.exit(-1)
  89.     else
  90.       local _ = msg and io.stderr:write(msg .. "\n")
  91.       _ = nil
  92.       return setmetatable({}, {
  93.         __tostring = function(self)
  94.           return "This is a dummy component"
  95.         end,
  96.         __index = function(self, k)
  97.           if k == "address" then
  98.             return guid.next()
  99.           elseif k == "slot" then
  100.             return -1
  101.           elseif k == "type" then
  102.             return componentName
  103.           else
  104.             return function()
  105.               return
  106.             end
  107.           end
  108.         end
  109.       }), false
  110.     end
  111.   end
  112.   return com[componentName], true
  113. end
  114.  
  115. if not fs.exists("/usr/lib/json.lua") then
  116.   local inet = reqcom("internet", true, "This program needs an internet card to install json lib!")
  117.   if not fs.exists("/usr/lib") then
  118.     fs.makeDirectory("/usr/lib")
  119.   end
  120.   local request = inet.request("http://regex.info/code/JSON.lua")
  121.   local file = io.open("/usr/lib/json.lua", "w")
  122.   while true do
  123.     local chunk = request.read()
  124.     if not chunk then break end
  125.     file:write(chunk)
  126.   end
  127.   file:close()
  128. end
  129.  
  130. local bridge = reqcom("openperipheral_bridge", true, "This program needs an Openperipheral bridge to work!")
  131.  
  132. local json = require("json")
  133.  
  134. if not fs.exists(config) then
  135.   local f = io.open(config, "w")
  136.   f:write(json:encode_pretty({
  137.     server = "%SERVER%",
  138.     admins = {"Fingercomp"},
  139.     main_channel = "#main",
  140.     net = {
  141.       enabled = true,
  142.       modem_strength = 400,
  143.       ports = {
  144.         ["6667"] = true,
  145.         ["6666"] = {"244d"}
  146.       },
  147.       ping = {
  148.         enabled = true,
  149.         interval = 180,
  150.         timeout = 180
  151.       }
  152.     },
  153.     users = {},
  154.     max_chan_lines = 750
  155.   }))
  156.   f:close()
  157.   print("No configuration file found, created a new one. Path to the config: " .. config)
  158.   print("Edit the settings and relaunch the program.")
  159.   return 0
  160. end
  161.  
  162. -- Let's load the config here to be sure the program
  163. -- can access it if I'd need it somewhere in program init
  164. local cfg = {}
  165. do
  166.   local f = io.open(config, "r")
  167.   local all = f:read("*a")
  168.   f:close()
  169.   cfg = json:decode(all)
  170.   if not cfg.main_channel:match("^#%w[%w%._]*$") then
  171.     io.stderr:write("invalid main channel name, expected \"^#%w[%w%._]*$\". Fix your configuration file.")
  172.   end
  173. end
  174.  
  175. local surfaces = {}
  176.  
  177. local NORMAL  = 0x0
  178. local VOICE   = 0x1
  179. local HALFOP  = 0x2
  180. local OP      = 0x4
  181. local ADMIN   = 0x8
  182. local SERVER  = 0x10
  183.  
  184. local PREFIXES = {
  185.   [NORMAL] = "",
  186.   [VOICE] = "§e+§f",
  187.   [HALFOP] = "§2%§f",
  188.   [OP] = "§a@§f"
  189. }
  190.  
  191. local notifications = {
  192.   join_chan = {pattern = "§6%s§f joined %s", nick = "§2-->"},
  193.   part_chan = {pattern = "§6%s§f left %s (%s)", nick = "§4<--"},
  194.   quit = {pattern = "§6%s§f quit the server (%s)", nick = "§4<--"},
  195.   pm = {pattern = "§3%s§6 → §3%s§f: %s", nick = "§3--"},
  196.   topic = {pattern = "§6%s§f changed topic to: \"%s\"", nick = "§5**"},
  197.   mode = {pattern = "§6%s§f set modes [%s %s]", nick = "§5**"}
  198. }
  199.  
  200. local modes = {}
  201. local users = {}
  202. local channels = {}
  203.  
  204. local codePtn = "§[%xoklmn]"
  205.  
  206. local function band(...)
  207.   local bit32 = bit32 or require("bit32")
  208.   return bit32.band(...)
  209. end
  210.  
  211. local function isin(tbl, value)
  212.   for k, v in pairs(tbl) do
  213.     if v == value then
  214.       return true, k
  215.     end
  216.   end
  217.   return false
  218. end
  219.  
  220. local function copy(tbl)
  221.   if type(tbl) ~= "table" then
  222.     return tbl
  223.   end
  224.   local result = {}
  225.   for k, v in pairs(tbl) do
  226.     result[k] = copy(v)
  227.   end
  228.   return result
  229. end
  230.  
  231. local function stripCodes(line)
  232.   return line:gsub(codePtn, "")
  233. end
  234.  
  235. local function getLineLen(line)
  236.   return unicode.len(stripCodes(line))
  237. end
  238.  
  239. local function subLine(line, p1, p2)
  240.   local result = {}
  241.   local code = ""
  242.   for i = 1, unicode.len(line), 1 do
  243.     local prev, sym, nxt = unicode.sub(line, i - 1, i - 1), unicode.sub(line, i, i), unicode.sub(line, i + 1, i + 1)
  244.     if prev and (prev .. sym):match(codePtn) then
  245.       code = prev .. sym
  246.     elseif not (sym .. nxt):match(codePtn) then
  247.       table.insert(result, code .. sym)
  248.       code = ""
  249.     end
  250.   end
  251.   for i = p2 + 1, #result, 1 do
  252.     table.remove(result)
  253.   end
  254.   for i = 1, p1 - 1, 1 do
  255.     table.remove(result, 1)
  256.   end
  257.   return table.concat(result, "")
  258. end
  259.  
  260. local function wrap(line, width)
  261.   local result = {}
  262.   for i = 1, getLineLen(line), width do
  263.     local wrappedLine = text.trim(subLine(line, i, i + width - 1))
  264.     if wrappedLine ~= "" then
  265.       table.insert(result, wrappedLine)
  266.     end
  267.   end
  268.   return result
  269. end
  270.  
  271. local function getLevel(chan, user)
  272.   local level = NORMAL
  273.   if not users[user] then return level end
  274.   if cfg.server == user then
  275.     level = level + SERVER
  276.   end
  277.   if isin(cfg.admins, user) then
  278.     level = level + ADMIN
  279.   end
  280.   if not channels[chan] or not channels[chan].users[user] then
  281.     return level
  282.   end
  283.   return level + channels[chan].users[user]
  284. end
  285.  
  286. local function checkLevel(chan, user, levels, any)
  287.   local proceed = true
  288.   local userLevel = getLevel(chan, user)
  289.   for _, level in pairs(levels) do
  290.     if band(userLevel, level) == level then
  291.       if any then
  292.         return true
  293.       end
  294.     else
  295.       proceed = false
  296.     end
  297.   end
  298.   return proceed
  299. end
  300.  
  301. function env.apcall(func, ...)
  302.   local data = {pcall(func, ...)}
  303.   if data[1] then
  304.     return true, table.unpack(data, 2)
  305.   end
  306.   local reason = data[2]
  307.   reason = reason:match("^.+:%d+:%s(.+)$")
  308.   if reason then
  309.     return false, reason, table.unpack(data, 3)
  310.   end
  311.   return false, table.unpack(data, 2)
  312. end
  313.  
  314. local function addObject(surface, name, func, ...)
  315.   checkArg(1, surface, "table")
  316.   checkArg(2, name, "string", "nil")
  317.   checkArg(3, func, "string")
  318.   local args = {...}
  319.   local reason
  320.   if name then
  321.     surface.objects[name], reason = surface.surface[func](table.unpack(args))
  322.   else
  323.     surface.objects.insert(surface.surface[func](table.unpack(args)))
  324.   end
  325.   if reason then
  326.     print(reason)
  327.   end
  328.   surface.objects[name].setUserdata({name = name})
  329.   return surface.objects[name]
  330. end
  331.  
  332. local function drawChat(surface)
  333.   addObject(surface, "chat.box.chat", "addBox", 5, 55, 400, 120, 0x282828, .8)
  334.   addObject(surface, "chat.box.topic", "addBox", 5, 45, 400, 11, 0x404040, .8)
  335.   addObject(surface, "chat.box.userlist", "addBox", 410, 35, 100, 150, 0x282828, .8)
  336.   addObject(surface, "chat.box.input", "addBox", 5, 175, 400, 10, 0x404040, .8)
  337.   addObject(surface, "chat.line.nick", "addLine", {x=105, y=55}, {x=105,y=185}, 0x20afff, .8)
  338.   addObject(surface, "chat.line.input", "addLine", {x=5, y=175}, {x=405,y=175}, 0x20afff, .8)
  339.   addObject(surface, "chat.line.topic", "addLine", {x=5, y=55}, {x=405, y=55}, 0x20afff, .8)
  340.   addObject(surface, "chat.line.userlist", "addLine", {x=410, y=45}, {x=510, y=45}, 0x20afff, .8)
  341.   for i = 1, 9, 1 do
  342.     local start = (i - 1) * 45 + 5
  343.     addObject(surface, "chat.poly.chans." .. i, "addPolygon", 0x105888, .8, {x=start, y=45}, {x=start, y=37}, {x=start+2, y=35}, {x=start+38, y=35}, {x=start+40, y=37}, {x=start+40, y=45}).setVisible(false)
  344.     addObject(surface, "chat.poly.chans." .. i .. ".active", "addPolygon", 0x101010, .8, {x=start, y=37}, {x=start, y=34}, {x=start+2, y=32}, {x=start+38, y=32}, {x=start+40, y=34}, {x=start+40, y=37}, {x=start+38, y=35}, {x=start+2, y=35}).setVisible(false)
  345.     local chanText = addObject(surface, "chat.text.chans." .. i, "addText", start+2, 37, "", 0xffffff)
  346.   end
  347.   addObject(surface, "chat.text.userlist", "addText", 412, 37, "Users:", 0x20afff)
  348.   for i = 1, 14 do
  349.     local start = (i - 1) * 10 + 47
  350.     addObject(surface, "chat.text.users." .. i, "addText", 412, start, "", 0xffffff)
  351.   end
  352.   for i = 1, 12 do
  353.     local start = (i - 1) * 10 + 57
  354.     addObject(surface, "chat.text.lines." .. i .. ".nick", "addText", 7, start, "", 0xffffff)
  355.     addObject(surface, "chat.text.lines." .. i .. ".msg", "addText", 107, start, "", 0xffffff)
  356.   end
  357.   addObject(surface, "chat.text.input.nick", "addText", 7, 177, "", 0xffffff)
  358.   addObject(surface, "chat.text.input.input", "addText", 107, 177, "", 0xd3d3d3)
  359.   addObject(surface, "chat.text.topic", "addText", 7, 47, "", 0xffffff)
  360. end
  361.  
  362. local function truncate(chan)
  363.   while #channels[chan].lines > cfg.max_chan_lines do
  364.     local ntcs, msgs = 0, 0
  365.     for k, v in pairs(channels[chan].lines) do
  366.       if (v.notify and v[1] or v[3]) ~= "all" then
  367.         ntcs = ntcs + 1
  368.       else
  369.         msgs = msgs + 1
  370.       end
  371.     end
  372.     local rmMsg = msgs > ntcs
  373.     for i = 1, #channels[chan].lines, 1 do
  374.       local inV = channels[chan].lines[i]
  375.       if (inV.notify and inV[1] or inV[3]) ~= "all" and not rmMsg or
  376.           (inV.notify and inV[1] or inV[3]) == "all" and rmMsg then
  377.         table.remove(channels[chan].lines, i)
  378.         break
  379.       end
  380.     end
  381.   end
  382. end
  383.  
  384. local function createChannel(chan, nick)
  385.   checkArg(1, chan, "string")
  386.   checkArg(2, nick, "string")
  387.   assert(users[nick], "no such nickname")
  388.   assert(chan:sub(1, 1) == "#", "not a channel")
  389.   assert(chan:match("^#%w[%w%._-]*$"), "invalid chars in chan name")
  390.   channels[chan] = {
  391.     info = {
  392.       ["creation-date"] = os.date("%Y-%m-%d %H:%M:%S")
  393.     },
  394.     users = {
  395.       [nick] = NORMAL
  396.     },
  397.     lines = {},
  398.     modes = {},
  399.     topic = "",
  400.     banned = {},
  401.     exempt = {}
  402.   }
  403.   table.insert(users[nick].channels, chan)
  404.   event.push("chat_event_createChannel", os.time(), chan, nick)
  405. end
  406.  
  407. local function addUser(user, isNetUser)
  408.   checkArg(1, user, "string")
  409.   checkArg(2, isNetUser, "boolean", "nil")
  410.   assert(not users[user], "user already exists")
  411.   cfg.users[user] = cfg.users[user] or {
  412.     pass = ""
  413.   }
  414.   users[user] = {
  415.     channels = {},
  416.     modes = {},
  417.     prompt = {},
  418.     history = {},
  419.     tabStart = 1,
  420.     currentTab = 1,
  421.     shown = true,
  422.     channelOffsets = {},
  423.     cfg = cfg.users[user],
  424.     net = isNetUser or false
  425.   }
  426. end
  427.  
  428. local function join(chan, user)
  429.   checkArg(1, chan, "string")
  430.   checkArg(2, user, "string")
  431.   assert(chan:sub(1, 1) == "#", "not a channel")
  432.   assert(users[user], "no such nickname")
  433.   assert(not isin(users[user].channels, chan), "already in the channel")
  434.   if not channels[chan] then
  435.     createChannel(chan, user)
  436.   else
  437.     channels[chan].users[user] = NORMAL
  438.     table.insert(users[user].channels, chan)
  439.   end
  440.   users[user].channelOffsets[chan] = 1
  441.   users[user].prompt[chan] = {"", 1, 1}
  442.   users[user].history[chan] = {pos = 0}
  443. end
  444.  
  445. local function part(chan, user)
  446.   checkArg(1, chan, "string")
  447.   checkArg(2, user, "string")
  448.   assert(channels[chan], "no such channel")
  449.   assert(users[user], "no such nickname")
  450.   assert(channels[chan].users[user], "user is not in the channel")
  451.   channels[chan].users[user] = nil
  452.   local _, pos = isin(users[user].channels, chan)
  453.   table.remove(users[user].channels, pos)
  454. end
  455.  
  456. local function sendMsgChan(chan, nick, msg, rec)
  457.   checkArg(1, chan, "string")
  458.   checkArg(2, nick, "string")
  459.   checkArg(3, msg, "string")
  460.   checkArg(4, rec, "table", "nil")
  461.   assert(chan:sub(1, 1) == "#", "not a channel")
  462.   assert(channels[chan], "no such channel")
  463.   assert(users[nick], "no such nickname")
  464.   local date = os.date("%Y-%m-%d %H:%M:%S")
  465.   rec = rec or "all"
  466.   table.insert(channels[chan].lines, {date = date, level = channels[chan].users[nick], nick, msg, rec})
  467.   truncate(chan)
  468.   event.push("chat_event_msg", os.time(), chan, nick, msg, rec == "all" or #rec, table.unpack(rec == "all" and {rec} or rec))
  469. end
  470.  
  471. local function sendNotifyChan(chan, notify, parts, rec)
  472.   checkArg(1, chan, "string")
  473.   checkArg(2, notify, "string")
  474.   checkArg(3, parts, "table")
  475.   checkArg(4, rec, "table", "nil")
  476.   assert(chan:sub(1, 1) == "#", "not a channel")
  477.   assert(channels[chan], "no such channel")
  478.   assert(notifications[notify], "no such notification")
  479.   local date = os.date("%Y-%m-%d %H:%M:%S")
  480.   rec = rec or "all"
  481.   table.insert(channels[chan].lines, {date = date, notify = {notify, parts}, rec})
  482.   truncate(chan)
  483.   event.push("chat_event_notice", os.time(), chan, notify, notifications[notify].pattern:format(table.unpack(parts)), rec == "all" or #rec, table.unpack(rec == "all" and {rec} or rec), srl.serialize(parts))
  484. end
  485.  
  486. local function sendPM(addressee, user, msg)
  487.   checkArg(1, addressee, "string")
  488.   checkArg(2, user, "string")
  489.   checkArg(3, msg, "string")
  490.   assert(users[addressee], "no such user")
  491.   assert(users[user], "no such nickname")
  492.   sendNotifyChan(cfg.main_channel, "pm", {user, addressee, msg}, {user, addressee})
  493.   event.push("chat_event_pm", os.time(), user, addressee, msg)
  494. end
  495.  
  496. modes.o = function(chan, user, set, arg)
  497.   if not arg then return false end
  498.   assert(channels[chan].users[arg], "no such user")
  499.   assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true), "no permission")
  500.   local was = channels[chan].users[arg]
  501.   channels[chan].users[arg] = set and OP or NORMAL
  502.   if was ~= channels[chan].users[arg] then
  503.     return true
  504.   end
  505. end
  506.  
  507. modes.h = function(chan, user, set, arg)
  508.   if not arg then return false end
  509.   assert(channels[chan].users[arg], "no such user")
  510.   assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true) or checkLevel(chan, user, {HALFOP}, true) and user == arg, "no permission")
  511.   local was = channels[chan].users[arg]
  512.   channels[chan].users[arg] = set and HALFOP or NORMAL
  513.   if was ~= channels[chan].users[arg] then
  514.     return true
  515.   end
  516. end
  517.  
  518. modes.v = function(chan, user, set, arg)
  519.   if not arg then return false end
  520.   assert(channels[chan].users[arg], "no such user")
  521.   assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true) or checkLevel(chan, user, {VOICE, HALFOP}, true) and user == arg, "no permission")
  522.   local was = channels[chan].users[arg]
  523.   channels[chan].users[arg] = set and VOICE or NORMAL
  524.   if was ~= channels[chan].users[arg] then
  525.     return true
  526.   end
  527. end
  528.  
  529. local function togglableMode(mode, level, any)
  530.   checkArg(1, mode, "string")
  531.   checkArg(2, level, "table")
  532.   checkArg(3, soft, "boolean", "nil")
  533.   any = any or true
  534.   return function(chan, user, set, arg)
  535.     assert(checkLevel(chan, user, level, any), "no permission")
  536.     if set and not isin(channels[chan].modes, mode) then
  537.       table.insert(channels[chan].modes, mode)
  538.     else
  539.       local _, pos = isin(channels[chan].modes, mode)
  540.       if pos then
  541.         table.remove(channels[chan].modes, pos)
  542.       else
  543.         return false
  544.       end
  545.     end
  546.    return true
  547.  end
  548. end
  549.  
  550. modes.t = togglableMode("t", {OP, ADMIN, SERVER})
  551. modes.m = togglableMode("m", {HALFOP, OP, ADMIN, SERVER})
  552.  
  553. local function setMode(chan, user, mode, arg)
  554.   checkArg(1, chan, "string")
  555.   checkArg(2, user, "string")
  556.   checkArg(3, mode, "string")
  557.   checkArg(4, arg, "string", "nil")
  558.   assert(channels[chan], "no such channel")
  559.   assert(mode:match("^[+-].$"), "wrong mode")
  560.   local set = mode:sub(1, 1) == "+"
  561.   mode = mode:sub(2)
  562.   assert(modes[mode], "unknown mode")
  563.   local success = modes[mode](chan, user, set, arg)
  564.   if success then
  565.     local modeStr = (set and "+" or "-") .. mode .. (arg and " " .. arg or "")
  566.     sendNotifyChan(chan, "mode", {user, chan, modeStr})
  567.   end
  568. end
  569.  
  570. local function joinN(chan, user)
  571.   join(chan, user)
  572.   sendNotifyChan(chan, "join_chan", {user, chan})
  573.   event.push("chat_event_join", os.time(), chan, user)
  574. end
  575.  
  576. local function partN(chan, user, reason)
  577.   reason = reason or ""
  578.   part(chan, user)
  579.   sendNotifyChan(chan, "part_chan", {user, chan, reason})
  580.   event.push("chat_event_part", os.time(), chan, user, reason)
  581. end
  582.  
  583. local function quitN(user, reason)
  584.   local chans = users[user].channels
  585.   reason = reason or ""
  586.   for i = #chans, 1, -1 do
  587.     local chan = chans[i]
  588.     part(chan, user)
  589.     sendNotifyChan(chan, "quit", {user, reason})
  590.   end
  591.   users[user] = nil
  592.   event.push("chat_event_quit", os.time(), user, reason, table.unpack(chans))
  593. end
  594.  
  595. local function sendMsgChanN(chan, user, msg)
  596.   if isin(channels[chan].modes, "m") and not checkLevel(chan, user, {VOICE, HALFOP, OP}, true) then
  597.     sendPM(user, cfg.server, "The channel is moderated")
  598.     return -1
  599.   end
  600.   if isin(channels[chan].banned, user) and not isin(channels[chan].exempt, user) and not checkLevel(chan, user, {HALFOP, OP, ADMIN, SERVER}) then
  601.     sendPM(user, cfg.server, "You are banned from the channel")
  602.     return -1
  603.   end
  604.   local success, reason = env.apcall(sendMsgChan, chan, user, msg)
  605.   if not success then
  606.     sendPM(user, cfg.server, "Could not send message: " .. reason)
  607.   end
  608. end
  609.  
  610. local function getActiveChannel(user)
  611.   local active = users[user].currentTab
  612.   return users[user].channels[active] or false
  613. end
  614.  
  615. local moduleHandlers = {}
  616. local commands = {}
  617. local storage = {}
  618.  
  619. env.getActiveChannel = getActiveChannel
  620. env.createChannel = createChannel
  621. env.addUser = addUser
  622. env.join = join
  623. env.part = part
  624. env.sendMsgChan = sendMsgChan
  625. env.sendNotifyChan = sendNotifyChan
  626. env.sendPM = sendPM
  627. env.joinN = joinN
  628. env.partN = partN
  629. env.quitN = quitN
  630. env.sendMsgChanN = sendMsgChanN
  631. env.addObject = addObject
  632. env.bridge = bridge
  633. env.surfaces = surfaces
  634. env.users = users
  635. env.channels = channels
  636. env.commands = commands
  637. env.isin = isin
  638. env.cfg = cfg
  639. env.setMode = setMode
  640. env.modes = modes
  641. env.getLevel = getLevel
  642. env.checkLevel = checkLevel
  643. env.togglableMode = togglableMode
  644. env.storage = storage
  645. env.reqcom = reqcom
  646. env.copy = copy
  647. env._MODULE = ""
  648. env._FILE = ""
  649. env.NORMAL = NORMAL
  650. env.VOICE = VOICE
  651. env.HALFOP = HALFOP
  652. env.OP = OP
  653. env.ADMIN = ADMIN
  654. env.SERVER = SERVER
  655. env.PREFIXES = PREFIXES
  656.  
  657. function env.addListener(eventName, name, func)
  658.   checkArg(1, eventName, "string")
  659.   checkArg(2, name, "string")
  660.   checkArg(3, func, "function")
  661.   if moduleHandlers[eventName] and moduleHandlers[eventName][name] then
  662.     assert(false, "ununique name!")
  663.   end
  664.   moduleHandlers[eventName] = moduleHandlers[eventName] or {}
  665.   moduleHandlers[eventName][name] = func
  666. end
  667.  
  668. function env.delListener(eventName, name)
  669.   checkArg(1, eventName, "string")
  670.   checkArg(2, name, "string")
  671.   if moduleHandlers[eventName][name] then
  672.     event.ignore(eventName, moduleHandlers[eventName][name])
  673.     moduleHandlers[eventName][name] = nil
  674.   end
  675. end
  676.  
  677. local function cmdWrapper(cmdInfo)
  678.   return function(evt, chan, user, raw, cmd, ...)
  679.     if checkLevel(chan, user, cmdInfo.level, true) then
  680.       cmdInfo.func(evt, chan, user, raw, cmd, ...)
  681.     else
  682.       sendPM(user, cfg.server, "no permission")
  683.     end
  684.   end
  685. end
  686.  
  687. local function command(setEnv)
  688.   return function(args)
  689.     checkArg(1, args, "table")
  690.     local name, level, help, doc, aliases, func = args.name, args.level, args.help, args.doc, args.aliases, args.func
  691.     local errorPattern = "\"%s\": %s expected, %s given"
  692.     assert(type(name) == "string", errorPattern:format("name", "string", type(name)))
  693.     assert(isin({"table", "number"}, type(level)), errorPattern:format("level", "table or number", type(level)))
  694.     assert(isin({"nil", "string"}, type(help)), errorPattern:format("help", "string or nil", type(help)))
  695.     assert(isin({"nil", "string"}, type(doc)), errorPattern:format("doc", "string or nil", type(doc)))
  696.     assert(isin({"table", "nil"}, type(aliases)), errorPattern:format("aliases", "table or nil", type(aliases)))
  697.     assert(type(func) == "function", errorPattern:format("func", "function", type(func)))
  698.     if type(level) == "number" then
  699.       local levels = {NORMAL, VOICE, HALFOP, OP, ADMIN, SERVER}
  700.       local _, pos = isin(levels, level)
  701.       assert(pos, "wrong level")
  702.       for i = 1, pos - 1, 1 do
  703.         table.remove(levels, 1)
  704.       end
  705.       level = levels
  706.     end
  707.     commands[name] = {level = level, help = help, doc = doc, aliases = aliases, func = func}
  708.     local cmds = {name, table.unpack(aliases or {})}
  709.     for _, cmd in pairs(cmds) do
  710.       env.addListener("chat_slash_cmd_" .. cmd, setEnv._MODULE .. ".commands." .. name .. "." .. cmd, cmdWrapper(commands[name]))
  711.     end
  712.   end
  713. end
  714.  
  715. function env.help(user, cmd)
  716.   checkArg(1, user, "string")
  717.   checkArg(2, cmd, "string")
  718.   assert(users[user], "no such user")
  719.   assert(commands[cmd], "no such command")
  720.   sendPM(cfg.server, user, "Help (" .. cmd .. "): " .. (commands[cmd].help or ""))
  721.   if commands[cmd].doc then
  722.     local docStr = commands[cmd].doc
  723.     local doc = {""}
  724.     for i = 1, unicode.len(docStr), 1 do
  725.       local sym = unicode.sub(docStr, i, i)
  726.       if sym == "\n" then
  727.         doc[#doc+1] = ""
  728.       else
  729.         doc[#doc] = doc[#doc] .. sym
  730.       end
  731.     end
  732.     for _, line in ipairs(doc) do
  733.       sendPM(user, cfg.server, "> " .. line)
  734.     end
  735.   end
  736. end
  737.  
  738. local function saveCfg()
  739.   local content = json:encode_pretty(cfg)
  740.   local f = io.open(config, "r")
  741.   local backup = io.open(config .. ".backup", "w")
  742.   backup:write(f:read("*a"))
  743.   backup:close()
  744.   f:close()
  745.   f = io.open(config, "w")
  746.   f:write(content)
  747.   f:close()
  748. end
  749.  
  750. local coreHandlers = {
  751.   chat_init = {
  752.     function(evt, time)
  753.       addUser(cfg.server)
  754.       join(cfg.main_channel, cfg.server)
  755.       channels[cfg.main_channel].users[cfg.server] = OP
  756.       bridge.clear()
  757.       for _, user in pairs(bridge.getUsers()) do
  758.         user = user.name
  759.         surfaces[user] = {surface = bridge.getSurfaceByName(user)}
  760.         surfaces[user].objects = {}
  761.         drawChat(surfaces[user])
  762.       end
  763.     end
  764.   },
  765.   chat_start = {
  766.     function(evt, time)
  767.       for user in pairs(surfaces) do
  768.         addUser(user)
  769.         joinN(cfg.main_channel, user)
  770.       end
  771.     end
  772.   },
  773.   glasses_attach = {
  774.     function(evt, addr, user, uuid)
  775.       surfaces[user] = {surface = bridge.getSurfaceByName(user)}
  776.       surfaces[user].surface.clear()
  777.       surfaces[user].objects = {}
  778.       drawChat(surfaces[user])
  779.       if not users[user] then
  780.         addUser(user)
  781.       end
  782.       joinN(cfg.main_channel, user)
  783.     end
  784.   },
  785.   glasses_detach = {
  786.     function(evt, addr, user, uuid)
  787.       local _ = surfaces[user] and surfaces[user].surface and surfaces[user].surface.clear()
  788.       surfaces[user] = nil
  789.       if users[user] then
  790.         quitN(user)
  791.       end
  792.     end
  793.   },
  794.   chat_update = {
  795.     function(evt, time, tick)
  796.       if tick % 5 == 0 then
  797.         for user, surface in pairs(surfaces) do
  798.           local userinfo = users[user]
  799.           if not userinfo then goto nextUser end
  800.           if not userinfo.shown then goto nextUser end
  801.  
  802.           -- 1. TABS
  803.           -- 1.1. Set tabs
  804.           local chans = userinfo.channels
  805.           chans = table.pack(table.unpack(chans, userinfo.tabStart, userinfo.tabStart + 10))
  806.           for i, chan in ipairs(chans) do
  807.             if #chan > 6 then
  808.               chanLabel = chan:sub(1, 5) .. "…"
  809.             else
  810.               chanLabel = chan
  811.             end
  812.             local textObj = surface.objects["chat.text.chans." .. i]
  813.             if textObj.getText() ~= chanLabel then
  814.               textObj.setText(chanLabel)
  815.               local userdata = textObj.getUserdata() or {}
  816.               userdata.chan = chan
  817.               textObj.setUserdata(userdata)
  818.             end
  819.             surface.objects["chat.poly.chans." .. i].setVisible(true)
  820.             surface.objects["chat.poly.chans." .. i .. ".active"].setVisible(false)
  821.           end
  822.  
  823.           -- 1.2. Hide unused tabs
  824.           if #userinfo.channels < 10 then
  825.             local start = #userinfo.channels + 1
  826.             for i = start, 9, 1 do
  827.               surface.objects["chat.poly.chans." .. i].setVisible(false)
  828.               local userdata = surface.objects["chat.text.chans." .. i].getUserdata() or {}
  829.               userdata.chan = nil
  830.               surface.objects["chat.text.chans." .. i].setUserdata(userdata)
  831.               surface.objects["chat.text.chans." .. i].setText("")
  832.               surface.objects["chat.poly.chans." .. i .. ".active"].setVisible(false)
  833.             end
  834.           end
  835.  
  836.           -- 1.3. Select active tab
  837.           local showTab = getActiveChannel(user)
  838.           if not showTab then
  839.             goto nextUser
  840.           end
  841.           local active = userinfo.currentTab
  842.           surface.objects["chat.poly.chans." .. active .. ".active"].setVisible(true)
  843.  
  844.  
  845.           -- 2. MSG AREA
  846.           -- 2.1. Get lines to show
  847.           local toShow = {}
  848.           local lines = channels[showTab].lines
  849.           local offset = userinfo.channelOffsets[showTab]
  850.           for i = 1, #lines, 1 do
  851.             local line = lines[i]
  852.             local date, nick, msg, rec, notify = line.date, nil, nil, nil, line.notify
  853.             if not notify then
  854.               nick, msg, rec = line[1], line[2], line[3]
  855.             else
  856.               rec = line[1]
  857.             end
  858.             if rec == "all" or isin(rec, user) then
  859.               local name = ""
  860.               if not notify then
  861.                 local userPrefix = PREFIXES[line.level or NORMAL]
  862.                 name = (userPrefix or "") .. nick
  863.               else
  864.                 msg = notifications[notify[1]].pattern:format(table.unpack(notify[2]))
  865.                 name = notifications[notify[1]].nick
  866.               end
  867.               local msglines = wrap(msg, 49)
  868.               if #msglines == 1 then
  869.                 table.insert(toShow, {nick = name, msg = msg})
  870.               else
  871.                 local wrapped = {}
  872.                 for i = #msglines, 1, -1 do
  873.                   local nickText = i == 1 and name or ""
  874.                   msg = msglines[i]
  875.                   table.insert(wrapped, {nick = nickText, msg = msg})
  876.                 end
  877.                 for wrI = #wrapped, 1, -1 do
  878.                   table.insert(toShow, wrapped[wrI])
  879.                 end
  880.               end
  881.             end
  882.           end
  883.           if offset > #toShow - 11 then
  884.             users[user].channelOffsets[showTab] = #toShow - 11
  885.             offset = #toShow - 11
  886.           end
  887.           for i = #lines - offset + 2, #lines, 1 do
  888.             table.remove(toShow, #toShow)
  889.           end
  890.  
  891.           -- 2.2. Show 'em all
  892.           for i = 12, 1, -1 do
  893.             local line = toShow[#toShow + i - 12]
  894.             line = line or {}
  895.             line.nick = line.nick or ""
  896.             line.msg = line.msg or ""
  897.             local nick = surface.objects["chat.text.lines." .. i .. ".nick"]
  898.             local msg = surface.objects["chat.text.lines." .. i .. ".msg"]
  899.             if getLineLen(line.nick) > 16 then
  900.               line.nick = subLine(line.nick, 1, 15) .. "…"
  901.             end
  902.             if nick.getText() ~= line.nick then
  903.               nick.setText(line.nick)
  904.             end if msg.getText() ~= line.msg then
  905.               msg.setText(line.msg)
  906.             end
  907.           end
  908.  
  909.  
  910.           -- 3. TOPIC
  911.           local topic = channels[showTab].topic
  912.           if getLineLen(topic) > 66 then
  913.             topic = subLine(topic, 1, 65) .. "…"
  914.           end
  915.           if surface.objects["chat.text.topic"].getText() ~= topic then
  916.             surface.objects["chat.text.topic"].setText(topic)
  917.           end
  918.  
  919.  
  920.           -- 4. USERLIST
  921.           local users = channels[showTab].users
  922.           local i = 1
  923.           for nick, prefix in pairs(users) do
  924.             if i > 14 then break end
  925.             local name = PREFIXES[prefix] .. nick
  926.             if getLineLen(name) > 16 then
  927.               name = subLine(name)
  928.             end
  929.             if surface.objects["chat.text.users." .. i].getText() ~= name then
  930.               surface.objects["chat.text.users." .. i].setText(name)
  931.             end
  932.             i = i + 1
  933.           end
  934.           for j = i, 14, 1 do
  935.             if surface.objects["chat.text.users." .. j].getText() ~= "" then
  936.               surface.objects["chat.text.users." .. j].setText("")
  937.             end
  938.           end
  939.  
  940.  
  941.           ::nextUser::
  942.         end
  943.       end
  944.     end,
  945.     function(evt, time, tick)
  946.       for user, surface in pairs(surfaces) do
  947.         local userinfo = users[user]
  948.         if not userinfo then goto nextInputUser end
  949.         local showTab = getActiveChannel(user)
  950.         if not showTab then goto nextInputUser end
  951.         local name = unicode.sub(user, 1, 16)
  952.         if surface.objects["chat.text.input.nick"].getText() ~= name then
  953.           surface.objects["chat.text.input.nick"].setText(name)
  954.         end
  955.         local prompt = userinfo.prompt[showTab] or {"", 1, 1}
  956.         local inputLine = prompt[1] .. " "
  957.         local curPos, offset = prompt[2], prompt[3]
  958.         curPos = curPos - offset + 1
  959.         inputLine = unicode.sub(inputLine, offset, offset + 48)
  960.         inputLine = unicode.sub(inputLine, 1, curPos - 1) .. "§n" .. unicode.sub(inputLine, curPos, curPos) .. "§r" .. unicode.sub(inputLine, curPos + 1)
  961.         local input = surface.objects["chat.text.input.input"]
  962.         if input.getText() ~= inputLine then
  963.           input.setText(inputLine)
  964.         end
  965.  
  966.         ::nextInputUser::
  967.       end
  968.     end,
  969.     function(evt, time, tick)
  970.       bridge.sync()
  971.     end,
  972.     function(evt, time, tick)
  973.       if tick % 600 == 0 then
  974.         saveCfg()
  975.       end
  976.     end
  977.   },
  978.   chat_stop = {
  979.     function(evt, time)
  980.       exit = true
  981.     end,
  982.     function(evt, time)
  983.       bridge.clear()
  984.       bridge.sync()
  985.     end,
  986.     function(evt, time)
  987.       saveCfg()
  988.     end
  989.   },
  990.   chat_load = {
  991.     function(evt, time)
  992.       for file in fs.list(modulesPath) do
  993.         if file:match("%.([^.]+)$") == "module" then
  994.           local module = file:match("^[^.]+")
  995.           storage.module = {}
  996.           local moduleEnv = setmetatable(env, {__index = _G})
  997.           moduleEnv._MODULE = module
  998.           moduleEnv._FILE = file
  999.           moduleEnv.command = command(moduleEnv)
  1000.           local chunk, reason = loadfile(fs.concat(modulesPath, file), nil, moduleEnv)
  1001.           if not chunk then
  1002.             io.stderr:write("Failed to load module \"" .. file .. "\": " .. (reason or "no reason") .. "\n")
  1003.           else
  1004.             local success, reason = xpcall(chunk, function(exception)
  1005.               return "Exception in module \"" .. file .. "\": " .. exception .. "!\n" .. debug.traceback() .. "\n"
  1006.             end)
  1007.             if not success then
  1008.               io.stderr:write(reason)
  1009.             else
  1010.               event.push("chat_loaded_module", file, os.time())
  1011.             end
  1012.           end
  1013.         end
  1014.       end
  1015.     end
  1016.   }
  1017. }
  1018.  
  1019. for eventName, hdlrs in pairs(coreHandlers) do
  1020.   for id, hdlr in pairs(hdlrs) do
  1021.     --print("Starting \"" .. eventName .. "\" listener [" .. id .. "]")
  1022.     event.listen(eventName, hdlr)
  1023.   end
  1024. end
  1025.  
  1026. print("init")
  1027. event.push("chat_init", os.time())
  1028. os.sleep(.5) -- Allow to process init
  1029.  
  1030. print("load")
  1031. event.push("chat_load", os.time())
  1032. os.sleep(.5)
  1033.  
  1034. for eventName, hdlrs in pairs(moduleHandlers) do
  1035.   for name, hdlr in pairs(hdlrs) do
  1036.     --print("Starting module \"" .. eventName .. "\" listener [" .. name .. "]")
  1037.     event.listen(eventName, hdlr)
  1038.   end
  1039. end
  1040.  
  1041. print("start")
  1042. event.push("chat_start", os.time())
  1043. os.sleep(.5)
  1044.  
  1045. local tick = 0
  1046. local upd = event.timer(.1, function()
  1047.   event.push("chat_update", os.time(), tick)
  1048.   tick = tick + 1
  1049. end, math.huge)
  1050.  
  1051. while not exit do
  1052.   os.sleep(.1)
  1053. end
  1054.  
  1055. os.sleep(.5)
  1056.  
  1057. for eventName, hdlrs in pairs(moduleHandlers) do
  1058.   for name, hdlr in pairs(hdlrs) do
  1059.     --print("Stopping module \"" .. eventName .. "\" listener [" .. name .. "]")
  1060.     event.ignore(eventName, hdlr)
  1061.   end
  1062. end
  1063.  
  1064. for eventName, hdlrs in pairs(coreHandlers) do
  1065.   for id, hdlr in pairs(hdlrs) do
  1066.     --print("Stopping \"" .. eventName .. "\" listener [" .. id .. "]")
  1067.     event.ignore(eventName, hdlr)
  1068.   end
  1069. end
  1070.  
  1071. event.cancel(upd)
  1072.  
  1073. -- vim: expandtab tabstop=2 shiftwidth=2
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement