View difference between Paste ID: KBxAJASx and FtKY6prR
SHOW: | | - or go back to the newest paste.
1
local component = require("component")
2
local computer = require("computer")
3
if not component.isAvailable("internet") then
4
  io.stderr:write("OpenIRC requires an Internet Card to run!\n")
5
  return
6
end
7
8
local glasses = component.openperipheral_bridge
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
local w, h = component.gpu.getResolution()
15
local chat, text_color, acolor = {}, 0x00ff00
16
17
local args, options = shell.parse(...)
18
if #args < 1 then
19
  print("Usage: irc <nickname> [server[:port]]")
20
  return
21
end
22
23
local nick = args[1]
24
local host = args[2] or "irc.esper.net:6667"
25
local size = 1
26
27
if not host:find(":") then
28
  host = host .. ":6667"
29
end
30
31
local sock, reason = internet.open(host)
32
if not sock then
33
  io.stderr:write(reason .. "\n")
34
  return
35
end
36
37
local function draw()
38
  glasses.clear()
39
  glasses.addBox(1, 1, 312*size*2, #chat*size*10+(size*3), 0x000000, 0.5)
40
  for i = 1, #chat do
41
    if chat[i]:find(nick) then
42
      acolor = 0xffff00
43
    end
44
    glasses.addText(3, size*10*i-(size*6), chat[i], acolor).setScale(size)
45
    acolor = text_color
46
  end
47
  glasses.sync()
48
end
49
50
local function print(message, overwrite)
51
  for i = 1, #message, 104 do
52
    table.insert(chat, message:sub(i, i+104))
53
  end
54
  if #chat > 10 then
55
    table.remove(chat, 1)
56
  end
57
  draw()
58
  local line
59
  repeat
60
    line, message = text.wrap(text.trim(message), w, w)
61
    if not overwrite then
62
      component.gpu.copy(1, 1, w, h - 1, 0, -1)
63
    end
64
    overwrite = false
65
    component.gpu.fill(1, h-1, w, 1, " ")
66
    component.gpu.set(1, h-1, line)
67
  until not message or message == ""
68
end
69
70
function autocreate(table, key)
71
  table[key] = {}
72
  return table[key]
73
end
74
75
local function name(identity)
76
  return identity and identity:match("^[^!]+") or identity or "Anonymous"
77
end
78
79
local callback = nil
80
local whois = setmetatable({}, {__index=autocreate})
81
local names = setmetatable({}, {__index=autocreate})
82
local timer
83
84
local ignore = {
85
  [213]=true, [214]=true, [215]=true, [216]=true, [217]=true,
86
  [218]=true, [231]=true, [232]=true, [233]=true, [240]=true,
87
  [241]=true, [244]=true, [244]=true, [246]=true, [247]=true,
88
  [250]=true, [300]=true, [316]=true, [361]=true, [362]=true,
89
  [363]=true, [373]=true, [384]=true, [492]=true,
90
  [265]=true, [266]=true, [330]=true
91
}
92
93
local commands = {
94
--Replys
95
  RPL_WELCOME = "001",
96
  RPL_YOURHOST = "002",
97
  RPL_CREATED = "003",
98
  RPL_MYINFO = "004",
99
  RPL_BOUNCE = "005",
100
  RPL_LUSERCLIENT = "251",
101
  RPL_LUSEROP = "252",
102
  RPL_LUSERUNKNOWN = "253",
103
  RPL_LUSERCHANNELS = "254",
104
  RPL_LUSERME = "255",
105
  RPL_AWAY = "301",
106
  RPL_UNAWAY = "305",
107
  RPL_NOWAWAY = "306",
108
  RPL_WHOISUSER = "311",
109
  RPL_WHOISSERVER = "312",
110
  RPL_WHOISOPERATOR = "313",
111
  RPL_WHOISIDLE = "317",
112
  RPL_ENDOFWHOIS = "318",
113
  RPL_WHOISCHANNELS = "319",
114
  RPL_CHANNELMODEIS = "324",
115
  RPL_NOTOPIC = "331",
116
  RPL_TOPIC = "332",
117
  RPL_NAMREPLY = "353",
118
  RPL_ENDOFNAMES = "366",
119
  RPL_MOTDSTART = "375",
120
  RPL_MOTD = "372",
121
  RPL_ENDOFMOTD = "376",
122
  RPL_WHOISSECURE = "671",
123
  RPL_HELPSTART = "704",
124
  RPL_HELPTXT = "705",
125
  RPL_ENDOFHELP = "706",
126
  RPL_UMODEGMSG = "718",
127
  
128
--Errors
129
  ERR_BANLISTFULL = "478",
130
  ERR_CHANNELISFULL = "471",
131
  ERR_UNKNOWNMODE = "472",
132
  ERR_INVITEONLYCHAN = "473",
133
  ERR_BANNEDFROMCHAN = "474",
134
  ERR_CHANOPRIVSNEEDED = "482",
135
  ERR_UNIQOPRIVSNEEDED = "485",
136
  ERR_USERNOTINCHANNEL = "441",
137
  ERR_NOTONCHANNEL = "442",
138
  ERR_NICKCOLLISION = "436",
139
  ERR_NICKNAMEINUSE = "433",
140
  ERR_ERRONEUSNICKNAME = "432",
141
  ERR_WASNOSUCHNICK = "406",
142
  ERR_TOOMANYCHANNELS = "405",
143
  ERR_CANNOTSENDTOCHAN = "404",
144
  ERR_NOSUCHCHANNEL = "403",
145
  ERR_NOSUCHNICK = "401",
146
  ERR_MODELOCK = "742"
147
}
148
149
local function handleCommand(prefix, command, args, message)
150
  if command == "PING" then
151
    sock:write(string.format("PONG :%s\r\n", message))
152
    sock:flush()
153
  elseif command == "NICK" then
154
    local oldNick, newNick = name(prefix), tostring(args[1] or message)
155
    if oldNick == nick then
156
      nick = newNick
157
    end
158
    print(oldNick .. " is now known as " .. newNick .. ".")
159
  elseif command == "MODE" then
160
    if #args == 2 then
161
      print("[" .. args[1] .. "] " .. name(prefix) .. " set mode".. ( #args[2] > 2 and "s" or "" ) .. " " .. tostring(args[2] or message) .. ".")
162
    else
163
      local setmode = {}
164
      local cumode = "+"
165
      args[2]:gsub(".", function(char)
166
        if char == "-" or char == "+" then
167
          cumode = char
168
        else
169
          table.insert(setmode, {cumode, char})
170
        end
171
      end)
172
      local d = {}
173
      local users = {}
174
      for i = 3, #args do
175
        users[i-2] = args[i]
176
      end
177
      users[#users+1] = message
178
      local last
179
      local ctxt = ""
180
      for c = 1, #users do
181
        if not setmode[c] then
182
          break
183
        end
184
        local mode = setmode[c][2]
185
        local pfx = setmode[c][1]=="+"
186
        local key = mode == "o" and (pfx and "opped" or "deoped") or
187
          mode == "v" and (pfx and "voiced" or "devoiced") or
188
          mode == "q" and (pfx and "quieted" or "unquieted") or
189
          mode == "b" and (pfx and "banned" or "unbanned") or
190
          "set " .. setmode[c][1] .. mode .. " on"
191
        if last ~= key then
192
          if last then
193
            print(ctxt)
194
          end
195
          ctxt = "[" .. args[1] .. "] " .. name(prefix) .. " " .. key
196
          last = key
197
        end
198
        ctxt = ctxt .. " " .. users[c]
199
      end
200
      if #ctxt > 0 then
201
        print(ctxt)
202
      end
203
    end
204
  elseif command == "QUIT" then
205
    print(name(prefix) .. " quit (" .. (message or "Quit") .. ").")
206
  elseif command == "JOIN" then
207
    print("[" .. args[1] .. "] " .. name(prefix) .. " entered the room.")
208
  elseif command == "PART" then
209
    print("[" .. args[1] .. "] " .. name(prefix) .. " has left the room (quit: " .. (message or "Quit") .. ").")
210
  elseif command == "TOPIC" then
211
    print("[" .. args[1] .. "] " .. name(prefix) .. " has changed the topic to: " .. message)
212
  elseif command == "KICK" then
213
    print("[" .. args[1] .. "] " .. name(prefix) .. " kicked " .. args[2])
214
  elseif command == "PRIVMSG" then
215
    local ctcp = message:match("^\1(.-)\1$")
216
    if ctcp then
217
      print("[" .. name(prefix) .. "] CTCP " .. ctcp)
218
      local ctcp, param = ctcp:match("^(%S+) ?(.-)$")
219
      ctcp = ctcp:upper()
220
      if ctcp == "TIME" then
221
        sock:write("NOTICE " .. name(prefix) .. " :\001TIME " .. os.date() .. "\001\r\n")
222
        sock:flush()
223
      elseif ctcp == "VERSION" then
224
        sock:write("NOTICE " .. name(prefix) .. " :\001VERSION Minecraft/OpenComputers Lua 5.2\001\r\n")
225
        sock:flush()
226
      elseif ctcp == "PING" then
227
        sock:write("NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001\r\n")
228
        sock:flush()
229
      end
230
    else
231
      if string.find(message, nick) then
232
        computer.beep()
233
      end
234
      if string.find(message, "\001ACTION") then
235
        print("[" .. args[1] .. "] " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
236
      else
237
        print("[" .. args[1] .. "] " .. name(prefix) .. ": " .. message)
238
      end
239
    end
240
  elseif command == "NOTICE" then
241
    print("[NOTICE] " .. message)
242
  elseif command == "ERROR" then
243
    print("[ERROR] " .. message)
244
  elseif tonumber(command) and ignore[tonumber(command)] then
245
  elseif command == commands.RPL_WELCOME then
246
    print(message)
247
  elseif command == commands.RPL_LUSERCLIENT then
248
    print(message)
249
  elseif command == commands.RPL_LUSERME then
250
    print(message)
251
  elseif command == commands.RPL_AWAY then
252
    print(string.format("%s is away: %s", name(args[1]), message))
253
  elseif command == commands.RPL_UNAWAY or command == commands.RPL_NOWAWAY then
254
    print(message)
255
  elseif command == commands.RPL_WHOISUSER then
256
    local nick = args[2]:lower()
257
    whois[nick].nick = args[2]
258
    whois[nick].user = args[3]
259
    whois[nick].host = args[4]
260
    whois[nick].realName = message
261
  elseif command == commands.RPL_WHOISSERVER then
262
    local nick = args[2]:lower()
263
    whois[nick].server = args[3]
264
    whois[nick].serverInfo = message
265
  elseif command == commands.RPL_WHOISOPERATOR then
266
    local nick = args[2]:lower()
267
    whois[nick].isOperator = true
268
  elseif command == commands.RPL_WHOISIDLE then
269
    local nick = args[2]:lower()
270
    whois[nick].idle = tonumber(args[3])
271
  elseif command == commands.RPL_WHOISSECURE then
272
    local nick = args[2]:lower()
273
    whois[nick].secureconn = "Is using a secure connection"
274
  elseif command == commands.RPL_ENDOFWHOIS then
275
    local nick = args[2]:lower()
276
    local info = whois[nick]
277
    if info.nick then print("Nick: " .. info.nick) end
278
    if info.user then print("User name: " .. info.user) end
279
    if info.realName then print("Real name: " .. info.realName) end
280
    if info.host then print("Host: " .. info.host) end
281
    if info.server then print("Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end
282
    if info.secureconn then print(info.secureconn) end
283
    if info.channels then print("Channels: " .. info.channels) end
284
    if info.idle then print("Idle for: " .. info.idle) end
285
    whois[nick] = nil
286
  elseif command == commands.RPL_WHOISCHANNELS then
287
    local nick = args[2]:lower()
288
    whois[nick].channels = message
289
  elseif command == commands.RPL_CHANNELMODEIS then
290
    print("Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")")
291
  elseif command == commands.RPL_NOTOPIC then
292
    print("No topic is set for " .. args[1] .. ".")
293
  elseif command == commands.RPL_TOPIC then
294
    print("Topic for " .. args[1] .. ": " .. message)
295
  elseif command == commands.RPL_NAMREPLY then
296
    local channel = args[3]
297
    table.insert(names[channel], message)
298
  elseif command == commands.RPL_ENDOFNAMES then
299
    local channel = args[2]
300
    print("Users on " .. channel .. ": " .. (#names[channel] > 0 and table.concat(names[channel], " ") or "none"))
301
    names[channel] = nil
302
  elseif command == commands.RPL_MOTDSTART then
303
    if options.motd then
304
      print(message .. args[1])
305
    end
306
  elseif command == commands.RPL_MOTD then
307
    if options.motd then
308
      print(message)
309
    end
310
  elseif command == commands.RPL_ENDOFMOTD then
311
  elseif command == commands.RPL_HELPSTART or 
312
  command == commands.RPL_HELPTXT or 
313
  command == commands.RPL_ENDOFHELP then
314
    print(message)
315
  elseif command == commands.ERR_BANLISTFULL or
316
  command == commands.ERR_BANNEDFROMCHAN or
317
  command == commands.ERR_CANNOTSENDTOCHAN or
318
  command == commands.ERR_CHANNELISFULL or
319
  command == commands.ERR_CHANOPRIVSNEEDED or
320
  command == commands.ERR_ERRONEUSNICKNAME or
321
  command == commands.ERR_INVITEONLYCHAN or
322
  command == commands.ERR_NICKCOLLISION or
323
  command == commands.ERR_NOSUCHNICK or
324
  command == commands.ERR_NOTONCHANNEL or
325
  command == commands.ERR_UNIQOPRIVSNEEDED or
326
  command == commands.ERR_UNKNOWNMODE or
327
  command == commands.ERR_USERNOTINCHANNEL or
328
  command == commands.ERR_WASNOSUCHNICK or
329
  command == commands.ERR_MODELOCK then
330
    print("[ERROR]: " .. message)
331
  elseif tonumber(command) and (tonumber(command) >= 200 and tonumber(command) < 400) then
332
    print("[Response " .. command .. "] " .. table.concat(args, ", ") .. ": " .. message)
333
  elseif tonumber(command) and (tonumber(command) >= 400 and tonumber(command) < 600) then
334
    print("[Error] " .. table.concat(args, ", ") .. ": " .. message)
335
  else
336
    print("Unhandled command: " .. command .. ": " .. message)
337
  end
338
end
339
340
local function check(line)
341
  if sock and line and line ~= "" then
342
    line = text.trim(line)
343
    if line:lower():sub(1,4) == "/me " then
344
      print("[" .. (target or "?") .. "] " .. nick .. " " .. line:sub(5), true)
345
    elseif line~="" then
346
      print("[" .. (target or "?") .. "] " .. nick .. ": " .. line, true)
347
    end
348
    if line:lower():sub(1, 5) == "/msg " then
349
      local user, message = line:sub(6):match("^(%S+) (.+)$")
350
      if message then
351
        message = text.trim(message)
352
      end
353
      if not user or not message or message == "" then
354
        print("Invalid use of /msg. Usage: /msg nick|channel message.")
355
        line = ""
356
      else
357
        target = user
358
        line = "PRIVMSG " .. target .. " :" .. message
359
      end
360
    elseif line:lower():sub(1, 6) == "/size " then
361
      local ssize = tonumber(line:sub(7))
362
      if type(ssize) == "number" then
363
        size = ssize
364
      end
365
    elseif line:lower():sub(1, 6) == "/join " then
366
      local channel = text.trim(line:sub(7))
367
      if not channel or channel == "" then
368
        print("Invalid use of /join. Usage: /join channel.")
369
        line = ""
370
      else
371
        target = channel
372
        line = "JOIN " .. channel
373
      end
374
    elseif line:lower():sub(1, 5) == "/lua " then
375
      local script = text.trim(line:sub(6))
376
      local result, reason = load(script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
377
      if not result then
378
        result, reason = load("return " .. script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
379
      end
380
      line = ""
381
      if not result then
382
        print("Error: " .. tostring(reason))
383
      else
384
        result, reason = pcall(result)
385
        if not result then
386
          print("Error: " .. tostring(reason))
387
        elseif type(reason) == "function" then
388
          callback = reason
389
        elseif reason then
390
          line = tostring(reason)
391
        end
392
      end
393
    elseif line:lower():sub(1,4) == "/me " then
394
      if not target then
395
        print("No default target set. Use /msg or /join to set one.")
396
        line = ""
397
      else
398
        line = "PRIVMSG " .. target .. " :\001ACTION " .. line:sub(5) .. "\001"
399
      end
400
    elseif line:sub(1, 1) == "/" then
401
      line = line:sub(2)
402
    elseif line ~= "" then
403
      if not target then
404
        print("No default target set. Use /msg or /join to set one.")
405
        line = ""
406
      else
407
        line = "PRIVMSG " .. target .. " :" .. line
408
      end
409
    end
410
    if line and line ~= "" then
411
      sock:write(line .. "\r\n")
412
      sock:flush()
413
    end
414
  end
415
end
416
417
local function keyboard(...)
418
  local data = {...}
419
  check(data[5])
420
end
421
422
event.listen('glasses_chat_command', keyboard)
423
424
local result, reason = pcall(function()
425
  term.clear()
426
  print("Welcome to OpenIRC!")
427
  sock:setTimeout(0.05)
428
  sock:write(string.format("NICK %s\r\n", nick))
429
  sock:write(string.format("USER %s 0 * :%s [OpenComputers]\r\n", nick:lower(), nick))
430
  sock:flush()
431
  timer = event.timer(0.5, function()
432
    if not sock then
433
      return false
434
    end
435
    repeat
436
      local ok, line = pcall(sock.read, sock)
437
      if ok then
438
        if not line then
439
          print("Connection lost.")
440
          sock:close()
441
          sock = nil
442
          return false
443
        end
444
        line = text.trim(line)
445
        local match, prefix = line:match("^(:(%S+) )")
446
        if match then line = line:sub(#match + 1) end
447
        local match, command = line:match("^(([^:]%S*))")
448
        if match then line = line:sub(#match + 1) end
449
        local args = {}
450
        repeat
451
          local match, arg = line:match("^( ([^:]%S*))")
452
          if match then
453
            line = line:sub(#match + 1)
454
            table.insert(args, arg)
455
          end
456
        until not match
457
        local message = line:match("^ :(.*)$")
458
459
        if callback then
460
          local result, reason = pcall(callback, prefix, command, args, message)
461
          if not result then
462
            print("Error in callback: " .. tostring(reason))
463
          end
464
        end
465
        handleCommand(prefix, command, args, message)
466
      end
467
    until not ok
468
  end, math.huge)
469
  local target = nil
470
  local history = {}
471
  repeat
472
    term.setCursor(1, h)
473
    term.write((target or "#") .. "> ")
474
    local line = term.read(history)
475
    check(line)
476
  until not sock or not line
477
end)
478
479
if sock then
480
  sock:write("QUIT\r\n")
481
  sock:close()
482
end
483
484
event.ignore('glasses_chat_command', keyboard)
485
486
if timer then
487
  event.cancel(timer)
488
end
489
490
if not result then
491
  error(reason, 0)
492
end
493
return reason