Advertisement
markov2019

Untitled

Jan 10th, 2025 (edited)
29
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.33 KB | None | 0 0
  1. local component = require("component")
  2. local computer = require("computer")
  3.  
  4. if not component.isAvailable("internet") then
  5.   io.stderr:write("OpenIRC requires an Internet Card to run!\n")
  6.   return
  7. end
  8.  
  9. local event = require("event")
  10. local internet = require("internet")
  11. local shell = require("shell")
  12. local term = require("term")
  13. local text = require("text")
  14.  
  15. -- Predefined connection details
  16. local nick = "YourNickname"  -- Replace with your nickname
  17. local host = "irc.quakenet.org:6667" -- IRC server address and port
  18. local channel = "#Chat_Streamer_S" -- Channel to join
  19.  
  20. if not component.isAvailable("chat_box") then
  21.   io.stderr:write("OpenIRC requires a Chat Box to run!\n")
  22.   return
  23. end
  24.  
  25. local chat_box = component.chat_box
  26. chat_box.setName("§cD") -- Set chat box name
  27.  
  28. if not host:find(":") then
  29.   host = host .. ":6667"
  30. end
  31.  
  32. -- try to connect to server.
  33. local sock, reason = internet.open(host)
  34. if not sock then
  35.   io.stderr:write(reason .. "\n")
  36.   return
  37. end
  38.  
  39. -- custom print that uses all except the last line for printing.
  40. local function print(message, overwrite)
  41.   local w, h = component.gpu.getResolution()
  42.   local line
  43.   repeat
  44.     line, message = text.wrap(text.trim(message), w, w)
  45.     if not overwrite then
  46.       component.gpu.copy(1, 1, w, h - 1, 0, -1)
  47.     end
  48.     overwrite = false
  49.     component.gpu.fill(1, h - 1, w, 1, " ")
  50.     component.gpu.set(1, h - 1, line)
  51.   until not message or message == ""
  52. end
  53.  
  54. -- utility method for reply tracking tables.
  55. function autocreate(table, key)
  56.   table[key] = {}
  57.   return table[key]
  58. end
  59.  
  60. -- extract nickname from identity.
  61. local function name(identity)
  62.   return identity and identity:match("^[^!]+") or identity or "Anonymous"
  63. end
  64.  
  65. -- user defined callback for messages (via `lua function(msg) ... end`)
  66. local callback = nil
  67.  
  68. -- list of whois info per user (used to accumulate whois replies).
  69. local whois = setmetatable({}, {__index=autocreate})
  70.  
  71. -- list of users per channel (used to accumulate names replies).
  72. local names = setmetatable({}, {__index=autocreate})
  73.  
  74. -- timer used to drive socket reading.
  75. local timer
  76.  
  77. -- ignored commands, reserved according to RFC.
  78. -- http://tools.ietf.org/html/rfc2812#section-5.3
  79. local ignore = {
  80.   [213]=true, [214]=true, [215]=true, [216]=true, [217]=true,
  81.   [218]=true, [231]=true, [232]=true, [233]=true, [240]=true,
  82.   [241]=true, [244]=true, [244]=true, [246]=true, [247]=true,
  83.   [250]=true, [300]=true, [316]=true, [361]=true, [362]=true,
  84.   [363]=true, [373]=true, [384]=true, [492]=true,
  85.   -- custom ignored responses.
  86.   [265]=true, [266]=true, [330]=true
  87. }
  88.  
  89. -- command numbers to names.
  90. local commands = {
  91. --Replys
  92.   RPL_WELCOME = "001",
  93.   RPL_YOURHOST = "002",
  94.   RPL_CREATED = "003",
  95.   RPL_MYINFO = "004",
  96.   RPL_BOUNCE = "005",
  97.   RPL_LUSERCLIENT = "251",
  98.   RPL_LUSEROP = "252",
  99.   RPL_LUSERUNKNOWN = "253",
  100.   RPL_LUSERCHANNELS = "254",
  101.   RPL_LUSERME = "255",
  102.   RPL_AWAY = "301",
  103.   RPL_UNAWAY = "305",
  104.   RPL_NOWAWAY = "306",
  105.   RPL_WHOISUSER = "311",
  106.   RPL_WHOISSERVER = "312",
  107.   RPL_WHOISOPERATOR = "313",
  108.   RPL_WHOISIDLE = "317",
  109.   RPL_ENDOFWHOIS = "318",
  110.   RPL_WHOISCHANNELS = "319",
  111.   RPL_CHANNELMODEIS = "324",
  112.   RPL_NOTOPIC = "331",
  113.   RPL_TOPIC = "332",
  114.   RPL_NAMREPLY = "353",
  115.   RPL_ENDOFNAMES = "366",
  116.   RPL_MOTDSTART = "375",
  117.   RPL_MOTD = "372",
  118.   RPL_ENDOFMOTD = "376",
  119.   RPL_WHOISSECURE = "671",
  120.   RPL_HELPSTART = "704",
  121.   RPL_HELPTXT = "705",
  122.   RPL_ENDOFHELP = "706",
  123.   RPL_UMODEGMSG = "718",
  124.  
  125. --Errors
  126.   ERR_BANLISTFULL = "478",
  127.   ERR_CHANNELISFULL = "471",
  128.   ERR_UNKNOWNMODE = "472",
  129.   ERR_INVITEONLYCHAN = "473",
  130.   ERR_BANNEDFROMCHAN = "474",
  131.   ERR_CHANOPRIVSNEEDED = "482",
  132.   ERR_UNIQOPRIVSNEEDED = "485",
  133.   ERR_USERNOTINCHANNEL = "441",
  134.   ERR_NOTONCHANNEL = "442",
  135.   ERR_NICKCOLLISION = "436",
  136.   ERR_NICKNAMEINUSE = "433",
  137.   ERR_ERRONEUSNICKNAME = "432",
  138.   ERR_WASNOSUCHNICK = "406",
  139.   ERR_TOOMANYCHANNELS = "405",
  140.   ERR_CANNOTSENDTOCHAN = "404",
  141.   ERR_NOSUCHCHANNEL = "403",
  142.   ERR_NOSUCHNICK = "401",
  143.   ERR_MODELOCK = "742"
  144. }
  145.  
  146. -- main command handling callback.
  147. local function handleCommand(prefix, command, args, message)
  148.   ---------------------------------------------------
  149.   -- Keepalive
  150.  
  151.   if command == "PING" then
  152.     sock:write(string.format("PONG :%s\r\n", message))
  153.     sock:flush()
  154.  
  155.   ---------------------------------------------------
  156.   -- General commands
  157.   elseif command == "NICK" then
  158.     local oldNick, newNick = name(prefix), tostring(args[1] or message)
  159.     if oldNick == nick then
  160.       nick = newNick
  161.     end
  162.     print(oldNick .. " is now known as " .. newNick .. ".")
  163.   elseif command == "MODE" then
  164.     if #args == 2 then
  165.       print("[" .. args[1] .. "] " .. name(prefix) .. " set mode".. ( #args[2] > 2 and "s" or "" ) .. " " .. tostring(args[2] or message) .. ".")
  166.     else
  167.       local setmode = {}
  168.       local cumode = "+"
  169.       args[2]:gsub(".", function(char)
  170.         if char == "-" or char == "+" then
  171.           cumode = char
  172.         else
  173.           table.insert(setmode, {cumode, char})
  174.         end
  175.       end)
  176.       local d = {}
  177.       local users = {}
  178.       for i = 3, #args do
  179.         users[i-2] = args[i]
  180.       end
  181.       users[#users+1] = message
  182.       local last
  183.       local ctxt = ""
  184.       for c = 1, #users do
  185.         if not setmode[c] then
  186.           break
  187.         end
  188.         local mode = setmode[c][2]
  189.         local pfx = setmode[c][1]=="+"
  190.         local key = mode == "o" and (pfx and "opped" or "deoped") or
  191.           mode == "v" and (pfx and "voiced" or "devoiced") or
  192.           mode == "q" and (pfx and "quieted" or "unquieted") or
  193.           mode == "b" and (pfx and "banned" or "unbanned") or
  194.           "set " .. setmode[c][1] .. mode .. " on"
  195.         if last ~= key then
  196.           if last then
  197.             print(ctxt)
  198.           end
  199.           ctxt = "[" .. args[1] .. "] " .. name(prefix) .. " " .. key
  200.           last = key
  201.         end
  202.         ctxt = ctxt .. " " .. users[c]
  203.       end
  204.       if #ctxt > 0 then
  205.         print(ctxt)
  206.       end
  207.     end
  208.   elseif command == "QUIT" then
  209.     print(name(prefix) .. " quit (" .. (message or "Quit") .. ").")
  210.   elseif command == "JOIN" then
  211.     print("[" .. args[1] .. "] " .. name(prefix) .. " entered the room.")
  212.   elseif command == "PART" then
  213.     print("[" .. args[1] .. "] " .. name(prefix) .. " has left the room (quit: " .. (message or "Quit") .. ").")
  214.   elseif command == "TOPIC" then
  215.     print("[" .. args[1] .. "] " .. name(prefix) .. " has changed the topic to: " .. message)
  216.   elseif command == "KICK" then
  217.     print("[" .. args[1] .. "] " .. name(prefix) .. " kicked " .. args[2])
  218.   elseif command == "PRIVMSG" then
  219.     local ctcp = message:match("^\1(.-)\1$")
  220.     if ctcp then
  221.       local orig_ctcp, param = ctcp:match("^(%S+) ?(.-)$")
  222.       ctcp = orig_ctcp:upper()
  223.       if ctcp == "TIME" then
  224.         sock:write("NOTICE " .. name(prefix) .. " :\001TIME " .. os.date() .. "\001\r\n")
  225.         sock:flush()
  226.       elseif ctcp == "VERSION" then
  227.         sock:write("NOTICE " .. name(prefix) .. " :\001VERSION Minecraft/OpenComputers Lua 5.2\001\r\n")
  228.         sock:flush()
  229.       elseif ctcp == "PING" then
  230.         sock:write("NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001\r\n")
  231.         sock:flush()
  232.       elseif ctcp == "ACTION" then
  233.         print("[" .. args[1] .. "] * " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
  234.         chat_box.say("* " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
  235.       else
  236.         print("[" .. name(prefix) .. "] CTCP " .. orig_ctcp)
  237.       end
  238.     else
  239.       if string.find(message, nick) then
  240.         computer.beep()
  241.       end
  242.       print("[" .. args[1] .. "] " .. name(prefix) .. ": " .. message)
  243.       chat_box.say(message) -- Only show the message content
  244.     end
  245.   elseif command == "NOTICE" then
  246.     print("[NOTICE] " .. message)
  247.   elseif command == "ERROR" then
  248.     print("[ERROR] " .. message)
  249.  
  250.   ---------------------------------------------------
  251.   -- Ignored reserved numbers
  252.   -- -- http://tools.ietf.org/html/rfc2812#section-5.3
  253.  
  254.   elseif tonumber(command) and ignore[tonumber(command)] then
  255.     -- ignore
  256.  
  257.   ---------------------------------------------------
  258.   -- Command replies
  259.   -- http://tools.ietf.org/html/rfc2812#section-5.1
  260.  
  261.   elseif command == commands.RPL_WELCOME then
  262.     print(message)
  263.   elseif command == commands.RPL_YOURHOST then -- ignore
  264.   elseif command == commands.RPL_CREATED then -- ignore
  265.   elseif command == commands.RPL_MYINFO then -- ignore
  266.   elseif command == commands.RPL_BOUNCE then -- ignore
  267.   elseif command == commands.RPL_LUSERCLIENT then
  268.     print(message)
  269.   elseif command == commands.RPL_LUSEROP then -- ignore
  270.   elseif command == commands.RPL_LUSERUNKNOWN then -- ignore
  271.   elseif command == commands.RPL_LUSERCHANNELS then -- ignore
  272.   elseif command == commands.RPL_LUSERME then
  273.     print(message)
  274.   elseif command == commands.RPL_AWAY then
  275.     print(string.format("%s is away: %s", name(args[1]), message))
  276.   elseif command == commands.RPL_UNAWAY or command == commands.RPL_NOWAWAY then
  277.     print(message)
  278.   elseif command == commands.RPL_WHOISUSER then
  279.     local nick = args[2]:lower()
  280.     whois[nick].nick = args[2]
  281.     whois[nick].user = args[3]
  282.     whois[nick].host = args[4]
  283.     whois[nick].realName = message
  284.   elseif command == commands.RPL_WHOISSERVER then
  285.     local nick = args[2]:lower()
  286.     whois[nick].server = args[3]
  287.     whois[nick].serverInfo = message
  288.   elseif command == commands.RPL_WHOISOPERATOR then
  289.     local nick = args[2]:lower()
  290.     whois[nick].isOperator = true
  291.   elseif command == commands.RPL_WHOISIDLE then
  292.     local nick = args[2]:lower()
  293.     whois[nick].idle = tonumber(args[3])
  294.   elseif command == commands.RPL_WHOISSECURE then
  295.     local nick = args[2]:lower()
  296.     whois[nick].secureconn = "Is using a secure connection"
  297.   elseif command == commands.RPL_ENDOFWHOIS then
  298.     local nick = args[2]:lower()
  299.     local info = whois[nick]
  300.     if info.nick then print("Nick: " .. info.nick) end
  301.     if info.user then print("User name: " .. info.user) end
  302.     if info.realName then print("Real name: " .. info.realName) end
  303.     if info.host then print("Host: " .. info.host) end
  304.     if info.server then print("Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end
  305.     if info.secureconn then print(info.secureconn) end
  306.     if info.channels then print("Channels: " .. info.channels) end
  307.     if info.idle then print("Idle for: " .. info.idle) end
  308.     whois[nick] = nil
  309.   elseif command == commands.RPL_WHOISCHANNELS then
  310.     local nick = args[2]:lower()
  311.     whois[nick].channels = message
  312.   elseif command == commands.RPL_CHANNELMODEIS then
  313.     print("Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")")
  314.   elseif command == commands.RPL_NOTOPIC then
  315.     print("No topic is set for " .. args[1] .. ".")
  316.   elseif command == commands.RPL_TOPIC then
  317.     print("Topic for " .. args[1] .. ": " .. message)
  318.   elseif command == commands.RPL_NAMREPLY then
  319.     local channel = args[3]
  320.     table.insert(names[channel], message)
  321.   elseif command == commands.RPL_ENDOFNAMES then
  322.     local channel = args[2]
  323.     print("Users on " .. channel .. ": " .. (#names[channel] > 0 and table.concat(names[channel], " ") or "none"))
  324.     names[channel] = nil
  325.   elseif command == commands.RPL_MOTDSTART then
  326.     if options.motd then
  327.       print(message .. args[1])
  328.     end
  329.   elseif command == commands.RPL_MOTD then
  330.     if options.motd then
  331.       print(message)
  332.     end
  333.   elseif command == commands.RPL_ENDOFMOTD then -- ignore
  334.   elseif command == commands.RPL_HELPSTART or
  335.   command == commands.RPL_HELPTXT or
  336.   command == commands.RPL_ENDOFHELP then
  337.     print(message)
  338.   elseif command == commands.ERR_BANLISTFULL or
  339.   command == commands.ERR_BANNEDFROMCHAN or
  340.   command == commands.ERR_CANNOTSENDTOCHAN or
  341.   command == commands.ERR_CHANNELISFULL or
  342.   command == commands.ERR_CHANOPRIVSNEEDED or
  343.   command == commands.ERR_ERRONEUSNICKNAME or
  344.   command == commands.ERR_INVITEONLYCHAN or
  345.   command == commands.ERR_NICKCOLLISION or
  346.   command == commands.ERR_NOSUCHNICK or
  347.   command == commands.ERR_NOTONCHANNEL or
  348.   command == commands.ERR_UNIQOPRIVSNEEDED or
  349.   command == commands.ERR_UNKNOWNMODE or
  350.   command == commands.ERR_USERNOTINCHANNEL or
  351.   command == commands.ERR_WASNOSUCHNICK or
  352.   command == commands.ERR_MODELOCK then
  353.     print("[ERROR]: " .. message)
  354.   elseif tonumber(command) and (tonumber(command) >= 200 and tonumber(command) < 400) then
  355.     print("[Response " .. command .. "] " .. table.concat(args, ", ") .. ": " .. message)
  356.  
  357.   ---------------------------------------------------
  358.   -- Error messages. No real point in handling those manually.
  359.   -- http://tools.ietf.org/html/rfc2812#section-5.2
  360.  
  361.   elseif tonumber(command) and (tonumber(command) >= 400 and tonumber(command) < 600) then
  362.     print("[Error] " .. table.concat(args, ", ") .. ": " .. message)
  363.  
  364.   ---------------------------------------------------
  365.   -- Unhandled message.
  366.  
  367.   else
  368.     print("Unhandled command: " .. command .. ": " .. message)
  369.   end
  370. end
  371.  
  372. -- catch errors to allow manual closing of socket and removal of timer.
  373. local result, reason = pcall(function()
  374.   -- say hello and connect
  375.   term.clear()
  376.   print("Welcome to OpenIRC!")
  377.  
  378.   -- avoid sock:read locking up the computer
  379.   sock:setTimeout(0.05)
  380.  
  381.   -- Send initial connection commands
  382.   sock:write(string.format("NICK %s\r\n", nick))
  383.   sock:write(string.format("USER %s 0 * :%s [OpenComputers]\r\n", nick:lower(), nick))
  384.   sock:flush()
  385.  
  386.   -- Add small delay to ensure registration completes before joining
  387.   os.sleep(1)
  388.  
  389.   -- Then join channel
  390.   sock:write(string.format("JOIN %s\r\n", channel))
  391.   sock:flush()
  392.  
  393.   -- Set default target to the channel
  394.   target = channel
  395.  
  396.   -- socket reading logic (receive messages) driven by a timer.
  397.   timer = event.timer(0.5, function()
  398.     if not sock then
  399.       return false
  400.     end
  401.     repeat
  402.       local ok, line = pcall(sock.read, sock)
  403.       if ok then
  404.         if not line then
  405.           print("Connection lost.")
  406.           sock:close()
  407.           sock = nil
  408.           return false
  409.         end
  410.         line = text.trim(line) -- get rid of trailing \r
  411.         local match, prefix = line:match("^(:(%S+) )")
  412.         if match then line = line:sub(#match + 1) end
  413.         local match, command = line:match("^(([^:]%S*))")
  414.         if match then line = line:sub(#match + 1) end
  415.         local args = {}
  416.         repeat
  417.           local match, arg = line:match("^( ([^:]%S*))")
  418.           if match then
  419.             line = line:sub(#match + 1)
  420.             table.insert(args, arg)
  421.           end
  422.         until not match
  423.         local message = line:match("^ :(.*)$")
  424.  
  425.         if callback then
  426.           local result, reason = pcall(callback, prefix, command, args, message)
  427.           if not result then
  428.             print("Error in callback: " .. tostring(reason))
  429.           end
  430.         end
  431.         handleCommand(prefix, command, args, message)
  432.       end
  433.     until not ok
  434.   end, math.huge)
  435.  
  436.   -- Catch messages from chat box and send to IRC
  437.   event.listen("chat_message", function(_, player, message)
  438.     if sock then
  439.       -- Send message to IRC channel
  440.       sock:write(string.format("PRIVMSG %s :[%s] %s\r\n", channel, player, message))
  441.       sock:flush()
  442.     end
  443.   end)
  444.  
  445.   -- default target for messages, so we don't have to type /msg all the time.
  446.   local target = nil
  447.  
  448.   -- command history.
  449.   local history = {}
  450.  
  451.   repeat
  452.     local w, h = component.gpu.getResolution()
  453.     term.setCursor(1, h)
  454.     term.write((target or "?") .. "> ")
  455.     local line = term.read(history)
  456.     if sock and line and line ~= "" then
  457.       line = text.trim(line)
  458.       if line:lower():sub(1,4) == "/me " then
  459.         print("[" .. (target or "?") .. "] " .. nick .. " " .. line:sub(5), true)
  460.       elseif line~="" then
  461.         print("[" .. (target or "?") .. "] " .. nick .. ": " .. line, true)
  462.       end
  463.       if line:lower():sub(1, 5) == "/msg " then
  464.         local user, message = line:sub(6):match("^(%S+) (.+)$")
  465.         if message then
  466.           message = text.trim(message)
  467.         end
  468.         if not user or not message or message == "" then
  469.           print("Invalid use of /msg. Usage: /msg nick|channel message.")
  470.           line = ""
  471.         else
  472.           target = user
  473.           line = "PRIVMSG " .. target .. " :" .. message
  474.         end
  475.       elseif line:lower():sub(1, 6) == "/join " then
  476.         local channel = text.trim(line:sub(7))
  477.         if not channel or channel == "" then
  478.           print("Invalid use of /join. Usage: /join channel.")
  479.           line = ""
  480.         else
  481.           target = channel
  482.           line = "JOIN " .. channel
  483.         end
  484.       elseif line:lower():sub(1, 5) == "/lua " then
  485.         local script = text.trim(line:sub(6))
  486.         local result, reason = load(script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
  487.         if not result then
  488.           result, reason = load("return " .. script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
  489.         end
  490.         line = ""
  491.         if not result then
  492.           print("Error: " .. tostring(reason))
  493.         else
  494.           result, reason = pcall(result)
  495.           if not result then
  496.             print("Error: " .. tostring(reason))
  497.           elseif type(reason) == "function" then
  498.             callback = reason
  499.           elseif reason then
  500.             line = tostring(reason)
  501.           end
  502.         end
  503.       elseif line:lower():sub(1,4) == "/me " then
  504.         if not target then
  505.           print("No default target set. Use /msg or /join to set one.")
  506.           line = ""
  507.         else
  508.           line = "PRIVMSG " .. target .. " :\001ACTION " .. line:sub(5) .. "\001"
  509.         end
  510.       elseif line:sub(1, 1) == "/" then
  511.         line = line:sub(2)
  512.       elseif line ~= "" then
  513.         if not target then
  514.           print("No default target set. Use /msg or /join to set one.")
  515.           line = ""
  516.         else
  517.           line = "PRIVMSG " .. target .. " :" .. line
  518.         end
  519.       end
  520.       if line and line ~= "" then
  521.         sock:write(line .. "\r\n")
  522.         sock:flush()
  523.       end
  524.     end
  525.   until not sock or not line
  526. end)
  527.  
  528. if sock then
  529.   sock:write("QUIT\r\n")
  530.   sock:close()
  531. end
  532. if timer then
  533.   event.cancel(timer)
  534. end
  535.  
  536. if not result then
  537.   error(reason, 0)
  538. end
  539. return reason
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement