Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
- --
- -- SPDX-License-Identifier: LicenseRef-CCPL
- term.clear()
- term.setCursorPos(1,1)
- local tArgs = { ... }
- -- Logging feature
- local bLogging = false
- for i = 1, #tArgs do
- if tArgs[i] == "-log" then
- bLogging = true
- table.remove(tArgs, i)
- break
- end
- end
- local logFile
- if bLogging then
- logFile = fs.open("chat_log.txt", "a")
- local oldPrint = print
- local oldWrite = write
- local function logWrite(text)
- if logFile then
- logFile.write(text)
- logFile.flush()
- end
- end
- function write(...)
- local t = table.pack(...)
- for i = 1, t.n do
- oldWrite(t[i])
- logWrite(t[i])
- end
- end
- function print(...)
- local t = table.pack(...)
- local text = table.concat(t, " ")
- oldPrint(text)
- logWrite(text .. "\n")
- end
- print("Logging enabled. Output will be written to chat_log.txt")
- end
- local function printUsage()
- local programName = arg[0] or fs.getName(shell.getRunningProgram())
- print("Usages:")
- print(programName .. " host")
- print(programName .. " join")
- end
- local sOpenedModem = nil
- local function openModem()
- for _, sModem in ipairs(peripheral.getNames()) do
- if peripheral.getType(sModem) == "modem" then
- if not rednet.isOpen(sModem) then
- rednet.open(sModem)
- sOpenedModem = sModem
- end
- return true
- end
- end
- print("No modems found.")
- return false
- end
- local function closeModem()
- if sOpenedModem ~= nil then
- rednet.close(sOpenedModem)
- sOpenedModem = nil
- end
- end
- -- Colours
- local highlightColour, textColour
- if term.isColour() then
- textColour = colours.white
- highlightColour = colours.yellow
- else
- textColour = colours.white
- highlightColour = colours.white
- end
- local sCommand = tArgs[1]
- if sCommand == "host" then
- -- "chat host"
- local sHostname = "global"
- if not openModem() then
- return
- end
- rednet.host("chat", sHostname)
- print("Hosting global chat...")
- print("0 users connected.")
- -- Load chat history
- local chatHistory = {}
- if fs.exists("chat_history.txt") then
- local f = fs.open("chat_history.txt", "r")
- for line in f.readLine do
- table.insert(chatHistory, line)
- end
- f.close()
- end
- local function saveMessage(sMessage)
- table.insert(chatHistory, sMessage)
- local f = fs.open("chat_history.txt", "a")
- f.writeLine(sMessage)
- f.close()
- end
- local tUsers = {}
- local nUsers = 0
- local function send(sText, nUserID)
- if nUserID then
- local tUser = tUsers[nUserID]
- if tUser then
- rednet.send(tUser.nID, {
- sType = "text",
- nUserID = nUserID,
- sText = sText,
- }, "chat")
- end
- else
- for nUserID, tUser in pairs(tUsers) do
- rednet.send(tUser.nID, {
- sType = "text",
- nUserID = nUserID,
- sText = sText,
- }, "chat")
- end
- end
- end
- local tPingPongTimer = {}
- local function ping(nUserID)
- local tUser = tUsers[nUserID]
- if not tUser then return end
- rednet.send(tUser.nID, {
- sType = "ping to client",
- nUserID = nUserID,
- }, "chat")
- local timer = os.startTimer(15)
- tUser.bPingPonged = false
- tPingPongTimer[timer] = nUserID
- end
- local function printUsers()
- local _, y = term.getCursorPos()
- term.setCursorPos(1, y - 1)
- term.clearLine()
- if nUsers == 1 then
- print(nUsers .. " user connected.")
- else
- print(nUsers .. " users connected.")
- end
- end
- local ok, error = pcall(parallel.waitForAny,
- function()
- while true do
- local _, timer = os.pullEvent("timer")
- local nUserID = tPingPongTimer[timer]
- if nUserID and tUsers[nUserID] then
- local tUser = tUsers[nUserID]
- if not tUser.bPingPonged then
- send("* " .. tUser.sUsername .. " has timed out")
- tUsers[nUserID] = nil
- nUsers = nUsers - 1
- printUsers()
- else
- ping(nUserID)
- end
- end
- end
- end,
- function()
- while true do
- local nSenderID, tMessage = rednet.receive("chat")
- if type(tMessage) == "table" then
- if tMessage.sType == "login" then
- local nUserID = tMessage.nUserID
- local sUsername = tMessage.sUsername
- if nUserID and sUsername then
- tUsers[nUserID] = {
- nID = nSenderID,
- nUserID = nUserID,
- sUsername = sUsername,
- }
- nUsers = nUsers + 1
- printUsers()
- -- Send chat history to new user
- for _, line in ipairs(chatHistory) do
- rednet.send(nSenderID, {
- sType = "text",
- nUserID = nUserID,
- sText = line,
- }, "chat")
- end
- send("* " .. sUsername .. " has joined the chat")
- saveMessage("* " .. sUsername .. " has joined the chat")
- ping(nUserID)
- end
- else
- local nUserID = tMessage.nUserID
- local tUser = tUsers[nUserID]
- if tUser and tUser.nID == nSenderID then
- if tMessage.sType == "logout" then
- send("* " .. tUser.sUsername .. " has left the chat")
- saveMessage("* " .. tUser.sUsername .. " has left the chat")
- tUsers[nUserID] = nil
- nUsers = nUsers - 1
- printUsers()
- elseif tMessage.sType == "chat" then
- local sMessage = tMessage.sText
- if sMessage then
- local sCommand = string.match(sMessage, "^/([a-z]+)")
- if sCommand then
- local sContent = string.sub(sMessage, #sCommand + 3)
- if sCommand == "me" then
- if #sContent > 0 then
- local msg = "* " .. tUser.sUsername .. " " .. sContent
- send(msg)
- saveMessage(msg)
- else
- send("* Usage: /me [action]", tUser.nUserID)
- end
- elseif sCommand == "nick" then
- if #sContent > 0 then
- local sOld = tUser.sUsername
- tUser.sUsername = sContent
- local msg = "* " .. sOld .. " is now known as " .. tUser.sUsername
- send(msg)
- saveMessage(msg)
- else
- send("* Usage: /nick [new name]", tUser.nUserID)
- end
- elseif sCommand == "users" then
- send("* Connected users:", tUser.nUserID)
- local sUsers = "*"
- for _, u in pairs(tUsers) do
- sUsers = sUsers .. " " .. u.sUsername
- end
- send(sUsers, tUser.nUserID)
- elseif sCommand == "help" then
- send("* Available commands:", tUser.nUserID)
- send("*/me /nick /users /msg /help /logout", tUser.nUserID)
- elseif sCommand == "msg" then
- local targetName, message = sContent:match("^(%S+)%s+(.+)$")
- if not targetName or not message then
- send("* Usage: /msg <username> <message>", tUser.nUserID)
- else
- local found = false
- for _, targetUser in pairs(tUsers) do
- if targetUser.sUsername:lower() == targetName:lower() then
- found = true
- local fromMsg = "[From " .. tUser.sUsername .. "] " .. message
- local toMsg = "[To " .. targetUser.sUsername .. "] " .. message
- send(fromMsg, targetUser.nUserID)
- send(toMsg, tUser.nUserID)
- break
- end
- end
- if not found then
- send("* User '" .. targetName .. "' not found.", tUser.nUserID)
- end
- end
- send("* Unknown command: /" .. sCommand, tUser.nUserID)
- end
- else
- local sFull = "<" .. tUser.sUsername .. "> " .. sMessage
- send(sFull)
- saveMessage(sFull)
- end
- end
- elseif tMessage.sType == "ping to server" then
- rednet.send(tUser.nID, {
- sType = "pong to client",
- nUserID = nUserID,
- }, "chat")
- elseif tMessage.sType == "pong to server" then
- tUser.bPingPonged = true
- end
- end
- end
- end
- end
- end
- )
- if not ok then printError(error) end
- for nUserID, tUser in pairs(tUsers) do
- rednet.send(tUser.nID, { sType = "kick", nUserID = nUserID }, "chat")
- end
- rednet.unhost("chat")
- closeModem()
- elseif sCommand == "join" then
- local sHostname = "global"
- local sUsername
- if fs.exists("chat_username.txt") then
- local f = fs.open("chat_username.txt", "r")
- sUsername = f.readAll()
- f.close()
- sUsername = sUsername:gsub("%s+$", "")
- print("Using saved username: " .. sUsername)
- else
- write("Enter your username: ")
- sUsername = read()
- local f = fs.open("chat_username.txt", "w")
- f.write(sUsername)
- f.close()
- end
- if not openModem() then return end
- write("Looking up " .. sHostname .. "... ")
- local nHostID = rednet.lookup("chat", sHostname)
- if not nHostID then print("Failed.") return else print("Success.") end
- local nUserID = math.random(1, 2147483647)
- rednet.send(nHostID, { sType = "login", nUserID = nUserID, sUsername = sUsername }, "chat")
- local bPingPonged = true
- local pingPongTimer = os.startTimer(0)
- local function ping()
- rednet.send(nHostID, { sType = "ping to server", nUserID = nUserID }, "chat")
- bPingPonged = false
- pingPongTimer = os.startTimer(15)
- end
- local w, h = term.getSize()
- local parentTerm = term.current()
- local titleWindow = window.create(parentTerm, 1, 1, w, 1, true)
- local historyWindow = window.create(parentTerm, 1, 2, w, h - 2, true)
- local promptWindow = window.create(parentTerm, 1, h, w, 1, true)
- historyWindow.setCursorPos(1, h - 2)
- term.clear()
- term.setTextColour(textColour)
- term.redirect(promptWindow)
- promptWindow.restoreCursor()
- local function drawTitle()
- local w = titleWindow.getSize()
- local sTitle = sUsername .. " on " .. sHostname
- titleWindow.setTextColour(highlightColour)
- titleWindow.setCursorPos(math.floor(w / 2 - #sTitle / 2), 1)
- titleWindow.clearLine()
- titleWindow.write(sTitle)
- promptWindow.restoreCursor()
- end
- local function printMessage(sMessage)
- term.redirect(historyWindow)
- print()
- if string.match(sMessage, "^%*") then
- term.setTextColour(highlightColour)
- write(sMessage)
- term.setTextColour(textColour)
- else
- local sUsernameBit = string.match(sMessage, "^<[^>]*>")
- if sUsernameBit then
- term.setTextColour(highlightColour)
- write(sUsernameBit)
- term.setTextColour(textColour)
- write(string.sub(sMessage, #sUsernameBit + 1))
- else
- write(sMessage)
- end
- end
- term.redirect(promptWindow)
- promptWindow.restoreCursor()
- end
- drawTitle()
- local ok, error = pcall(parallel.waitForAny,
- function()
- while true do
- local sEvent, timer = os.pullEvent()
- if sEvent == "timer" then
- if timer == pingPongTimer then
- if not bPingPonged then
- printMessage("Server timeout.")
- return
- else
- ping()
- end
- end
- elseif sEvent == "term_resize" then
- local w, h = parentTerm.getSize()
- titleWindow.reposition(1, 1, w, 1)
- historyWindow.reposition(1, 2, w, h - 2)
- promptWindow.reposition(1, h, w, 1)
- end
- end
- end,
- function()
- while true do
- local nSenderID, tMessage = rednet.receive("chat")
- if nSenderID == nHostID and type(tMessage) == "table" and tMessage.nUserID == nUserID then
- if tMessage.sType == "text" then
- local sText = tMessage.sText
- if sText then printMessage(sText) end
- elseif tMessage.sType == "ping to client" then
- rednet.send(nSenderID, { sType = "pong to server", nUserID = nUserID }, "chat")
- elseif tMessage.sType == "pong to client" then
- bPingPonged = true
- elseif tMessage.sType == "kick" then
- return
- end
- end
- end
- end,
- function()
- local tSendHistory = {}
- while true do
- promptWindow.setCursorPos(1, 1)
- promptWindow.clearLine()
- promptWindow.setTextColor(highlightColour)
- promptWindow.write(": ")
- promptWindow.setTextColor(textColour)
- local sChat = read(nil, tSendHistory)
- if string.match(sChat, "^/logout") then
- break
- else
- rednet.send(nHostID, { sType = "chat", nUserID = nUserID, sText = sChat }, "chat")
- table.insert(tSendHistory, sChat)
- end
- end
- end
- )
- term.redirect(parentTerm)
- local _, h = term.getSize()
- term.setCursorPos(1, h)
- term.clearLine()
- term.setCursorBlink(false)
- if not ok then printError(error) end
- rednet.send(nHostID, { sType = "logout", nUserID = nUserID }, "chat")
- closeModem()
- print("Disconnected.")
- else
- printUsage()
- end
Advertisement
Add Comment
Please, Sign In to add comment