From 45b52583d52ed36c5c0b8b1f0aaf33354b795388 Mon Sep 17 00:00:00 2001 From: plugd Date: Sun, 11 Aug 2024 15:37:41 +0200 Subject: [PATCH] Board path now always on command line. --- qwikboard | 191 +++++++++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 88 deletions(-) diff --git a/qwikboard b/qwikboard index a808233..e9e8bc3 100755 --- a/qwikboard +++ b/qwikboard @@ -1,5 +1,5 @@ #!/usr/bin/env lua --- OBBS: An Offline-first BBS Utility +-- QWiKBoard: A QWK-powered message board system -- Copyright (C) 2024 plugd -- This program is free software: you can redistribute it and/or modify @@ -17,21 +17,19 @@ local posix = require "posix" -local usage = [[Usage: obbs [-c config_file] COMMAND arguments +local usage = [[Usage: qwikboard DIR COMMAND arguments -Here COMMAND is one of: +Here DIR is the path to the message board directory and COMMAND is one of: newcfg init qwkout repin nncp_handler +]] -You can also use the -c option to specify an alternative configuration -file. (The default is the file "config.lua" in the current directory.)]] - -obbs = {} -local config_file = "config.lua" +qb = {} local function load_config () + local config_file = qb.path .. "/config.lua" io.write("Loading config from file '" .. config_file .. "'... ") dofile(config_file) print("done.") @@ -64,6 +62,10 @@ function fs.exists(filename) return io.open(filename, "r") end +function fs.direxists(dirname) + return pcall(posix.dir, dirname) +end + function fs.dir(path) return posix.dir(path) end @@ -96,7 +98,7 @@ function fs.touch(filename) end function fs.mktempdir() - return posix.mkdtemp("/tmp/obbs-XXXXXX") + return posix.mkdtemp("/tmp/qwikboard-XXXXXX") end --- Message database parsing --- @@ -105,26 +107,26 @@ function Message(msg) msgs[msg.number] = msg end -local function get_next_msg_number(obbs, conf_num) - local msgnum_filename = obbs.path .. "/conferences/" .. - obbs.conferences[conf_num+1] .. ".next" +local function get_next_msg_number(conf_num) + local msgnum_filename = qb.path .. "/conferences/" .. + qb.conferences[conf_num+1] .. ".next" local mnf = assert(io.open(msgnum_filename, "r")) local next_num = tonumber(mnf:read("*all")) mnf:close() return next_num end -local function set_next_msg_number(obbs, conf_num, next_num) - local msgnum_filename = obbs.path .. "/conferences/" .. - obbs.conferences[conf_num+1] .. ".next" +local function set_next_msg_number(conf_num, next_num) + local msgnum_filename = qb.path .. "/conferences/" .. + qb.conferences[conf_num+1] .. ".next" local mnf = assert(io.open(msgnum_filename, "w")) mnf:write(next_num) mnf:close() end -local function append_new_msg(obbs, conf_num, msg) - local cf = assert(io.open(obbs.path .. "/conferences/" .. - obbs.conferences[conf_num+1] .. ".msgs", "a+")) +local function append_new_msg(conf_num, msg) + local cf = assert(io.open(qb.path .. "/conferences/" .. + qb.conferences[conf_num+1] .. ".msgs", "a+")) cf:write("\n") cf:write("Message{\n") cf:write("\tnumber=" .. msg.number .. ",\n") @@ -148,21 +150,21 @@ end local qwk = {} -function qwk.write_control(target_dir, obbs, user_name) +function qwk.write_control(target_dir, user_name) local cf = assert(io.open(target_dir .. "/CONTROL.DAT", "w")) - cf:write(obbs.name .. "\r\n") + cf:write(qb.name .. "\r\n") cf:write("\r\n") -- BBS location cf:write("\r\n") -- BBS phone number - cf:write(obbs.sysop .. "\r\n") - cf:write("12345," .. obbs.bbsid .. "\r\n") + cf:write(qb.sysop .. "\r\n") + cf:write("12345," .. qb.bbsid .. "\r\n") cf:write(os.date("%d-%m-%Y,%X") .. "\r\n") -- packet creation time cf:write(user_name .. "\r\n") cf:write("\r\n") cf:write(0 .. "\r\n") cf:write(0 .. "\r\n") -- TODO: Number of messages in packet - cf:write(#obbs.conferences-1 .. "\r\n") -- Index of final conference - for i,v in ipairs(obbs.conferences) do + cf:write(#qb.conferences-1 .. "\r\n") -- Index of final conference + for i,v in ipairs(qb.conferences) do cf:write(i-1 .. "\r\n") cf:write(v .. "\r\n") end @@ -174,27 +176,27 @@ function qwk.write_control(target_dir, obbs, user_name) cf:close() end -function qwk.write_message (target_dir, obbs) +function qwk.write_message (target_dir) local mf = assert(io.open(target_dir .. "/MESSAGES.DAT", "w")) local pkt_msg_num = 0 - mf:write(space_pad("Produced by OBBS.", 128)) + mf:write(space_pad("Produced by QWiKBoard.", 128)) - for cnum,cname in ipairs(obbs.conferences) do + for cnum,cname in ipairs(qb.conferences) do msgs = {} - dofile(obbs.path .. "/conferences/" .. cname .. ".msgs") + dofile(qb.path .. "/conferences/" .. cname .. ".msgs") for i,msg in ipairs(msgs) do pkt_msg_num = pkt_msg_num + 1 - qwk.write_message_header_block(mf, obbs, cnum, pkt_msg_num, msg) - qwk.write_message_text(mf, obbs, msg) + qwk.write_message_header_block(mf, qb, cnum, pkt_msg_num, msg) + qwk.write_message_text(mf, qb, msg) end end mf:close() end -function qwk.write_message_header_block(mf, obbs, conf_num, pkt_msg_num, msg) +function qwk.write_message_header_block(mf, conf_num, pkt_msg_num, msg) mf:write(" ") -- Message status flag mf:write(space_pad(tostring(msg.number), 7)) mf:write(msg.date) -- mm-dd-yy @@ -219,16 +221,16 @@ function qwk.write_message_header_block(mf, obbs, conf_num, pkt_msg_num, msg) mf:write(" ") -- No network tag-line present end -function qwk.write_message_text(mf, obbs, msg) +function qwk.write_message_text(mf, msg) mf:write((string.gsub(msg.text, "\n", "\xe3"))) local padding = (128 - (string.len(msg.text) % 128)) % 128 mf:write(string.rep("\0",padding)) end -function qwk.process_replies(archive_dir, obbs, user_name) +function qwk.process_replies(archive_dir, user_name) - local msg_filename = archive_dir .. "/" .. obbs.bbsid .. ".MSG" + local msg_filename = archive_dir .. "/" .. qb.bbsid .. ".MSG" if not fs.exists(msg_filename) then print("Error: MSG file not found.") @@ -238,17 +240,17 @@ function qwk.process_replies(archive_dir, obbs, user_name) local mf = assert(io.open(msg_filename, "r")) local block = mf:read(128) - if not string.find(block, obbs.bbsid .. " *") then + if not string.find(block, qb.bbsid .. " *") then print("Error: Failed to match BBSID in first block.") return false end while mf:read(0) do - qwk.process_reply(mf, obbs, user_name) + qwk.process_reply(mf, qb, user_name) end end -function qwk.process_reply(mf, obbs, user_name) +function qwk.process_reply(mf, user_name) local msg = {} mf:read(1) -- Message status (ignore for now) @@ -275,12 +277,12 @@ function qwk.process_reply(mf, obbs, user_name) end msg.text = string.gsub(msg.text, "\xe3", "\n") - msg.number = get_next_msg_number(obbs, conf_num) - set_next_msg_number(obbs, conf_num, msg.number+1) + msg.number = get_next_msg_number(qb, conf_num) + set_next_msg_number(qb, conf_num, msg.number+1) - append_new_msg(obbs, conf_num, msg) + append_new_msg(qb, conf_num, msg) - print("Processed message for conference " .. obbs.conferences[conf_num+1] .. ".") + print("Processed message for conference " .. qb.conferences[conf_num+1] .. ".") -- print("Message{") -- for k,v in pairs(msg) do @@ -295,47 +297,58 @@ local cmd = {} -- Send default configuration to stdout function cmd.newcfg () - print [[ --- OBBS Configuration File -obbs.path = "." -- Path to OBBS root + local config_file = qb.path .. "/config.lua" + + if fs.exists(config_file) then + print("Configuration file '" .. config_file .. "' already exists. Aborting.") + return false + end + + print("Writing new configuration file " .. config_file .. "...") -obbs.name = "My OBBS Name" -obbs.bbsid = "MYOBBSID" -- upper case, max 8 char -obbs.sysop = "Sysop Name" + local cf = assert(io.open(config_file, "w")) + cf:write([[ +-- QWiKBoard Configuration File -obbs.conferences = { +qb.name = "My BBS Name" +qb.bbsid = "MYBBSID" -- upper case, max 8 char +qb.sysop = "Sysop Name" + +qb.conferences = { "Announcements", "General", "Meta" } -- Ensure these point to zip and unzip on your system -obbs.zip = "/usr/bin/zip" -obbs.unzip = "/usr/bin/unzip" -]] +qb.zip = "/usr/bin/zip" +qb.unzip = "/usr/bin/unzip" +]]) + cf:close() + end -- Initialise conference directory structure function cmd.init () load_config() - if fs.exists(obbs.path .. "/conferences") or - fs.exists(obbs.path .. "/notices") then - print("One or more OBBS directories already exist. Aborting.") + if fs.exists(qb.path .. "/conferences") or + fs.exists(qb.path .. "/notices") then + print("One or more QWiKBoard directories already exist. Aborting.") return false end - fs.mkdir(obbs.path .. "/conferences") - for i,v in ipairs(obbs.conferences) do - fs.touch(obbs.path .. "/conferences/" .. v .. ".msgs") - io.open(obbs.path .. "/conferences/" .. v .. ".next", "w"):write(1) + fs.mkdir(qb.path .. "/conferences") + for i,v in ipairs(qb.conferences) do + fs.touch(qb.path .. "/conferences/" .. v .. ".msgs") + io.open(qb.path .. "/conferences/" .. v .. ".next", "w"):write(1) end - fs.mkdir(obbs.path .. "/notices") - fs.touch(obbs.path .. "/notices/hello") - fs.touch(obbs.path .. "/notices/news") - fs.touch(obbs.path .. "/notices/goodbye") + fs.mkdir(qb.path .. "/notices") + fs.touch(qb.path .. "/notices/hello") + fs.touch(qb.path .. "/notices/news") + fs.touch(qb.path .. "/notices/goodbye") end -- qwkout: generate QWK file @@ -343,7 +356,7 @@ function cmd.qwkout () load_config() if not arg[2] then - print [[Usage: obbs qwkout USER OUTFILE + print [[Usage: qwikboard DIR qwkout USER OUTFILE Here OUTFILE is is the name of the output QWK file relative to the current directory. The QWK file is traditionally named @@ -357,22 +370,22 @@ BBSID.QWK, with BBSID being the 8 character ID of the BBS.]] local work_dir = fs.mktempdir() -- CONTROL.DAT - qwk.write_control(work_dir, obbs, user_name) + qwk.write_control(work_dir, qb, user_name) -- Copy BBS welcome, news and goodbye files - fs.copy(obbs.path .. "/notices/hello", work_dir .. "/HELLO") - fs.copy(obbs.path .. "/notices/news", work_dir .. "/NEWS") - fs.copy(obbs.path .. "/notices/goodbye", work_dir .. "/GOODBYE") + fs.copy(qb.path .. "/notices/hello", work_dir .. "/HELLO") + fs.copy(qb.path .. "/notices/news", work_dir .. "/NEWS") + fs.copy(qb.path .. "/notices/goodbye", work_dir .. "/GOODBYE") -- Pack messages - qwk.write_message(work_dir, obbs) + qwk.write_message(work_dir, qb) -- Create archive in outgoing - os.execute(obbs.zip .. " -rj " .. - obbs.path .. "/" .. qwk_filename .. + os.execute(qb.zip .. " -rj " .. + qb.path .. "/" .. qwk_filename .. " " .. work_dir) fs.rmdir(work_dir, true) @@ -384,11 +397,12 @@ function cmd.repin() load_config() if not arg[2] then - print [[Usage: obbs repin INFILE USER + print [[Usage: qwikboard DIR repin INFILE USER Here INFILE is the name of the REP file to import new messages from. This is traditionally named BBSID.rep, where BBSID is -the 8 character max ID of the BBS, but obbs doesn't care about this.]] +the 8 character max ID of the BBS, but QWiKBoard doesn't care +about this.]] return end @@ -403,9 +417,9 @@ the 8 character max ID of the BBS, but obbs doesn't care about this.]] local work_dir = fs.mktempdir() fs.copy(rep_filename, work_dir .. "/repfile.rep") - os.execute(obbs.unzip .. " " .. work_dir .. "/repfile.rep -d " .. work_dir) + os.execute(qb.unzip .. " " .. work_dir .. "/repfile.rep -d " .. work_dir) - qwk.process_replies(work_dir, obbs, user_name) + qwk.process_replies(work_dir, qb, user_name) fs.rmdir(work_dir, true) @@ -423,30 +437,31 @@ be made accessible by user (node) NODENAME by adding the following to their neigh entry in the /etc/nncp.hjson file exec: { - obbs: ["/usr/bin/obbs -c /var/share/obbs/config.lua nncp"] + offline: ["/usr/local/bin/qwikboard /var/qb/ nncp_handler"] }]] + end end -- Main local function main() - if #arg > 2 and arg[1]=="-c" then - config_file = arg[2] - table.remove(arg, 1) - table.remove(arg, 1) - end - - if #arg < 1 then + if #arg < 2 then print(usage) else - if cmd[arg[1]] then - local f = cmd[arg[1]] - table.remove(arg,1) - f() + if not fs.direxists(arg[1]) then + print("Directory '" .. arg[1] .. "' does not exist. Aborting.") else - print("Unknown command '" .. arg[1] .. "'") - print(usage) + qb.path = arg[1] + table.remove(arg, 1) + if cmd[arg[1]] then + local f = cmd[arg[1]] + table.remove(arg,1) + f() + else + print("Unknown command '" .. arg[1] .. "'") + print(usage) + end end end end -- 2.20.1