#!/usr/bin/lua5.2 telemver = "0.3 // 2018-05-04" file = require("pl.file") io = require("io") lfs = require("lfs") os = require("os") path = require("pl.path") string = require("string") table = require("table") bad_topics = {} bad_topics["."] = true bad_topics[".."] = true bad_topics["topic"] = true -- This is, obvviously, not secure and will need to be updated username = os.getenv("USER") current_board = "" current_thread_index = nil function cat_file(filename) io.input(filename) welcome = io.read("*all") io.input(io.stdin) io.write(welcome) end function show_welcome() print("::::::::::: This is telem ver."..telemver.." :::::::::::") cat_file("/var/bbs/docs/welcome") end function show_prompt() io.write("["..current_board.."] COMMAND :> ") end function getchar() local char os.execute("/bin/stty -icanon") char = io.read(1) os.execute("/bin/stty icanon") return char end function get_threads(board) local threads = {} for topic in lfs.dir("/var/bbs/boards/"..board) do if bad_topics[topic] == nil then thread = {} thread.filename = topic thread.directory = "/var/bbs/boards/"..board.."/"..topic _, _, timestamp, thread.author = string.find(topic, "(%d+)-(%a+)") thread.timestamp = tonumber(timestamp) io.input(thread.directory.."/subject") thread.subject = io.read("*line") io.input(io.stdin) posts = get_posts(thread) thread.post_count = #posts thread.updated = 0 for _, post in ipairs(posts) do if post.timestamp > thread.updated then thread.updated = post.timestamp end end table.insert(threads, thread) end end table.sort(threads, function(x,y) return x.updated > y.updated end) return threads end function get_posts(thread) local posts = {} for reply in lfs.dir(thread.directory) do if string.sub(reply, 1,1) == "." then goto continue end if reply == "subject" then goto continue end post = {} post.filename = thread.directory .. "/" .. reply if reply == "original" then post.author = thread.author post.timestamp = thread.timestamp else _, _, timestamp, post.author = string.find(reply, "(%d+)-(%a+)") post.timestamp = tonumber(timestamp) end table.insert(posts, post) ::continue:: end table.sort(posts, function(x,y) return x.timestamp < y.timestamp end) return posts end function do_board() -- Creates a new (empty) board -- Get details print("New board name? Max 19 chars") -- TODO: Check and enforce 19 chars board = string.upper(io.read()) print("Description?") desc = io.read() -- Create directory board_dir = path.join("/home/solderpunk/bbs/boards/", board) lfs.mkdir(board_dir) -- Write topic file topic_file = path.join(board_dir, "topic") file.write(topic_file, desc) -- Update representation of BBS boards[board] = true table.insert(board_names, board) table.sort(board_names) -- Done! print("Board created.") end function do_go() print("Which board?") board = string.upper(io.read()) -- TODO: display list of boards only if no board entered if board == "" then do_list() elseif boards[board] == nil then print("No such board") else current_board = board end end function do_help() cat_file("/var/bbs/docs/help") end function do_help2() cat_file("/var/bbs/docs/help2") end function do_unimplemented() print("Sorry, this command is not (yet) implemented!") end function do_list() for _,b in pairs(board_names) do posts = -3 -- Don't want to count "topic" file or "." or ".." for topic in lfs.dir("/var/bbs/boards/"..b) do posts = posts +1 end print(string.format("%s [%d posts]", b, posts)) end end function do_messages() if current_board == "" then print("Not at any board") else threads = get_threads(current_board) for i, thread in ipairs(threads) do print(tostring(i), os.date("%x %H:%M", thread.timestamp), thread.author, thread.subject) --, tostring(thread.post_count)) end end current_thread_index = threads end function do_new() if current_board == "" then print("Not at any board") return end -- Get subject for new thread print("Subject?") subject = io.read() -- Save body of post to temp file filename = os.tmpname() os.execute("$EDITOR " .. filename) -- TODO: show and confirm -- Make thread dir timestamp = tostring(os.time()) thread_dir = timestamp .. "-" .. username thread_path = path.join("/home/solderpunk/bbs/boards/", current_board, thread_dir) lfs.mkdir(thread_path) -- Write subject file file.write(path.join(thread_path, "subject"), subject) -- Move post file post_file = thread_dir -- first post and thread directory names are the same! newpath = path.join(thread_path, post_file) ret, str = file.move(filename, newpath) if not ret then print(str) end -- Done! print("Post submitted.") end -- Type stuff below function do_type_first() current_thread_index = 1 do_type_show_post(current_thread_index) print(string.format("Viewing post %d of %d in thread", current_thread_index, #current_thread_posts)) end function do_type_next() if current_thread_index ~= #current_thread_posts then current_thread_index = current_thread_index + 1 end do_type_show_post(current_thread_index) print(string.format("Viewing post %d of %d in thread", current_thread_index, #current_thread_posts)) end function do_type_prev() if current_thread_index ~= 1 then current_thread_index = current_thread_index - 1 end do_type_show_post(current_thread_index) print(string.format("Viewing post %d of %d in thread", current_thread_index, #current_thread_posts)) end function do_type_reply() filename = os.tmpname() os.execute("$EDITOR " .. filename) timestamp = tostring(os.time()) newfilename = timestamp .. "-" .. username newpath = path.join(thread.directory, newfilename) ret, str = file.move(filename, newpath) if not ret then print(str) end current_thread_posts = get_posts(thread) current_thread_index = #current_thread_posts do_type_show_post(current_thread_index) end type_dispatch = {} type_dispatch["f"] = do_type_first type_dispatch["n"] = do_type_next type_dispatch["p"] = do_type_prev type_dispatch["r"] = do_type_reply type_dispatch["d"] = function() return end function do_type() print("Which thread?") local thread_id = string.upper(io.read()) thread = current_thread_index[tonumber(thread_id)] print("I've been asked to type the thread: " .. thread.subject) current_thread_subject = thread.subject current_thread_posts = get_posts(thread) current_thread_index = #current_thread_posts do_type_show_post(current_thread_index) print(string.format("Viewing post %d of %d in thread", current_thread_index, #current_thread_posts)) -- for i, post in ipairs(posts) do -- print(tostring(i), os.date("%x %H:%M", post.timestamp), post.author) --cat_file(post.filename) -- end repeat show_type_prompt() c = getchar() io.write("\n") if type_dispatch[c] == nil then print("Eh?") else type_dispatch[c]() end until c == "d" end function show_type_prompt() io.write("[f]irst, [n]ext, [p]rev, [r]eply, [d]one >") end function do_type_show_post(index) local post = current_thread_posts[index] print("SUBJECT: " .. current_thread_subject) print("AUTHOR: " .. post.author) print("POSTED: " .. os.date("%H:%M %B %d, %Y", post.timestamp)) print("--------------------------------") cat_file(post.filename) print("--------------------------------") end function do_quit() print("Goodbye!") end function do_rules() cat_file("/var/bbs/docs/rules") end function do_stub() print("Stub!") end function do_who() os.execute("/usr/bin/w") end -- MAIN PROGRAM BODY BELOW show_welcome() boards, board_names = {}, {} for board in lfs.dir("/var/bbs/boards") do if string.sub(board, 1, 1) ~= "." then boards[board] = true table.insert(board_names, board) end end table.sort(board_names) message_index = {} -- Build dispatch table mapping chars to functions dispatch = {} dispatch["h"] = do_help dispatch["g"] = do_go dispatch["l"] = do_list dispatch["m"] = do_messages dispatch["q"] = do_quit dispatch["t"] = do_type dispatch["?"] = do_help2 dispatch["!"] = do_rules dispatch["w"] = do_who dispatch["s"] = do_unimplemented dispatch["="] = do_unimplemented dispatch["M"] = do_unimplemented dispatch["n"] = do_new dispatch["r"] = do_unimplemented dispatch["b"] = do_board dispatch["c"] = do_unimplemented -- Infinite loop of command dispatch repeat show_prompt() c = getchar() io.write("\n") if dispatch[c] == nil then print("What?") else dispatch[c]() end until c == "q"