MigasRocha

Twitch Chat

Dec 28th, 2020 (edited)
172
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 21.20 KB | None | 0 0
  1. CHAT = "migasrocha3"
  2. APENASSUB = false
  3. LIMPAR = true
  4.  
  5. local expect = require "cc.expect"
  6. local expect, field = expect.expect, expect.field
  7.  
  8. local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring
  9. local debug_info = type(debug) == "table" and type(debug.getinfo) == "function" and debug.getinfo
  10. local debug_local = type(debug) == "table" and type(debug.getlocal) == "function" and debug.getlocal
  11.  
  12. --- @{table.insert} alternative, but with the length stored inline.
  13. local function append(out, value)
  14.     local n = out.n + 1
  15.     out[n], out.n = value, n
  16. end
  17.  
  18. --- A document containing formatted text, with multiple possible layouts.
  19. --
  20. -- Documents effectively represent a sequence of strings in alternative layouts,
  21. -- which we will try to print in the most compact form necessary.
  22. --
  23. -- @type Doc
  24. local Doc = { }
  25.  
  26. --- An empty document.
  27. local empty = setmetatable({ tag = "nil" }, Doc)
  28.  
  29. --- A document with a single space in it.
  30. local space = setmetatable({ tag = "text", text = " " }, Doc)
  31.  
  32. --- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
  33. local line = setmetatable({ tag = "line", flat = empty }, Doc)
  34.  
  35. --- A line break. When collapsed with @{group}, this will be replaced with @{space}.
  36. local space_line = setmetatable({ tag = "line", flat = space }, Doc)
  37.  
  38. local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
  39.  
  40. local function mk_text(text, colour)
  41.     return text_cache[text] or setmetatable({ tag = "text", text = text, colour = colour }, Doc)
  42. end
  43.  
  44. --- Create a new document from a string.
  45. --
  46. -- If your string contains multiple lines, @{group} will flatten the string
  47. -- into a single line, with spaces between each line.
  48. --
  49. -- @tparam      string text   The string to construct a new document with.
  50. -- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
  51. -- colour.
  52. -- @treturn Doc The document with the provided text.
  53. -- @usage Write some blue text.
  54. --     local pretty = require "cc.pretty"
  55. --     pretty.print(pretty.text("Hello!", colours.blue))
  56. local function text(text, colour)
  57.     expect(1, text, "string")
  58.     expect(2, colour, "number", "nil")
  59.  
  60.     local cached = text_cache[text]
  61.     if cached then return cached end
  62.  
  63.     local new_line = text:find("\n", 1)
  64.     if not new_line then return mk_text(text, colour) end
  65.  
  66.     -- Split the string by "\n". With a micro-optimisation to skip empty strings.
  67.     local doc = setmetatable({ tag = "concat", n = 0 }, Doc)
  68.     if new_line ~= 1 then append(doc, mk_text(text:sub(1, new_line - 1), colour)) end
  69.  
  70.     new_line = new_line + 1
  71.     while true do
  72.         local next_line = text:find("\n", new_line)
  73.         append(doc, space_line)
  74.         if not next_line then
  75.             if new_line <= #text then append(doc, mk_text(text:sub(new_line), colour)) end
  76.             return doc
  77.         else
  78.             if new_line <= next_line - 1 then
  79.                 append(doc, mk_text(text:sub(new_line, next_line - 1), colour))
  80.             end
  81.             new_line = next_line + 1
  82.         end
  83.     end
  84. end
  85.  
  86. --- Concatenate several documents together. This behaves very similar to string concatenation.
  87. --
  88. -- @tparam Doc|string ... The documents to concatenate.
  89. -- @treturn Doc The concatenated documents.
  90. -- @usage
  91. --     local pretty = require "cc.pretty"
  92. --     local doc1, doc2 = pretty.text("doc1"), pretty.text("doc2")
  93. --     print(pretty.concat(doc1, " - ", doc2))
  94. --     print(doc1 .. " - " .. doc2) -- Also supports ..
  95. local function concat(...)
  96.     local args = table.pack(...)
  97.     for i = 1, args.n do
  98.         if type(args[i]) == "string" then args[i] = text(args[i]) end
  99.         if getmetatable(args[i]) ~= Doc then expect(i, args[i], "document") end
  100.     end
  101.  
  102.     if args.n == 0 then return empty end
  103.     if args.n == 1 then return args[1] end
  104.  
  105.     args.tag = "concat"
  106.     return setmetatable(args, Doc)
  107. end
  108.  
  109. Doc.__concat = concat --- @local
  110.  
  111. --- Indent later lines of the given document with the given number of spaces.
  112. --
  113. -- For instance, nesting the document
  114. -- ```txt
  115. -- foo
  116. -- bar
  117. -- ```
  118. -- by two spaces will produce
  119. -- ```txt
  120. -- foo
  121. --   bar
  122. -- ```
  123. --
  124. -- @tparam number depth The number of spaces with which the document should be indented.
  125. -- @tparam Doc    doc   The document to indent.
  126. -- @treturn Doc The nested document.
  127. -- @usage
  128. --     local pretty = require "cc.pretty"
  129. --     print(pretty.nest(2, pretty.text("foo\nbar")))
  130. local function nest(depth, doc)
  131.     expect(1, depth, "number")
  132.     if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
  133.     if depth <= 0 then error("depth must be a positive number", 2) end
  134.  
  135.     return setmetatable({ tag = "nest", depth = depth, doc }, Doc)
  136. end
  137.  
  138. local function flatten(doc)
  139.     if doc.flat then return doc.flat end
  140.  
  141.     local kind = doc.tag
  142.     if kind == "nil" or kind == "text" then
  143.         return doc
  144.     elseif kind == "concat" then
  145.         local out = setmetatable({ tag = "concat", n = doc.n }, Doc)
  146.         for i = 1, doc.n do out[i] = flatten(doc[i]) end
  147.         doc.flat, out.flat = out, out -- cache the flattened node
  148.         return out
  149.     elseif kind == "nest" then
  150.         return flatten(doc[1])
  151.     elseif kind == "group" then
  152.         return doc[1]
  153.     else
  154.         error("Unknown doc " .. kind)
  155.     end
  156. end
  157.  
  158. --- Builds a document which is displayed on a single line if there is enough
  159. -- room, or as normal if not.
  160. --
  161. -- @tparam Doc doc The document to group.
  162. -- @treturn Doc The grouped document.
  163. -- @usage Uses group to show things being displayed on one or multiple lines.
  164. --
  165. --     local pretty = require "cc.pretty"
  166. --     local doc = pretty.group("Hello" .. pretty.space_line .. "World")
  167. --     print(pretty.render(doc, 5)) -- On multiple lines
  168. --     print(pretty.render(doc, 20)) -- Collapsed onto one.
  169. local function group(doc)
  170.     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
  171.  
  172.     if doc.tag == "group" then return doc end -- Skip if already grouped.
  173.  
  174.     local flattened = flatten(doc)
  175.     if flattened == doc then return doc end -- Also skip if flattening does nothing.
  176.     return setmetatable({ tag = "group", flattened, doc }, Doc)
  177. end
  178.  
  179. local function get_remaining(doc, width)
  180.     local kind = doc.tag
  181.     if kind == "nil" or kind == "line" then
  182.         return width
  183.     elseif kind == "text" then
  184.         return width - #doc.text
  185.     elseif kind == "concat" then
  186.         for i = 1, doc.n do
  187.             width = get_remaining(doc[i], width)
  188.             if width < 0 then break end
  189.         end
  190.         return width
  191.     elseif kind == "group" or kind == "nest" then
  192.         return get_remaining(kind[1])
  193.     else
  194.         error("Unknown doc " .. kind)
  195.     end
  196. end
  197.  
  198. --- Display a document on the terminal.
  199. --
  200. -- @tparam      Doc     doc         The document to render
  201. -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in.
  202. local function write(doc, ribbon_frac)
  203.     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
  204.     expect(2, ribbon_frac, "number", "nil")
  205.  
  206.     local term = term
  207.     local width, height = term.getSize()
  208.     local ribbon_width = (ribbon_frac or 0.6) * width
  209.     if ribbon_width < 0 then ribbon_width = 0 end
  210.     if ribbon_width > width then ribbon_width = width end
  211.  
  212.     local def_colour = term.getTextColour()
  213.     local current_colour = def_colour
  214.  
  215.     local function go(doc, indent, col)
  216.         local kind = doc.tag
  217.         if kind == "nil" then
  218.             return col
  219.         elseif kind == "text" then
  220.             local doc_colour = doc.colour or def_colour
  221.             if doc_colour ~= current_colour then
  222.                 term.setTextColour(doc_colour)
  223.                 current_colour = doc_colour
  224.             end
  225.  
  226.             str_write(doc.text)
  227.  
  228.             return col + #doc.text
  229.         elseif kind == "line" then
  230.             local _, y = term.getCursorPos()
  231.             if y < height then
  232.                 term.setCursorPos(indent + 1, y + 1)
  233.             else
  234.                 term.scroll(1)
  235.                 term.setCursorPos(indent + 1, height)
  236.             end
  237.  
  238.             return indent
  239.         elseif kind == "concat" then
  240.             for i = 1, doc.n do col = go(doc[i], indent, col) end
  241.             return col
  242.         elseif kind == "nest" then
  243.             return go(doc[1], indent + doc.depth, col)
  244.         elseif kind == "group" then
  245.             if get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
  246.                 return go(doc[1], indent, col)
  247.             else
  248.                 return go(doc[2], indent, col)
  249.             end
  250.         else
  251.             error("Unknown doc " .. kind)
  252.         end
  253.     end
  254.  
  255.     local col = math.max(term.getCursorPos() - 1, 0)
  256.     go(doc, 0, col)
  257.     if current_colour ~= def_colour then term.setTextColour(def_colour) end
  258. end
  259.  
  260. --- Display a document on the terminal with a trailing new line.
  261. --
  262. -- @tparam      Doc     doc         The document to render.
  263. -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in.
  264. local function print(doc, ribbon_frac)
  265.     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
  266.     expect(2, ribbon_frac, "number", "nil")
  267.     write(doc, ribbon_frac)
  268.     str_write("\n")
  269. end
  270.  
  271. --- Render a document, converting it into a string.
  272. --
  273. -- @tparam      Doc     doc         The document to render.
  274. -- @tparam[opt] number  width       The maximum width of this document. Note that long strings will not be wrapped to
  275. -- fit this width - it is only used for finding the best layout.
  276. -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in.
  277. -- @treturn string The rendered document as a string.
  278. local function render(doc, width, ribbon_frac)
  279.     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
  280.     expect(2, width, "number", "nil")
  281.     expect(3, ribbon_frac, "number", "nil")
  282.  
  283.     local ribbon_width
  284.     if width then
  285.         ribbon_width = (ribbon_frac or 0.6) * width
  286.         if ribbon_width < 0 then ribbon_width = 0 end
  287.         if ribbon_width > width then ribbon_width = width end
  288.     end
  289.  
  290.     local out = { n = 0 }
  291.     local function go(doc, indent, col)
  292.         local kind = doc.tag
  293.         if kind == "nil" then
  294.             return col
  295.         elseif kind == "text" then
  296.             append(out, doc.text)
  297.             return col + #doc.text
  298.         elseif kind == "line" then
  299.             append(out, "\n" .. (" "):rep(indent))
  300.             return indent
  301.         elseif kind == "concat" then
  302.             for i = 1, doc.n do col = go(doc[i], indent, col) end
  303.             return col
  304.         elseif kind == "nest" then
  305.             return go(doc[1], indent + doc.depth, col)
  306.         elseif kind == "group" then
  307.             if not width or get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
  308.                 return go(doc[1], indent, col)
  309.             else
  310.                 return go(doc[2], indent, col)
  311.             end
  312.         else
  313.             error("Unknown doc " .. kind)
  314.         end
  315.     end
  316.  
  317.     go(doc, 0, 0)
  318.     return table.concat(out, "", 1, out.n)
  319. end
  320.  
  321. Doc.__tostring = render --- @local
  322.  
  323. local keywords = {
  324.     ["and"] = true, ["break"] = true, ["do"] = true, ["else"] = true,
  325.     ["elseif"] = true, ["end"] = true, ["false"] = true, ["for"] = true,
  326.     ["function"] = true, ["if"] = true, ["in"] = true, ["local"] = true,
  327.     ["nil"] = true, ["not"] = true, ["or"] = true, ["repeat"] = true, ["return"] = true,
  328.     ["then"] = true, ["true"] = true, ["until"] = true, ["while"] = true,
  329.   }
  330.  
  331. local comma = text(",")
  332. local braces = text("{}")
  333. local obrace, cbrace = text("{"), text("}")
  334. local obracket, cbracket = text("["), text("] = ")
  335.  
  336. local function key_compare(a, b)
  337.     local ta, tb = type(a), type(b)
  338.  
  339.     if ta == "string" then return tb ~= "string" or a < b
  340.     elseif tb == "string" then return false
  341.     end
  342.  
  343.     if ta == "number" then return tb ~= "number" or a < b end
  344.     return false
  345. end
  346.  
  347. local function show_function(fn, options)
  348.     local info = debug_info and debug_info(fn, "Su")
  349.  
  350.     -- Include function source position if available
  351.     local name
  352.     if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then
  353.         name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">"
  354.     else
  355.         name = tostring(fn)
  356.     end
  357.  
  358.     -- Include arguments if a Lua function and if available. Lua will report "C"
  359.     -- functions as variadic.
  360.     if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then
  361.         local args = {}
  362.         for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end
  363.         if info.isvararg then args[#args + 1] = "..." end
  364.         name = name .. "(" .. table.concat(args, ", ") .. ")"
  365.     end
  366.  
  367.     return name
  368. end
  369.  
  370. local function pretty_impl(obj, options, tracking)
  371.     local obj_type = type(obj)
  372.     if obj_type == "string" then
  373.         local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
  374.         return text(formatted, colours.red)
  375.     elseif obj_type == "number" then
  376.         return text(tostring(obj), colours.magenta)
  377.     elseif obj_type == "function" then
  378.         return text(show_function(obj, options), colours.lightGrey)
  379.     elseif obj_type ~= "table" or tracking[obj] then
  380.         return text(tostring(obj), colours.lightGrey)
  381.     elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
  382.         return text(tostring(obj))
  383.     elseif next(obj) == nil then
  384.         return braces
  385.     else
  386.         tracking[obj] = true
  387.         local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
  388.  
  389.         local length, keys, keysn = #obj, {}, 1
  390.         for k in pairs(obj) do keys[keysn], keysn = k, keysn + 1 end
  391.         table.sort(keys, key_compare)
  392.  
  393.         for i = 1, keysn - 1 do
  394.             if i > 1 then append(doc, comma) append(doc, space_line) end
  395.  
  396.             local k = keys[i]
  397.             local v = obj[k]
  398.             local ty = type(k)
  399.             if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then
  400.                 append(doc, pretty_impl(v, options, tracking))
  401.             elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
  402.                 append(doc, text(k .. " = "))
  403.                 append(doc, pretty_impl(v, options, tracking))
  404.             else
  405.                 append(doc, obracket)
  406.                 append(doc, pretty_impl(k, options, tracking))
  407.                 append(doc, cbracket)
  408.                 append(doc, pretty_impl(v, options, tracking))
  409.             end
  410.         end
  411.  
  412.         tracking[obj] = nil
  413.         return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, doc.n))), space_line, cbrace))
  414.     end
  415. end
  416.  
  417. --- Pretty-print an arbitrary object, converting it into a document.
  418. --
  419. -- This can then be rendered with @{write} or @{print}.
  420. --
  421. -- @param obj The object to pretty-print.
  422. -- @tparam[opt] { function_args = boolean, function_source = boolean } options
  423. -- Controls how various properties are displayed.
  424. --  - `function_args`: Show the arguments to a function if known (`false` by default).
  425. --  - `function_source`: Show where the function was defined, instead of
  426. --    `function: xxxxxxxx` (`false` by default).
  427. -- @treturn Doc The object formatted as a document.
  428. -- @usage Display a table on the screen
  429. --     local pretty = require "cc.pretty"
  430. --     pretty.print(pretty.pretty({ 1, 2, 3 }))
  431. local function prettyf(obj, options)
  432.     expect(2, options, "table", "nil")
  433.     options = options or {}
  434.  
  435.     local actual_options = {
  436.         function_source = field(options, "function_source", "boolean", "nil") or false,
  437.         function_args = field(options, "function_args", "boolean", "nil") or false,
  438.     }
  439.     return pretty_impl(obj, actual_options, {})
  440. end
  441.  
  442. local pretty = {
  443.     empty = empty,
  444.     space = space,
  445.     line = line,
  446.     space_line = space_line,
  447.     text = text,
  448.     concat = concat,
  449.     nest = nest,
  450.     group = group,
  451.  
  452.     write = write,
  453.     print = print,
  454.     render = render,
  455.  
  456.     pretty = prettyf,
  457. }
  458.  
  459. local monitor = peripheral.find("monitor")
  460. local oldTerm = term.redirect(monitor)
  461.  
  462. function split(str, character)
  463.   result = {}
  464.   index = 1
  465.   for s in string.gmatch(str, "[^"..character.."]+") do
  466.     result[index] = s
  467.     index = index + 1
  468.   end
  469.   return result
  470. end
  471. local hex = {"F0F0F0", "F2B233", "E57FD8", "99B2F2", "DEDE6C", "7FCC19", "F2B2CC", "4C4C4C", "999999", "4C99B2", "B266E5", "3366CC", "7F664C", "57A64E", "CC4C4C", "191919"}
  472. local rgb = {}
  473. for i=1,16,1 do
  474.   rgb[i] = {tonumber(hex[i]:sub(1, 2), 16), tonumber(hex[i]:sub(3, 4), 16), tonumber(hex[i]:sub(5, 6), 16)}
  475. end
  476. local rgb2 = {}
  477. for i=1,16,1 do
  478.   rgb2[i] = {}
  479.   for j=1,16,1 do
  480.     rgb2[i][j] = {(rgb[i][1] * 34 + rgb[j][1] * 20) / 54, (rgb[i][2] * 34 + rgb[j][2] * 20) / 54, (rgb[i][3] * 34 + rgb[j][3] * 20) / 54}
  481.   end
  482. end
  483.  
  484. colors.fromRGB = function (r, g, b)
  485.   local dist = 1e100
  486.   local d = 1e100
  487.   local color = -1
  488.   for i=1,16,1 do
  489.     d = math.sqrt((math.max(rgb[i][1], r) - math.min(rgb[i][1], r)) ^ 2 + (math.max(rgb[i][2], g) - math.min(rgb[i][2], g)) ^ 2 + (math.max(rgb[i][3], b) - math.min(rgb[i][3], b)) ^ 2)
  490.     if d < dist then
  491.       dist = d
  492.       color = i - 1
  493.     end
  494.   end
  495.   return 2 ^ color
  496. end
  497.  
  498. local ws
  499.  
  500. function main()
  501.     ws, err = http.websocket("wss://irc-ws.chat.twitch.tv")
  502.     if err then
  503.         print(err)
  504.     elseif ws then
  505.         ws.send("PASS ottomated")
  506.         ws.send("NICK justinfan"..math.random(10000, 99999))
  507.         ws.send("JOIN #"..CHAT)
  508.         ws.send("CAP REQ :twitch.tv/tags")
  509.         ws.send("CAP REQ :twitch.tv/commands")
  510.         while true do
  511.             local msg = ws.receive()
  512.             if msg == "PING :tmi.twitch.tv" then
  513.                 ws.send("PONG :tmi.twitch.tv")
  514.             else
  515.                 -- print(msg)
  516.                 local parts = split(msg, " ")
  517.                 if parts[3] == "PRIVMSG" then
  518.                     local twitchParts = split(parts[1], ";")
  519.                     local nameColor = "#ffffff"
  520.                     local displayName
  521.                     local subscriber = false
  522.                     local founder = false
  523.                     local id
  524.                     for _, part in ipairs(twitchParts) do
  525.                         if string.sub(part, 1, 12) == "@badge-info=" then
  526.                             founder = string.find(part, "founder") ~= nil
  527.                         elseif string.sub(part, 1, 6) == "color=" then
  528.                             nameColor = string.sub(part, 7, -1)
  529.                         elseif string.sub(part, 1, 13) == "display-name=" then
  530.                             displayName = string.sub(part, 14, -1)
  531.                         elseif string.sub(part, 1, 3) == "id=" then
  532.                             id = string.sub(part, 4, -1)
  533.                         elseif string.sub(part, 1, 11) == "subscriber=" then
  534.                             subscriber = string.sub(part, 12, -1) == "1"
  535.                         end
  536.                     end
  537.                     subscriber = subscriber or founder
  538.                    
  539.                     local namePalette
  540.                     if #nameColor < 1 then
  541.                         namePalette = 2^math.random(0, 15)
  542.                     else
  543.                         local hex = string.sub(nameColor, 2, -1)
  544.                         local r = tonumber(string.sub(hex, 1, 2), 16)
  545.                         local g = tonumber(string.sub(hex, 3, 4), 16)
  546.                         local b = tonumber(string.sub(hex, 5, 6), 16)
  547.                         namePalette = colors.fromRGB(r, g, b)
  548.                     end
  549.  
  550.                     if namePalette == colors.black or namePalette == colors.white then
  551.                         namePalette = colors.lightGray
  552.                     end
  553.  
  554.                     if displayName and id then
  555.                         local message = string.sub(parts[5], 2, -1)
  556.  
  557.                         for i=6,#parts do
  558.                             message = message.." "..parts[i]
  559.                         end
  560.                         if string.sub(message, 1, 7) == "ACTION" then
  561.                             message = string.sub(message, 9, -4)
  562.                         else
  563.                             message = string.sub(message, 1, -2)
  564.                         end
  565.                         if subscriber or not APENASSUB then
  566.                             pretty.print(pretty.group(pretty.text(displayName, namePalette) .. pretty.text(":", colors.white) .. pretty.space .. pretty.text(message, colors.white)))
  567.                             -- term.setTextColor(namePalette)
  568.                             -- term.write(displayName)
  569.                             -- term.setCursorPos()
  570.                             -- print(displayName, message)
  571.                         end
  572.                     end
  573.                 elseif LIMPAR and (parts[3] == "CLEARMSG" or parts[3] == "CLEARCHAT") then
  574.                     term.clear()
  575.                     term.setCursorPos(1,1)
  576.                 end
  577.             end
  578.         end
  579.     end
  580. end
  581. while true do
  582.  pcall(main)
  583.  if ws then
  584.      ws.close()
  585.  end
  586. end
  587. --term.redirect(oldTerm)
Add Comment
Please, Sign In to add comment