View difference between Paste ID: R9QMwJZ6 and 6jwudu7s
SHOW: | | - or go back to the newest paste.
1
CHATROOM = "ash_on_lol"
2
SUBSONLY = false
3
CLEAR_MESSAGES = 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 #"..CHATROOM)
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 SUBSONLY 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 CLEAR_MESSAGES 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)