DorianH935

Untitled

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