Guest User

chat.lua

a guest
Jan 7th, 2025
6
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.40 KB | None | 0 0
  1. -- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
  2. --
  3. -- SPDX-License-Identifier: LicenseRef-CCPL
  4.  
  5. local tArgs = { ... }
  6.  
  7. local function printUsage()
  8.     local programName = arg[0] or fs.getName(shell.getRunningProgram())
  9.     print("Usages:")
  10.     print(programName .. " host <hostname>")
  11.     print(programName .. " join <hostname> <nickname>")
  12. end
  13.  
  14. local sOpenedModem = nil
  15. local function openModem()
  16.     for _, sModem in ipairs(peripheral.getNames()) do
  17.         if peripheral.getType(sModem) == "modem" then
  18.             if not rednet.isOpen(sModem) then
  19.                 rednet.open(sModem)
  20.                 sOpenedModem = sModem
  21.             end
  22.             return true
  23.         end
  24.     end
  25.     print("No modems found.")
  26.     return false
  27. end
  28.  
  29. local function closeModem()
  30.     if sOpenedModem ~= nil then
  31.         rednet.close(sOpenedModem)
  32.         sOpenedModem = nil
  33.     end
  34. end
  35.  
  36. -- Colours
  37. local highlightColour, textColour
  38. if term.isColour() then
  39.     textColour = colours.white
  40.     highlightColour = colours.yellow
  41. else
  42.     textColour = colours.white
  43.     highlightColour = colours.white
  44. end
  45.  
  46. local sCommand = tArgs[1]
  47. if sCommand == "host" then
  48.     -- "chat host"
  49.     -- Get hostname
  50.     local sHostname = tArgs[2]
  51.     if sHostname == nil then
  52.         printUsage()
  53.         return
  54.     end
  55.  
  56.     -- Host server
  57.     if not openModem() then
  58.         return
  59.     end
  60.     rednet.host("chat", sHostname)
  61.     print("0 users connected.")
  62.  
  63.     local tUsers = {}
  64.     local nUsers = 0
  65.     local function send(sText, nUserID)
  66.         if nUserID then
  67.             local tUser = tUsers[nUserID]
  68.             if tUser then
  69.                 rednet.send(tUser.nID, {
  70.                     sType = "text",
  71.                     nUserID = nUserID,
  72.                     sText = sText,
  73.                 }, "chat")
  74.             end
  75.         else
  76.             for nUserID, tUser in pairs(tUsers) do
  77.                 rednet.send(tUser.nID, {
  78.                     sType = "text",
  79.                     nUserID = nUserID,
  80.                     sText = sText,
  81.                 }, "chat")
  82.             end
  83.         end
  84.     end
  85.  
  86.     -- Setup ping pong
  87.     local tPingPongTimer = {}
  88.     local function ping(nUserID)
  89.         local tUser = tUsers[nUserID]
  90.         rednet.send(tUser.nID, {
  91.             sType = "ping to client",
  92.             nUserID = nUserID,
  93.         }, "chat")
  94.  
  95.         local timer = os.startTimer(15)
  96.         tUser.bPingPonged = false
  97.         tPingPongTimer[timer] = nUserID
  98.     end
  99.  
  100.     local function printUsers()
  101.         local _, y = term.getCursorPos()
  102.         term.setCursorPos(1, y - 1)
  103.         term.clearLine()
  104.         if nUsers == 1 then
  105.             print(nUsers .. " user connected.")
  106.         else
  107.             print(nUsers .. " users connected.")
  108.         end
  109.     end
  110.  
  111.     -- Handle messages
  112.     local ok, error = pcall(parallel.waitForAny,
  113.         function()
  114.             while true do
  115.                 local _, timer = os.pullEvent("timer")
  116.                 local nUserID = tPingPongTimer[timer]
  117.                 if nUserID and tUsers[nUserID] then
  118.                     local tUser = tUsers[nUserID]
  119.                     if tUser then
  120.                         if not tUser.bPingPonged then
  121.                             send("* " .. tUser.sUsername .. " has timed out")
  122.                             tUsers[nUserID] = nil
  123.                             nUsers = nUsers - 1
  124.                             printUsers()
  125.                         else
  126.                             ping(nUserID)
  127.                         end
  128.                     end
  129.                 end
  130.             end
  131.         end,
  132.         function()
  133.             while true do
  134.                 local tCommands
  135.                 tCommands = {
  136.                     ["me"] = function(tUser, sContent)
  137.                         if #sContent > 0 then
  138.                             send("* " .. tUser.sUsername .. " " .. sContent)
  139.                         else
  140.                             send("* Usage: /me [words]", tUser.nUserID)
  141.                         end
  142.                     end,
  143.                     ["nick"] = function(tUser, sContent)
  144.                         if #sContent > 0 then
  145.                             local sOldName = tUser.sUsername
  146.                             tUser.sUsername = sContent
  147.                             send("* " .. sOldName .. " is now known as " .. tUser.sUsername)
  148.                         else
  149.                             send("* Usage: /nick [nickname]", tUser.nUserID)
  150.                         end
  151.                     end,
  152.                     ["users"] = function(tUser, sContent)
  153.                         send("* Connected Users:", tUser.nUserID)
  154.                         local sUsers = "*"
  155.                         for _, tUser in pairs(tUsers) do
  156.                             sUsers = sUsers .. " " .. tUser.sUsername
  157.                         end
  158.                         send(sUsers, tUser.nUserID)
  159.                     end,
  160.                     ["help"] = function(tUser, sContent)
  161.                         send("* Available commands:", tUser.nUserID)
  162.                         local sCommands = "*"
  163.                         for sCommand in pairs(tCommands) do
  164.                             sCommands = sCommands .. " /" .. sCommand
  165.                         end
  166.                         send(sCommands .. " /logout", tUser.nUserID)
  167.                     end,
  168.                 }
  169.  
  170.                 local nSenderID, tMessage = rednet.receive("chat")
  171.                 if type(tMessage) == "table" then
  172.                     if tMessage.sType == "login" then
  173.                         -- Login from new client
  174.                         local nUserID = tMessage.nUserID
  175.                         local sUsername = tMessage.sUsername
  176.                         if nUserID and sUsername then
  177.                             tUsers[nUserID] = {
  178.                                 nID = nSenderID,
  179.                                 nUserID = nUserID,
  180.                                 sUsername = sUsername,
  181.                             }
  182.                             nUsers = nUsers + 1
  183.                             printUsers()
  184.                             send("* " .. sUsername .. " has joined the chat")
  185.                             ping(nUserID)
  186.                         end
  187.  
  188.                     else
  189.                         -- Something else from existing client
  190.                         local nUserID = tMessage.nUserID
  191.                         local tUser = tUsers[nUserID]
  192.                         if tUser and tUser.nID == nSenderID then
  193.                             if tMessage.sType == "logout" then
  194.                                 send("* " .. tUser.sUsername .. " has left the chat")
  195.                                 tUsers[nUserID] = nil
  196.                                 nUsers = nUsers - 1
  197.                                 printUsers()
  198.  
  199.                             elseif tMessage.sType == "chat" then
  200.                                 local sMessage = tMessage.sText
  201.                                 if sMessage then
  202.                                     local sCommand = string.match(sMessage, "^/([a-z]+)")
  203.                                     if sCommand then
  204.                                         local fnCommand = tCommands[sCommand]
  205.                                         if fnCommand then
  206.                                             local sContent = string.sub(sMessage, #sCommand + 3)
  207.                                             fnCommand(tUser, sContent)
  208.                                         else
  209.                                             send("* Unrecognised command: /" .. sCommand, tUser.nUserID)
  210.                                         end
  211.                                     else
  212.                                         send("<" .. tUser.sUsername .. "> " .. tMessage.sText)
  213.                                     end
  214.                                 end
  215.  
  216.                             elseif tMessage.sType == "ping to server" then
  217.                                 rednet.send(tUser.nID, {
  218.                                     sType = "pong to client",
  219.                                     nUserID = nUserID,
  220.                                 }, "chat")
  221.  
  222.                             elseif tMessage.sType == "pong to server" then
  223.                                 tUser.bPingPonged = true
  224.  
  225.                             end
  226.                         end
  227.                     end
  228.                  end
  229.             end
  230.         end
  231.    )
  232.     if not ok then
  233.         printError(error)
  234.     end
  235.  
  236.     -- Unhost server
  237.     for nUserID, tUser in pairs(tUsers) do
  238.         rednet.send(tUser.nID, {
  239.             sType = "kick",
  240.             nUserID = nUserID,
  241.         }, "chat")
  242.     end
  243.     rednet.unhost("chat")
  244.     closeModem()
  245.  
  246. elseif sCommand == "join" then
  247.     -- "chat join"
  248.     -- Get hostname and username
  249.     local sHostname = tArgs[2]
  250.     local sUsername = tArgs[3]
  251.     if sHostname == nil or sUsername == nil then
  252.         printUsage()
  253.         return
  254.     end
  255.  
  256.     -- Connect
  257.     if not openModem() then
  258.         return
  259.     end
  260.     write("Looking up " .. sHostname .. "... ")
  261.     local nHostID = rednet.lookup("chat", sHostname)
  262.     if nHostID == nil then
  263.         print("Failed.")
  264.         return
  265.     else
  266.         print("Success.")
  267.     end
  268.  
  269.     -- Login
  270.     local nUserID = math.random(1, 2147483647)
  271.     rednet.send(nHostID, {
  272.         sType = "login",
  273.         nUserID = nUserID,
  274.         sUsername = sUsername,
  275.     }, "chat")
  276.  
  277.     -- Setup ping pong
  278.     local bPingPonged = true
  279.     local pingPongTimer = os.startTimer(0)
  280.  
  281.     local function ping()
  282.         rednet.send(nHostID, {
  283.             sType = "ping to server",
  284.             nUserID = nUserID,
  285.         }, "chat")
  286.         bPingPonged = false
  287.         pingPongTimer = os.startTimer(15)
  288.     end
  289.  
  290.     -- Handle messages
  291.     local w, h = term.getSize()
  292.     local parentTerm = term.current()
  293.     local titleWindow = window.create(parentTerm, 1, 1, w, 1, true)
  294.     local historyWindow = window.create(parentTerm, 1, 2, w, h - 2, true)
  295.     local promptWindow = window.create(parentTerm, 1, h, w, 1, true)
  296.     historyWindow.setCursorPos(1, h - 2)
  297.  
  298.     term.clear()
  299.     term.setTextColour(textColour)
  300.     term.redirect(promptWindow)
  301.     promptWindow.restoreCursor()
  302.  
  303.     local function drawTitle()
  304.         local w = titleWindow.getSize()
  305.         local sTitle = sUsername .. " on " .. sHostname
  306.         titleWindow.setTextColour(highlightColour)
  307.         titleWindow.setCursorPos(math.floor(w / 2 - #sTitle / 2), 1)
  308.         titleWindow.clearLine()
  309.         titleWindow.write(sTitle)
  310.         promptWindow.restoreCursor()
  311.     end
  312.  
  313.     local function printMessage(sMessage)
  314.         term.redirect(historyWindow)
  315.         print()
  316.         if string.match(sMessage, "^%*") then
  317.             -- Information
  318.             term.setTextColour(highlightColour)
  319.             write(sMessage)
  320.             term.setTextColour(textColour)
  321.         else
  322.             -- Chat
  323.             local sUsernameBit = string.match(sMessage, "^<[^>]*>")
  324.             if sUsernameBit then
  325.                 term.setTextColour(highlightColour)
  326.                 write(sUsernameBit)
  327.                 term.setTextColour(textColour)
  328.                 write(string.sub(sMessage, #sUsernameBit + 1))
  329.             else
  330.                 write(sMessage)
  331.             end
  332.         end
  333.         term.redirect(promptWindow)
  334.         promptWindow.restoreCursor()
  335.     end
  336.  
  337.     drawTitle()
  338.  
  339.     local ok, error = pcall(parallel.waitForAny,
  340.         function()
  341.             while true do
  342.                 local sEvent, timer = os.pullEvent()
  343.                 if sEvent == "timer" then
  344.                     if timer == pingPongTimer then
  345.                         if not bPingPonged then
  346.                             printMessage("Server timeout.")
  347.                             return
  348.                         else
  349.                             ping()
  350.                         end
  351.                     end
  352.  
  353.                 elseif sEvent == "term_resize" then
  354.                     local w, h = parentTerm.getSize()
  355.                     titleWindow.reposition(1, 1, w, 1)
  356.                     historyWindow.reposition(1, 2, w, h - 2)
  357.                     promptWindow.reposition(1, h, w, 1)
  358.  
  359.                 end
  360.             end
  361.         end,
  362.         function()
  363.             while true do
  364.                 local nSenderID, tMessage = rednet.receive("chat")
  365.                 if nSenderID == nHostID and type(tMessage) == "table" and tMessage.nUserID == nUserID then
  366.                     if tMessage.sType == "text" then
  367.                         local sText = tMessage.sText
  368.                         if sText then
  369.                             printMessage(sText)
  370.                         end
  371.  
  372.                     elseif tMessage.sType == "ping to client" then
  373.                         rednet.send(nSenderID, {
  374.                             sType = "pong to server",
  375.                             nUserID = nUserID,
  376.                         }, "chat")
  377.  
  378.                     elseif tMessage.sType == "pong to client" then
  379.                         bPingPonged = true
  380.  
  381.                     elseif tMessage.sType == "kick" then
  382.                         return
  383.  
  384.                     end
  385.                 end
  386.             end
  387.         end,
  388.         function()
  389.             local tSendHistory = {}
  390.             while true do
  391.                 promptWindow.setCursorPos(1, 1)
  392.                 promptWindow.clearLine()
  393.                 promptWindow.setTextColor(highlightColour)
  394.                 promptWindow.write(": ")
  395.                 promptWindow.setTextColor(textColour)
  396.  
  397.                 local sChat = read(nil, tSendHistory)
  398.                 if string.match(sChat, "^/logout") then
  399.                     break
  400.                 else
  401.                     rednet.send(nHostID, {
  402.                         sType = "chat",
  403.                         nUserID = nUserID,
  404.                         sText = sChat,
  405.                     }, "chat")
  406.                     table.insert(tSendHistory, sChat)
  407.                 end
  408.             end
  409.         end
  410.     )
  411.  
  412.     -- Close the windows
  413.     term.redirect(parentTerm)
  414.  
  415.     -- Print error notice
  416.     local _, h = term.getSize()
  417.     term.setCursorPos(1, h)
  418.     term.clearLine()
  419.     term.setCursorBlink(false)
  420.     if not ok then
  421.         printError(error)
  422.     end
  423.  
  424.     -- Logout
  425.     rednet.send(nHostID, {
  426.         sType = "logout",
  427.         nUserID = nUserID,
  428.     }, "chat")
  429.     closeModem()
  430.  
  431.     -- Print disconnection notice
  432.     print("Disconnected.")
  433.  
  434. else
  435.     -- "chat somethingelse"
  436.     printUsage()
  437.  
  438. end
  439.  
Add Comment
Please, Sign In to add comment