Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Copyright 2016-2017 Fingercomp
- -- Licensed under the Apache License, Version 2.0 (the "License");
- -- you may not use this file except in compliance with the License.
- -- You may obtain a copy of the License at
- -- http://www.apache.org/licenses/LICENSE-2.0
- -- Unless required by applicable law or agreed to in writing, software
- -- distributed under the License is distributed on an "AS IS" BASIS,
- -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- -- See the License for the specific language governing permissions and
- -- limitations under the License.
- local com = require("component")
- local event = require("event")
- local unicode = require("unicode")
- local fs = require("filesystem")
- local text = require("text")
- local srl = require("serialization")
- local modulesPath = "/usr/lib/chat-modules/"
- local env = {}
- local config = "/etc/opg-chat.json"
- local exit = false
- 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"))
- 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")
- local guid
- if openos == "1.6" then
- local success
- success, guid = pcall(require, "uuid")
- if not success then
- guid = require("guid")
- end
- else
- guid = {
- toHex = function(n)
- if type(n) ~= 'number' then
- return nil, string.format("toHex only converts numbers to strings, %s is not a string, but a %s", tostring(n), type(n))
- end
- if n == 0 then
- return '0'
- end
- local hexchars = "0123456789abcdef"
- local result = ""
- local prefix = "" -- maybe later allow for arg to request 0x prefix
- if n < 0 then
- prefix = "-"
- n = -n
- end
- while n > 0 do
- local next = math.floor(n % 16) + 1 -- lua has 1 based array indices
- n = math.floor(n / 16)
- result = hexchars:sub(next, next) .. result
- end
- return prefix .. result
- end,
- next = function()
- -- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
- -- 8-4-4-4-12
- local sets = {8, 4, 4, 4, 12}
- local result = ""
- local i
- for _,set in ipairs(sets) do
- if result:len() > 0 then
- result = result .. "-"
- end
- for i = 1,set do
- result = result .. guid.toHex(math.random(0, 15))
- end
- end
- return result
- end
- }
- end
- event.push = event.push or require("computer").pushSignal
- local function reqcom(componentName, req, msg)
- if not com.isAvailable(componentName) then
- if req then
- io.stderr:write((msg or "No such component: " .. componentName .. "!") .. "\n")
- os.exit(-1)
- else
- local _ = msg and io.stderr:write(msg .. "\n")
- _ = nil
- return setmetatable({}, {
- __tostring = function(self)
- return "This is a dummy component"
- end,
- __index = function(self, k)
- if k == "address" then
- return guid.next()
- elseif k == "slot" then
- return -1
- elseif k == "type" then
- return componentName
- else
- return function()
- return
- end
- end
- end
- }), false
- end
- end
- return com[componentName], true
- end
- if not fs.exists("/usr/lib/json.lua") then
- local inet = reqcom("internet", true, "This program needs an internet card to install json lib!")
- if not fs.exists("/usr/lib") then
- fs.makeDirectory("/usr/lib")
- end
- local request = inet.request("http://regex.info/code/JSON.lua")
- local file = io.open("/usr/lib/json.lua", "w")
- while true do
- local chunk = request.read()
- if not chunk then break end
- file:write(chunk)
- end
- file:close()
- end
- local bridge = reqcom("openperipheral_bridge", true, "This program needs an Openperipheral bridge to work!")
- local json = require("json")
- if not fs.exists(config) then
- local f = io.open(config, "w")
- f:write(json:encode_pretty({
- server = "%SERVER%",
- admins = {"Fingercomp"},
- main_channel = "#main",
- net = {
- enabled = true,
- modem_strength = 400,
- ports = {
- ["6667"] = true,
- ["6666"] = {"244d"}
- },
- ping = {
- enabled = true,
- interval = 180,
- timeout = 180
- }
- },
- users = {},
- max_chan_lines = 750
- }))
- f:close()
- print("No configuration file found, created a new one. Path to the config: " .. config)
- print("Edit the settings and relaunch the program.")
- return 0
- end
- -- Let's load the config here to be sure the program
- -- can access it if I'd need it somewhere in program init
- local cfg = {}
- do
- local f = io.open(config, "r")
- local all = f:read("*a")
- f:close()
- cfg = json:decode(all)
- if not cfg.main_channel:match("^#%w[%w%._]*$") then
- io.stderr:write("invalid main channel name, expected \"^#%w[%w%._]*$\". Fix your configuration file.")
- end
- end
- local surfaces = {}
- local NORMAL = 0x0
- local VOICE = 0x1
- local HALFOP = 0x2
- local OP = 0x4
- local ADMIN = 0x8
- local SERVER = 0x10
- local PREFIXES = {
- [NORMAL] = "",
- [VOICE] = "§e+§f",
- [HALFOP] = "§2%§f",
- [OP] = "§a@§f"
- }
- local notifications = {
- join_chan = {pattern = "§6%s§f joined %s", nick = "§2-->"},
- part_chan = {pattern = "§6%s§f left %s (%s)", nick = "§4<--"},
- quit = {pattern = "§6%s§f quit the server (%s)", nick = "§4<--"},
- pm = {pattern = "§3%s§6 → §3%s§f: %s", nick = "§3--"},
- topic = {pattern = "§6%s§f changed topic to: \"%s\"", nick = "§5**"},
- mode = {pattern = "§6%s§f set modes [%s %s]", nick = "§5**"}
- }
- local modes = {}
- local users = {}
- local channels = {}
- local codePtn = "§[%xoklmn]"
- local function band(...)
- local bit32 = bit32 or require("bit32")
- return bit32.band(...)
- end
- local function isin(tbl, value)
- for k, v in pairs(tbl) do
- if v == value then
- return true, k
- end
- end
- return false
- end
- local function copy(tbl)
- if type(tbl) ~= "table" then
- return tbl
- end
- local result = {}
- for k, v in pairs(tbl) do
- result[k] = copy(v)
- end
- return result
- end
- local function stripCodes(line)
- return line:gsub(codePtn, "")
- end
- local function getLineLen(line)
- return unicode.len(stripCodes(line))
- end
- local function subLine(line, p1, p2)
- local result = {}
- local code = ""
- for i = 1, unicode.len(line), 1 do
- local prev, sym, nxt = unicode.sub(line, i - 1, i - 1), unicode.sub(line, i, i), unicode.sub(line, i + 1, i + 1)
- if prev and (prev .. sym):match(codePtn) then
- code = prev .. sym
- elseif not (sym .. nxt):match(codePtn) then
- table.insert(result, code .. sym)
- code = ""
- end
- end
- for i = p2 + 1, #result, 1 do
- table.remove(result)
- end
- for i = 1, p1 - 1, 1 do
- table.remove(result, 1)
- end
- return table.concat(result, "")
- end
- local function wrap(line, width)
- local result = {}
- for i = 1, getLineLen(line), width do
- local wrappedLine = text.trim(subLine(line, i, i + width - 1))
- if wrappedLine ~= "" then
- table.insert(result, wrappedLine)
- end
- end
- return result
- end
- local function getLevel(chan, user)
- local level = NORMAL
- if not users[user] then return level end
- if cfg.server == user then
- level = level + SERVER
- end
- if isin(cfg.admins, user) then
- level = level + ADMIN
- end
- if not channels[chan] or not channels[chan].users[user] then
- return level
- end
- return level + channels[chan].users[user]
- end
- local function checkLevel(chan, user, levels, any)
- local proceed = true
- local userLevel = getLevel(chan, user)
- for _, level in pairs(levels) do
- if band(userLevel, level) == level then
- if any then
- return true
- end
- else
- proceed = false
- end
- end
- return proceed
- end
- function env.apcall(func, ...)
- local data = {pcall(func, ...)}
- if data[1] then
- return true, table.unpack(data, 2)
- end
- local reason = data[2]
- reason = reason:match("^.+:%d+:%s(.+)$")
- if reason then
- return false, reason, table.unpack(data, 3)
- end
- return false, table.unpack(data, 2)
- end
- local function addObject(surface, name, func, ...)
- checkArg(1, surface, "table")
- checkArg(2, name, "string", "nil")
- checkArg(3, func, "string")
- local args = {...}
- local reason
- if name then
- surface.objects[name], reason = surface.surface[func](table.unpack(args))
- else
- surface.objects.insert(surface.surface[func](table.unpack(args)))
- end
- if reason then
- print(reason)
- end
- surface.objects[name].setUserdata({name = name})
- return surface.objects[name]
- end
- local function drawChat(surface)
- addObject(surface, "chat.box.chat", "addBox", 5, 55, 400, 120, 0x282828, .8)
- addObject(surface, "chat.box.topic", "addBox", 5, 45, 400, 11, 0x404040, .8)
- addObject(surface, "chat.box.userlist", "addBox", 410, 35, 100, 150, 0x282828, .8)
- addObject(surface, "chat.box.input", "addBox", 5, 175, 400, 10, 0x404040, .8)
- addObject(surface, "chat.line.nick", "addLine", {x=105, y=55}, {x=105,y=185}, 0x20afff, .8)
- addObject(surface, "chat.line.input", "addLine", {x=5, y=175}, {x=405,y=175}, 0x20afff, .8)
- addObject(surface, "chat.line.topic", "addLine", {x=5, y=55}, {x=405, y=55}, 0x20afff, .8)
- addObject(surface, "chat.line.userlist", "addLine", {x=410, y=45}, {x=510, y=45}, 0x20afff, .8)
- for i = 1, 9, 1 do
- local start = (i - 1) * 45 + 5
- 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)
- 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)
- local chanText = addObject(surface, "chat.text.chans." .. i, "addText", start+2, 37, "", 0xffffff)
- end
- addObject(surface, "chat.text.userlist", "addText", 412, 37, "Users:", 0x20afff)
- for i = 1, 14 do
- local start = (i - 1) * 10 + 47
- addObject(surface, "chat.text.users." .. i, "addText", 412, start, "", 0xffffff)
- end
- for i = 1, 12 do
- local start = (i - 1) * 10 + 57
- addObject(surface, "chat.text.lines." .. i .. ".nick", "addText", 7, start, "", 0xffffff)
- addObject(surface, "chat.text.lines." .. i .. ".msg", "addText", 107, start, "", 0xffffff)
- end
- addObject(surface, "chat.text.input.nick", "addText", 7, 177, "", 0xffffff)
- addObject(surface, "chat.text.input.input", "addText", 107, 177, "", 0xd3d3d3)
- addObject(surface, "chat.text.topic", "addText", 7, 47, "", 0xffffff)
- end
- local function truncate(chan)
- while #channels[chan].lines > cfg.max_chan_lines do
- local ntcs, msgs = 0, 0
- for k, v in pairs(channels[chan].lines) do
- if (v.notify and v[1] or v[3]) ~= "all" then
- ntcs = ntcs + 1
- else
- msgs = msgs + 1
- end
- end
- local rmMsg = msgs > ntcs
- for i = 1, #channels[chan].lines, 1 do
- local inV = channels[chan].lines[i]
- if (inV.notify and inV[1] or inV[3]) ~= "all" and not rmMsg or
- (inV.notify and inV[1] or inV[3]) == "all" and rmMsg then
- table.remove(channels[chan].lines, i)
- break
- end
- end
- end
- end
- local function createChannel(chan, nick)
- checkArg(1, chan, "string")
- checkArg(2, nick, "string")
- assert(users[nick], "no such nickname")
- assert(chan:sub(1, 1) == "#", "not a channel")
- assert(chan:match("^#%w[%w%._-]*$"), "invalid chars in chan name")
- channels[chan] = {
- info = {
- ["creation-date"] = os.date("%Y-%m-%d %H:%M:%S")
- },
- users = {
- [nick] = NORMAL
- },
- lines = {},
- modes = {},
- topic = "",
- banned = {},
- exempt = {}
- }
- table.insert(users[nick].channels, chan)
- event.push("chat_event_createChannel", os.time(), chan, nick)
- end
- local function addUser(user, isNetUser)
- checkArg(1, user, "string")
- checkArg(2, isNetUser, "boolean", "nil")
- assert(not users[user], "user already exists")
- cfg.users[user] = cfg.users[user] or {
- pass = ""
- }
- users[user] = {
- channels = {},
- modes = {},
- prompt = {},
- history = {},
- tabStart = 1,
- currentTab = 1,
- shown = true,
- channelOffsets = {},
- cfg = cfg.users[user],
- net = isNetUser or false
- }
- end
- local function join(chan, user)
- checkArg(1, chan, "string")
- checkArg(2, user, "string")
- assert(chan:sub(1, 1) == "#", "not a channel")
- assert(users[user], "no such nickname")
- assert(not isin(users[user].channels, chan), "already in the channel")
- if not channels[chan] then
- createChannel(chan, user)
- else
- channels[chan].users[user] = NORMAL
- table.insert(users[user].channels, chan)
- end
- users[user].channelOffsets[chan] = 1
- users[user].prompt[chan] = {"", 1, 1}
- users[user].history[chan] = {pos = 0}
- end
- local function part(chan, user)
- checkArg(1, chan, "string")
- checkArg(2, user, "string")
- assert(channels[chan], "no such channel")
- assert(users[user], "no such nickname")
- assert(channels[chan].users[user], "user is not in the channel")
- channels[chan].users[user] = nil
- local _, pos = isin(users[user].channels, chan)
- table.remove(users[user].channels, pos)
- end
- local function sendMsgChan(chan, nick, msg, rec)
- checkArg(1, chan, "string")
- checkArg(2, nick, "string")
- checkArg(3, msg, "string")
- checkArg(4, rec, "table", "nil")
- assert(chan:sub(1, 1) == "#", "not a channel")
- assert(channels[chan], "no such channel")
- assert(users[nick], "no such nickname")
- local date = os.date("%Y-%m-%d %H:%M:%S")
- rec = rec or "all"
- table.insert(channels[chan].lines, {date = date, level = channels[chan].users[nick], nick, msg, rec})
- truncate(chan)
- event.push("chat_event_msg", os.time(), chan, nick, msg, rec == "all" or #rec, table.unpack(rec == "all" and {rec} or rec))
- end
- local function sendNotifyChan(chan, notify, parts, rec)
- checkArg(1, chan, "string")
- checkArg(2, notify, "string")
- checkArg(3, parts, "table")
- checkArg(4, rec, "table", "nil")
- assert(chan:sub(1, 1) == "#", "not a channel")
- assert(channels[chan], "no such channel")
- assert(notifications[notify], "no such notification")
- local date = os.date("%Y-%m-%d %H:%M:%S")
- rec = rec or "all"
- table.insert(channels[chan].lines, {date = date, notify = {notify, parts}, rec})
- truncate(chan)
- 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))
- end
- local function sendPM(addressee, user, msg)
- checkArg(1, addressee, "string")
- checkArg(2, user, "string")
- checkArg(3, msg, "string")
- assert(users[addressee], "no such user")
- assert(users[user], "no such nickname")
- sendNotifyChan(cfg.main_channel, "pm", {user, addressee, msg}, {user, addressee})
- event.push("chat_event_pm", os.time(), user, addressee, msg)
- end
- modes.o = function(chan, user, set, arg)
- if not arg then return false end
- assert(channels[chan].users[arg], "no such user")
- assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true), "no permission")
- local was = channels[chan].users[arg]
- channels[chan].users[arg] = set and OP or NORMAL
- if was ~= channels[chan].users[arg] then
- return true
- end
- end
- modes.h = function(chan, user, set, arg)
- if not arg then return false end
- assert(channels[chan].users[arg], "no such user")
- assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true) or checkLevel(chan, user, {HALFOP}, true) and user == arg, "no permission")
- local was = channels[chan].users[arg]
- channels[chan].users[arg] = set and HALFOP or NORMAL
- if was ~= channels[chan].users[arg] then
- return true
- end
- end
- modes.v = function(chan, user, set, arg)
- if not arg then return false end
- assert(channels[chan].users[arg], "no such user")
- assert(checkLevel(chan, user, {OP, ADMIN, SERVER}, true) or checkLevel(chan, user, {VOICE, HALFOP}, true) and user == arg, "no permission")
- local was = channels[chan].users[arg]
- channels[chan].users[arg] = set and VOICE or NORMAL
- if was ~= channels[chan].users[arg] then
- return true
- end
- end
- local function togglableMode(mode, level, any)
- checkArg(1, mode, "string")
- checkArg(2, level, "table")
- checkArg(3, soft, "boolean", "nil")
- any = any or true
- return function(chan, user, set, arg)
- assert(checkLevel(chan, user, level, any), "no permission")
- if set and not isin(channels[chan].modes, mode) then
- table.insert(channels[chan].modes, mode)
- else
- local _, pos = isin(channels[chan].modes, mode)
- if pos then
- table.remove(channels[chan].modes, pos)
- else
- return false
- end
- end
- return true
- end
- end
- modes.t = togglableMode("t", {OP, ADMIN, SERVER})
- modes.m = togglableMode("m", {HALFOP, OP, ADMIN, SERVER})
- local function setMode(chan, user, mode, arg)
- checkArg(1, chan, "string")
- checkArg(2, user, "string")
- checkArg(3, mode, "string")
- checkArg(4, arg, "string", "nil")
- assert(channels[chan], "no such channel")
- assert(mode:match("^[+-].$"), "wrong mode")
- local set = mode:sub(1, 1) == "+"
- mode = mode:sub(2)
- assert(modes[mode], "unknown mode")
- local success = modes[mode](chan, user, set, arg)
- if success then
- local modeStr = (set and "+" or "-") .. mode .. (arg and " " .. arg or "")
- sendNotifyChan(chan, "mode", {user, chan, modeStr})
- end
- end
- local function joinN(chan, user)
- join(chan, user)
- sendNotifyChan(chan, "join_chan", {user, chan})
- event.push("chat_event_join", os.time(), chan, user)
- end
- local function partN(chan, user, reason)
- reason = reason or ""
- part(chan, user)
- sendNotifyChan(chan, "part_chan", {user, chan, reason})
- event.push("chat_event_part", os.time(), chan, user, reason)
- end
- local function quitN(user, reason)
- local chans = users[user].channels
- reason = reason or ""
- for i = #chans, 1, -1 do
- local chan = chans[i]
- part(chan, user)
- sendNotifyChan(chan, "quit", {user, reason})
- end
- users[user] = nil
- event.push("chat_event_quit", os.time(), user, reason, table.unpack(chans))
- end
- local function sendMsgChanN(chan, user, msg)
- if isin(channels[chan].modes, "m") and not checkLevel(chan, user, {VOICE, HALFOP, OP}, true) then
- sendPM(user, cfg.server, "The channel is moderated")
- return -1
- end
- if isin(channels[chan].banned, user) and not isin(channels[chan].exempt, user) and not checkLevel(chan, user, {HALFOP, OP, ADMIN, SERVER}) then
- sendPM(user, cfg.server, "You are banned from the channel")
- return -1
- end
- local success, reason = env.apcall(sendMsgChan, chan, user, msg)
- if not success then
- sendPM(user, cfg.server, "Could not send message: " .. reason)
- end
- end
- local function getActiveChannel(user)
- local active = users[user].currentTab
- return users[user].channels[active] or false
- end
- local moduleHandlers = {}
- local commands = {}
- local storage = {}
- env.getActiveChannel = getActiveChannel
- env.createChannel = createChannel
- env.addUser = addUser
- env.join = join
- env.part = part
- env.sendMsgChan = sendMsgChan
- env.sendNotifyChan = sendNotifyChan
- env.sendPM = sendPM
- env.joinN = joinN
- env.partN = partN
- env.quitN = quitN
- env.sendMsgChanN = sendMsgChanN
- env.addObject = addObject
- env.bridge = bridge
- env.surfaces = surfaces
- env.users = users
- env.channels = channels
- env.commands = commands
- env.isin = isin
- env.cfg = cfg
- env.setMode = setMode
- env.modes = modes
- env.getLevel = getLevel
- env.checkLevel = checkLevel
- env.togglableMode = togglableMode
- env.storage = storage
- env.reqcom = reqcom
- env.copy = copy
- env._MODULE = ""
- env._FILE = ""
- env.NORMAL = NORMAL
- env.VOICE = VOICE
- env.HALFOP = HALFOP
- env.OP = OP
- env.ADMIN = ADMIN
- env.SERVER = SERVER
- env.PREFIXES = PREFIXES
- function env.addListener(eventName, name, func)
- checkArg(1, eventName, "string")
- checkArg(2, name, "string")
- checkArg(3, func, "function")
- if moduleHandlers[eventName] and moduleHandlers[eventName][name] then
- assert(false, "ununique name!")
- end
- moduleHandlers[eventName] = moduleHandlers[eventName] or {}
- moduleHandlers[eventName][name] = func
- end
- function env.delListener(eventName, name)
- checkArg(1, eventName, "string")
- checkArg(2, name, "string")
- if moduleHandlers[eventName][name] then
- event.ignore(eventName, moduleHandlers[eventName][name])
- moduleHandlers[eventName][name] = nil
- end
- end
- local function cmdWrapper(cmdInfo)
- return function(evt, chan, user, raw, cmd, ...)
- if checkLevel(chan, user, cmdInfo.level, true) then
- cmdInfo.func(evt, chan, user, raw, cmd, ...)
- else
- sendPM(user, cfg.server, "no permission")
- end
- end
- end
- local function command(setEnv)
- return function(args)
- checkArg(1, args, "table")
- local name, level, help, doc, aliases, func = args.name, args.level, args.help, args.doc, args.aliases, args.func
- local errorPattern = "\"%s\": %s expected, %s given"
- assert(type(name) == "string", errorPattern:format("name", "string", type(name)))
- assert(isin({"table", "number"}, type(level)), errorPattern:format("level", "table or number", type(level)))
- assert(isin({"nil", "string"}, type(help)), errorPattern:format("help", "string or nil", type(help)))
- assert(isin({"nil", "string"}, type(doc)), errorPattern:format("doc", "string or nil", type(doc)))
- assert(isin({"table", "nil"}, type(aliases)), errorPattern:format("aliases", "table or nil", type(aliases)))
- assert(type(func) == "function", errorPattern:format("func", "function", type(func)))
- if type(level) == "number" then
- local levels = {NORMAL, VOICE, HALFOP, OP, ADMIN, SERVER}
- local _, pos = isin(levels, level)
- assert(pos, "wrong level")
- for i = 1, pos - 1, 1 do
- table.remove(levels, 1)
- end
- level = levels
- end
- commands[name] = {level = level, help = help, doc = doc, aliases = aliases, func = func}
- local cmds = {name, table.unpack(aliases or {})}
- for _, cmd in pairs(cmds) do
- env.addListener("chat_slash_cmd_" .. cmd, setEnv._MODULE .. ".commands." .. name .. "." .. cmd, cmdWrapper(commands[name]))
- end
- end
- end
- function env.help(user, cmd)
- checkArg(1, user, "string")
- checkArg(2, cmd, "string")
- assert(users[user], "no such user")
- assert(commands[cmd], "no such command")
- sendPM(cfg.server, user, "Help (" .. cmd .. "): " .. (commands[cmd].help or ""))
- if commands[cmd].doc then
- local docStr = commands[cmd].doc
- local doc = {""}
- for i = 1, unicode.len(docStr), 1 do
- local sym = unicode.sub(docStr, i, i)
- if sym == "\n" then
- doc[#doc+1] = ""
- else
- doc[#doc] = doc[#doc] .. sym
- end
- end
- for _, line in ipairs(doc) do
- sendPM(user, cfg.server, "> " .. line)
- end
- end
- end
- local function saveCfg()
- local content = json:encode_pretty(cfg)
- local f = io.open(config, "r")
- local backup = io.open(config .. ".backup", "w")
- backup:write(f:read("*a"))
- backup:close()
- f:close()
- f = io.open(config, "w")
- f:write(content)
- f:close()
- end
- local coreHandlers = {
- chat_init = {
- function(evt, time)
- addUser(cfg.server)
- join(cfg.main_channel, cfg.server)
- channels[cfg.main_channel].users[cfg.server] = OP
- bridge.clear()
- for _, user in pairs(bridge.getUsers()) do
- user = user.name
- surfaces[user] = {surface = bridge.getSurfaceByName(user)}
- surfaces[user].objects = {}
- drawChat(surfaces[user])
- end
- end
- },
- chat_start = {
- function(evt, time)
- for user in pairs(surfaces) do
- addUser(user)
- joinN(cfg.main_channel, user)
- end
- end
- },
- glasses_attach = {
- function(evt, addr, user, uuid)
- surfaces[user] = {surface = bridge.getSurfaceByName(user)}
- surfaces[user].surface.clear()
- surfaces[user].objects = {}
- drawChat(surfaces[user])
- if not users[user] then
- addUser(user)
- end
- joinN(cfg.main_channel, user)
- end
- },
- glasses_detach = {
- function(evt, addr, user, uuid)
- local _ = surfaces[user] and surfaces[user].surface and surfaces[user].surface.clear()
- surfaces[user] = nil
- if users[user] then
- quitN(user)
- end
- end
- },
- chat_update = {
- function(evt, time, tick)
- if tick % 5 == 0 then
- for user, surface in pairs(surfaces) do
- local userinfo = users[user]
- if not userinfo then goto nextUser end
- if not userinfo.shown then goto nextUser end
- -- 1. TABS
- -- 1.1. Set tabs
- local chans = userinfo.channels
- chans = table.pack(table.unpack(chans, userinfo.tabStart, userinfo.tabStart + 10))
- for i, chan in ipairs(chans) do
- if #chan > 6 then
- chanLabel = chan:sub(1, 5) .. "…"
- else
- chanLabel = chan
- end
- local textObj = surface.objects["chat.text.chans." .. i]
- if textObj.getText() ~= chanLabel then
- textObj.setText(chanLabel)
- local userdata = textObj.getUserdata() or {}
- userdata.chan = chan
- textObj.setUserdata(userdata)
- end
- surface.objects["chat.poly.chans." .. i].setVisible(true)
- surface.objects["chat.poly.chans." .. i .. ".active"].setVisible(false)
- end
- -- 1.2. Hide unused tabs
- if #userinfo.channels < 10 then
- local start = #userinfo.channels + 1
- for i = start, 9, 1 do
- surface.objects["chat.poly.chans." .. i].setVisible(false)
- local userdata = surface.objects["chat.text.chans." .. i].getUserdata() or {}
- userdata.chan = nil
- surface.objects["chat.text.chans." .. i].setUserdata(userdata)
- surface.objects["chat.text.chans." .. i].setText("")
- surface.objects["chat.poly.chans." .. i .. ".active"].setVisible(false)
- end
- end
- -- 1.3. Select active tab
- local showTab = getActiveChannel(user)
- if not showTab then
- goto nextUser
- end
- local active = userinfo.currentTab
- surface.objects["chat.poly.chans." .. active .. ".active"].setVisible(true)
- -- 2. MSG AREA
- -- 2.1. Get lines to show
- local toShow = {}
- local lines = channels[showTab].lines
- local offset = userinfo.channelOffsets[showTab]
- for i = 1, #lines, 1 do
- local line = lines[i]
- local date, nick, msg, rec, notify = line.date, nil, nil, nil, line.notify
- if not notify then
- nick, msg, rec = line[1], line[2], line[3]
- else
- rec = line[1]
- end
- if rec == "all" or isin(rec, user) then
- local name = ""
- if not notify then
- local userPrefix = PREFIXES[line.level or NORMAL]
- name = (userPrefix or "") .. nick
- else
- msg = notifications[notify[1]].pattern:format(table.unpack(notify[2]))
- name = notifications[notify[1]].nick
- end
- local msglines = wrap(msg, 49)
- if #msglines == 1 then
- table.insert(toShow, {nick = name, msg = msg})
- else
- local wrapped = {}
- for i = #msglines, 1, -1 do
- local nickText = i == 1 and name or ""
- msg = msglines[i]
- table.insert(wrapped, {nick = nickText, msg = msg})
- end
- for wrI = #wrapped, 1, -1 do
- table.insert(toShow, wrapped[wrI])
- end
- end
- end
- end
- if offset > #toShow - 11 then
- users[user].channelOffsets[showTab] = #toShow - 11
- offset = #toShow - 11
- end
- for i = #lines - offset + 2, #lines, 1 do
- table.remove(toShow, #toShow)
- end
- -- 2.2. Show 'em all
- for i = 12, 1, -1 do
- local line = toShow[#toShow + i - 12]
- line = line or {}
- line.nick = line.nick or ""
- line.msg = line.msg or ""
- local nick = surface.objects["chat.text.lines." .. i .. ".nick"]
- local msg = surface.objects["chat.text.lines." .. i .. ".msg"]
- if getLineLen(line.nick) > 16 then
- line.nick = subLine(line.nick, 1, 15) .. "…"
- end
- if nick.getText() ~= line.nick then
- nick.setText(line.nick)
- end if msg.getText() ~= line.msg then
- msg.setText(line.msg)
- end
- end
- -- 3. TOPIC
- local topic = channels[showTab].topic
- if getLineLen(topic) > 66 then
- topic = subLine(topic, 1, 65) .. "…"
- end
- if surface.objects["chat.text.topic"].getText() ~= topic then
- surface.objects["chat.text.topic"].setText(topic)
- end
- -- 4. USERLIST
- local users = channels[showTab].users
- local i = 1
- for nick, prefix in pairs(users) do
- if i > 14 then break end
- local name = PREFIXES[prefix] .. nick
- if getLineLen(name) > 16 then
- name = subLine(name)
- end
- if surface.objects["chat.text.users." .. i].getText() ~= name then
- surface.objects["chat.text.users." .. i].setText(name)
- end
- i = i + 1
- end
- for j = i, 14, 1 do
- if surface.objects["chat.text.users." .. j].getText() ~= "" then
- surface.objects["chat.text.users." .. j].setText("")
- end
- end
- ::nextUser::
- end
- end
- end,
- function(evt, time, tick)
- for user, surface in pairs(surfaces) do
- local userinfo = users[user]
- if not userinfo then goto nextInputUser end
- local showTab = getActiveChannel(user)
- if not showTab then goto nextInputUser end
- local name = unicode.sub(user, 1, 16)
- if surface.objects["chat.text.input.nick"].getText() ~= name then
- surface.objects["chat.text.input.nick"].setText(name)
- end
- local prompt = userinfo.prompt[showTab] or {"", 1, 1}
- local inputLine = prompt[1] .. " "
- local curPos, offset = prompt[2], prompt[3]
- curPos = curPos - offset + 1
- inputLine = unicode.sub(inputLine, offset, offset + 48)
- inputLine = unicode.sub(inputLine, 1, curPos - 1) .. "§n" .. unicode.sub(inputLine, curPos, curPos) .. "§r" .. unicode.sub(inputLine, curPos + 1)
- local input = surface.objects["chat.text.input.input"]
- if input.getText() ~= inputLine then
- input.setText(inputLine)
- end
- ::nextInputUser::
- end
- end,
- function(evt, time, tick)
- bridge.sync()
- end,
- function(evt, time, tick)
- if tick % 600 == 0 then
- saveCfg()
- end
- end
- },
- chat_stop = {
- function(evt, time)
- exit = true
- end,
- function(evt, time)
- bridge.clear()
- bridge.sync()
- end,
- function(evt, time)
- saveCfg()
- end
- },
- chat_load = {
- function(evt, time)
- for file in fs.list(modulesPath) do
- if file:match("%.([^.]+)$") == "module" then
- local module = file:match("^[^.]+")
- storage.module = {}
- local moduleEnv = setmetatable(env, {__index = _G})
- moduleEnv._MODULE = module
- moduleEnv._FILE = file
- moduleEnv.command = command(moduleEnv)
- local chunk, reason = loadfile(fs.concat(modulesPath, file), nil, moduleEnv)
- if not chunk then
- io.stderr:write("Failed to load module \"" .. file .. "\": " .. (reason or "no reason") .. "\n")
- else
- local success, reason = xpcall(chunk, function(exception)
- return "Exception in module \"" .. file .. "\": " .. exception .. "!\n" .. debug.traceback() .. "\n"
- end)
- if not success then
- io.stderr:write(reason)
- else
- event.push("chat_loaded_module", file, os.time())
- end
- end
- end
- end
- end
- }
- }
- for eventName, hdlrs in pairs(coreHandlers) do
- for id, hdlr in pairs(hdlrs) do
- --print("Starting \"" .. eventName .. "\" listener [" .. id .. "]")
- event.listen(eventName, hdlr)
- end
- end
- print("init")
- event.push("chat_init", os.time())
- os.sleep(.5) -- Allow to process init
- print("load")
- event.push("chat_load", os.time())
- os.sleep(.5)
- for eventName, hdlrs in pairs(moduleHandlers) do
- for name, hdlr in pairs(hdlrs) do
- --print("Starting module \"" .. eventName .. "\" listener [" .. name .. "]")
- event.listen(eventName, hdlr)
- end
- end
- print("start")
- event.push("chat_start", os.time())
- os.sleep(.5)
- local tick = 0
- local upd = event.timer(.1, function()
- event.push("chat_update", os.time(), tick)
- tick = tick + 1
- end, math.huge)
- while not exit do
- os.sleep(.1)
- end
- os.sleep(.5)
- for eventName, hdlrs in pairs(moduleHandlers) do
- for name, hdlr in pairs(hdlrs) do
- --print("Stopping module \"" .. eventName .. "\" listener [" .. name .. "]")
- event.ignore(eventName, hdlr)
- end
- end
- for eventName, hdlrs in pairs(coreHandlers) do
- for id, hdlr in pairs(hdlrs) do
- --print("Stopping \"" .. eventName .. "\" listener [" .. id .. "]")
- event.ignore(eventName, hdlr)
- end
- end
- event.cancel(upd)
- -- vim: expandtab tabstop=2 shiftwidth=2
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement