SHOW:
|
|
- or go back to the newest paste.
| 1 | - | --[[ |
| 1 | + | local baseUrl = "http://86.11.11.101:9090/" |
| 2 | - | IRCCC v 0.9.2 ComputerCraft IRC Client |
| 2 | + | |
| 3 | - | Matti Vapa, 2014 |
| 3 | + | |
| 4 | - | https://github.com/mattijv/IRCCC/ |
| 4 | + | |
| 5 | - | http://pastebin.com/gaSL8HZC |
| 5 | + | |
| 6 | -- JSON4Lua: JSON encoding / decoding support for the Lua language. | |
| 7 | - | This is a ComputerCraft IRC client, or more specifically a client for the qwebirc backend server. |
| 7 | + | |
| 8 | - | Unfortunately the HTTP API in ComputerCraft doesn't allow for a true IRC client (even though the |
| 8 | + | |
| 9 | - | HTTP protocol can in theory be used to communicate with an IRC server[1]). This client provides |
| 9 | + | |
| 10 | - | a work-around by interfacing with the qwebirc backend. qwebirc is a popular webchat interface and |
| 10 | + | |
| 11 | - | is relatively straightforward to work with. In addition, several IRC servers already host a webchat |
| 11 | + | |
| 12 | - | server, so there's no need to set up your own, although that remains a possibility if you can't |
| 12 | + | |
| 13 | - | find a webchat for your favourite server to connect to. |
| 13 | + | |
| 14 | ||
| 15 | - | How to use: |
| 15 | + | local errormsg = printError |
| 16 | local base = _G | |
| 17 | - | * Download with: pastebin get gaSL8HZC irc |
| 17 | + | |
| 18 | - | * Check that the baseUrl and dynamicUrl parameters are correct. Examples are provided for espernet |
| 18 | + | |
| 19 | - | and quakenet. By default the client connects to espernet. |
| 19 | + | |
| 20 | - | * Start the client with: |
| 20 | + | |
| 21 | - | irc |
| 21 | + | |
| 22 | local decode_scanString | |
| 23 | - | You can also supply an optional nick argument to set your nickname, or you can change your nick within the client with the standard /nick command. |
| 23 | + | |
| 24 | - | Currently supported commands are: |
| 24 | + | |
| 25 | local isArray | |
| 26 | - | /window N - switch to window N |
| 26 | + | |
| 27 | - | /join CHANNEL - join CHANNEL |
| 27 | + | |
| 28 | - | /part - part the current channel or private chat |
| 28 | + | |
| 29 | - | /quit - disconnect and quit |
| 29 | + | |
| 30 | - | /nick NICK - change nickname to NICK |
| 30 | + | |
| 31 | - | /query USER - start a private chat with user USER |
| 31 | + | |
| 32 | - | /help - list available commands |
| 32 | + | |
| 33 | ||
| 34 | local vtype = base.type(v) | |
| 35 | - | 1. https://www.youtube.com/watch?v=2ctRfWnisSk#t=343 |
| 35 | + | |
| 36 | -- Handle strings | |
| 37 | - | TODO: |
| 37 | + | |
| 38 | - | - color support |
| 38 | + | |
| 39 | - | - better handling of the IRC protocol |
| 39 | + | |
| 40 | - | - use window API where available |
| 40 | + | |
| 41 | -- Handle booleans | |
| 42 | - | This program is released under the MIT license. |
| 42 | + | |
| 43 | return base.tostring(v) | |
| 44 | - | ]]-- |
| 44 | + | |
| 45 | ||
| 46 | -- Handle tables | |
| 47 | - | -- look at the source of the qwebirc webchat login page and take the values |
| 47 | + | |
| 48 | - | -- for baseUrl and dynamicBaseUrl for use here |
| 48 | + | |
| 49 | - | -- examples for espernet and quakenet |
| 49 | + | |
| 50 | local bArray, maxCount = isArray(v) | |
| 51 | - | local baseUrl = "http://webchat.esper.net/" |
| 51 | + | |
| 52 | for i = 1,maxCount do | |
| 53 | - | --local baseUrl = "http://webchat.quakenet.org/" |
| 53 | + | |
| 54 | - | --local dynamicUrl = "dynamic/leibniz/" |
| 54 | + | |
| 55 | else -- An object, not an array | |
| 56 | for i,j in base.pairs(v) do | |
| 57 | if isEncodable(i) and isEncodable(j) then | |
| 58 | table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) | |
| 59 | end | |
| 60 | end | |
| 61 | end | |
| 62 | if bArray then | |
| 63 | return '[' .. table.concat(rval,',') ..']' | |
| 64 | else | |
| 65 | return '{' .. table.concat(rval,',') .. '}'
| |
| 66 | end | |
| 67 | end | |
| 68 | ||
| 69 | -- Handle null values | |
| 70 | if vtype=='function' and v==null then | |
| 71 | return 'null' | |
| 72 | end | |
| 73 | ||
| 74 | base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) | |
| 75 | end | |
| 76 | ||
| 77 | local function decode(s, startPos) | |
| 78 | startPos = startPos and startPos or 1 | |
| 79 | startPos = decode_scanWhitespace(s,startPos) | |
| 80 | base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') | |
| 81 | local curChar = string.sub(s,startPos,startPos) | |
| 82 | -- Object | |
| 83 | if curChar=='{' then
| |
| 84 | return decode_scanObject(s,startPos) | |
| 85 | end | |
| 86 | -- Array | |
| 87 | if curChar=='[' then | |
| 88 | return decode_scanArray(s,startPos) | |
| 89 | end | |
| 90 | -- Number | |
| 91 | if string.find("+-0123456789.e", curChar, 1, true) then
| |
| 92 | return decode_scanNumber(s,startPos) | |
| 93 | end | |
| 94 | -- String | |
| 95 | if curChar==[["]] or curChar==[[']] then | |
| 96 | return decode_scanString(s,startPos) | |
| 97 | end | |
| 98 | if string.sub(s,startPos,startPos+1)=='/*' then | |
| 99 | return decode(s, decode_scanComment(s,startPos)) | |
| 100 | end | |
| 101 | -- Otherwise, it must be a constant | |
| 102 | return decode_scanConstant(s,startPos) | |
| 103 | end | |
| 104 | ||
| 105 | local function null() | |
| 106 | return null -- so json.null() will also return null ;-) | |
| 107 | end | |
| 108 | ||
| 109 | ||
| 110 | function decode_scanArray(s,startPos) | |
| 111 | local array = {} -- The return value
| |
| 112 | local stringLen = string.len(s) | |
| 113 | base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) | |
| 114 | startPos = startPos + 1 | |
| 115 | -- Infinite loop for array elements | |
| 116 | repeat | |
| 117 | startPos = decode_scanWhitespace(s,startPos) | |
| 118 | base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') | |
| 119 | local curChar = string.sub(s,startPos,startPos) | |
| 120 | if (curChar==']') then | |
| 121 | return array, startPos+1 | |
| 122 | end | |
| 123 | if (curChar==',') then | |
| 124 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 125 | end | |
| 126 | base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') | |
| 127 | object, startPos = decode(s,startPos) | |
| 128 | table.insert(array,object) | |
| 129 | until false | |
| 130 | end | |
| 131 | ||
| 132 | function decode_scanComment(s, startPos) | |
| 133 | base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) | |
| 134 | local endPos = string.find(s,'*/',startPos+2) | |
| 135 | base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) | |
| 136 | return endPos+2 | |
| 137 | end | |
| 138 | ||
| 139 | function decode_scanConstant(s, startPos) | |
| 140 | local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
| |
| 141 | local constNames = {"true","false","null"}
| |
| 142 | ||
| 143 | for i,k in base.pairs(constNames) do | |
| 144 | --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
| |
| 145 | if string.sub(s,startPos, startPos + string.len(k) -1 )==k then | |
| 146 | return consts[k], startPos + string.len(k) | |
| 147 | end | |
| 148 | end | |
| 149 | base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) | |
| 150 | end | |
| 151 | ||
| 152 | function decode_scanNumber(s,startPos) | |
| 153 | local endPos = startPos+1 | |
| 154 | local stringLen = string.len(s) | |
| 155 | local acceptableChars = "+-0123456789.e" | |
| 156 | while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) | |
| 157 | and endPos<=stringLen | |
| 158 | ) do | |
| 159 | endPos = endPos + 1 | |
| 160 | end | |
| 161 | local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) | |
| 162 | local stringEval = base.loadstring(stringValue) | |
| 163 | base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) | |
| 164 | return stringEval(), endPos | |
| 165 | end | |
| 166 | ||
| 167 | function decode_scanObject(s,startPos) | |
| 168 | local object = {}
| |
| 169 | local stringLen = string.len(s) | |
| 170 | local key, value | |
| 171 | base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
| |
| 172 | startPos = startPos + 1 | |
| 173 | repeat | |
| 174 | startPos = decode_scanWhitespace(s,startPos) | |
| 175 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') | |
| 176 | local curChar = string.sub(s,startPos,startPos) | |
| 177 | if (curChar=='}') then | |
| 178 | return object,startPos+1 | |
| 179 | end | |
| 180 | if (curChar==',') then | |
| 181 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 182 | end | |
| 183 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') | |
| 184 | -- Scan the key | |
| 185 | key, startPos = decode(s,startPos) | |
| 186 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 187 | startPos = decode_scanWhitespace(s,startPos) | |
| 188 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 189 | base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) | |
| 190 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 191 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 192 | value, startPos = decode(s,startPos) | |
| 193 | object[key]=value | |
| 194 | until false -- infinite loop while key-value pairs are found | |
| 195 | end | |
| 196 | ||
| 197 | function decode_scanString(s,startPos) | |
| 198 | base.assert(startPos, 'decode_scanString(..) called without start position') | |
| 199 | local startChar = string.sub(s,startPos,startPos) | |
| 200 | base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') | |
| 201 | local escaped = false | |
| 202 | local endPos = startPos + 1 | |
| 203 | local bEnded = false | |
| 204 | local stringLen = string.len(s) | |
| 205 | repeat | |
| 206 | local curChar = string.sub(s,endPos,endPos) | |
| 207 | -- Character escaping is only used to escape the string delimiters | |
| 208 | if not escaped then | |
| 209 | if curChar==[[\]] then | |
| 210 | escaped = true | |
| 211 | else | |
| 212 | bEnded = curChar==startChar | |
| 213 | end | |
| 214 | else | |
| 215 | -- If we're escaped, we accept the current character come what may | |
| 216 | escaped = false | |
| 217 | end | |
| 218 | endPos = endPos + 1 | |
| 219 | base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) | |
| 220 | until bEnded | |
| 221 | local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) | |
| 222 | local stringEval = base.loadstring(stringValue) | |
| 223 | base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) | |
| 224 | return stringEval(), endPos | |
| 225 | end | |
| 226 | ||
| 227 | function decode_scanWhitespace(s,startPos) | |
| 228 | local whitespace=" \n\r\t" | |
| 229 | local stringLen = string.len(s) | |
| 230 | while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do | |
| 231 | startPos = startPos + 1 | |
| 232 | end | |
| 233 | return startPos | |
| 234 | end | |
| 235 | ||
| 236 | function encodeString(s) | |
| 237 | s = string.gsub(s,'\\','\\\\') | |
| 238 | s = string.gsub(s,'"','\\"') | |
| 239 | s = string.gsub(s,'\n','\\n') | |
| 240 | s = string.gsub(s,'\t','\\t') | |
| 241 | return s | |
| 242 | end | |
| 243 | ||
| 244 | function isArray(t) | |
| 245 | -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable | |
| 246 | -- (with the possible exception of 'n') | |
| 247 | local maxIndex = 0 | |
| 248 | for k,v in base.pairs(t) do | |
| 249 | if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair | |
| 250 | if (not isEncodable(v)) then return false end -- All array elements must be encodable | |
| 251 | maxIndex = math.max(maxIndex,k) | |
| 252 | else | |
| 253 | if (k=='n') then | |
| 254 | if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements | |
| 255 | else -- Else of (k=='n') | |
| 256 | if isEncodable(v) then return false end | |
| 257 | end -- End of (k~='n') | |
| 258 | end -- End of k,v not an indexed pair | |
| 259 | end -- End of loop across all pairs | |
| 260 | return true, maxIndex | |
| 261 | end | |
| 262 | ||
| 263 | function isEncodable(o) | |
| 264 | local t = base.type(o) | |
| 265 | return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) | |
| 266 | end | |
| 267 | ||
| 268 | -------------------------------------------------------------------------------- | |
| 269 | -------------------------------------------------------------------------------- | |
| 270 | ||
| 271 | -- ComputerCraft IRC client code begins here | |
| 272 | ||
| 273 | -------------------------------------------------------------------------------- | |
| 274 | -------------------------------------------------------------------------------- | |
| 275 | ||
| 276 | local defaultNick = "cc-irc-client"..tostring(math.random(1,1000)) | |
| 277 | local nick = defaultNick | |
| 278 | local password = nil | |
| 279 | local newNick = "" | |
| 280 | ||
| 281 | local counter = 0 -- used by the qwebirc protocol for something | |
| 282 | local sessionID = "" -- as is this, although a bit more self evident | |
| 283 | ||
| 284 | local currentChannel = "status" | |
| 285 | local quitReason = "" | |
| 286 | ||
| 287 | local BUFFERSIZE = 100 -- keep this many lines in each windows history | |
| 288 | local offset = 0 -- for scrolling, not implemented | |
| 289 | ||
| 290 | local w,h = term.getSize() | |
| 291 | local legacy = true --(window == nil) CC v < 1.6 doesn't have the window API which would be useful, but maybe later | |
| 292 | local windows = {}
| |
| 293 | local winnumbers = {}
| |
| 294 | local seen = {}
| |
| 295 | if not legacy then | |
| 296 | local origTerm = term.current() | |
| 297 | windows["input"] = window.create(origTerm,1,h-1,w,2) | |
| 298 | windows["status"] = window.create(origTerm,1,1,w,h-2) | |
| 299 | windows["current"] = windows["status"] | |
| 300 | else | |
| 301 | windows["status"] = {}
| |
| 302 | windows["current"] = windows["status"] | |
| 303 | end | |
| 304 | table.insert(winnumbers,"status") | |
| 305 | seen[windows["status"]] = true | |
| 306 | local lock = "off" | |
| 307 | ||
| 308 | local win2num = function(win) | |
| 309 | for k,v in pairs(winnumbers) do | |
| 310 | if v == win then return k end | |
| 311 | end | |
| 312 | return nil | |
| 313 | end | |
| 314 | ||
| 315 | --os.loadAPI("json")
| |
| 316 | ||
| 317 | local post = function(url,data) | |
| 318 | local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed... | |
| 319 | local resp = http.post(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data) | |
| 320 | counter = counter + 1 | |
| 321 | return resp | |
| 322 | end | |
| 323 | ||
| 324 | -- better responsiveness with asynchronous methods as we usually let the recv couroutine handle the responses | |
| 325 | local request = function(url,data) | |
| 326 | local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed... | |
| 327 | http.request(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data) | |
| 328 | counter = counter + 1 | |
| 329 | end | |
| 330 | ||
| 331 | -- these special URLs are used by the webchat server for different methods | |
| 332 | local send = function(data) | |
| 333 | return request("p","&s="..sessionID.."&c="..textutils.urlEncode(data))
| |
| 334 | end | |
| 335 | ||
| 336 | local recv = function() | |
| 337 | return post("s","&s="..sessionID)
| |
| 338 | end | |
| 339 | ||
| 340 | local connect = function(password) | |
| 341 | if password ~= nil then | |
| 342 | return post("n","&nick="..nick.."&password="..password)
| |
| 343 | else | |
| 344 | return post("n","&nick="..nick)
| |
| 345 | end | |
| 346 | end | |
| 347 | ||
| 348 | -- some helper functions | |
| 349 | local pong = function(data) | |
| 350 | return send("PONG :"..data)
| |
| 351 | end | |
| 352 | ||
| 353 | local quit = function(_reason) | |
| 354 | local reason = _reason or "" | |
| 355 | return send("QUIT :"..reason)
| |
| 356 | end | |
| 357 | ||
| 358 | -- lua default string methods suck :/ | |
| 359 | local split = function(str,sep) | |
| 360 | local sep, fields = sep or ":", {}
| |
| 361 | local pattern = string.format("([^%s]+)", sep)
| |
| 362 | str:gsub(pattern, function(c) fields[#fields+1] = c end) | |
| 363 | return fields | |
| 364 | end | |
| 365 | ||
| 366 | local exit = function(reason) | |
| 367 | local r = reason or "" | |
| 368 | term.clear() | |
| 369 | term.setCursorPos(1,1) | |
| 370 | print(r) | |
| 371 | end | |
| 372 | ||
| 373 | -- for debug | |
| 374 | local writeLine | |
| 375 | writeLine = function(data,line) | |
| 376 | if not (type(data) == "table") then | |
| 377 | return tostring(data) | |
| 378 | end | |
| 379 | for i=1,#data do | |
| 380 | if type(data[i]) == "table" then | |
| 381 | line = writeLine(data[i],line) | |
| 382 | elseif data[i] ~= nick then | |
| 383 | line = line..data[i].." " | |
| 384 | end | |
| 385 | end | |
| 386 | return line | |
| 387 | end | |
| 388 | ||
| 389 | -- some IRC protocol codes we handle "properly" | |
| 390 | local codes = {}
| |
| 391 | codes["371"] = "RPL_INFO" | |
| 392 | codes["374"] = "RPL_ENDINFO" | |
| 393 | codes["375"] = "RPL_MOTDSTART" | |
| 394 | codes["372"] = "RPL_MOTD" | |
| 395 | codes["376"] = "RPL_ENDOFMOTD" | |
| 396 | codes["352"] = "RPL_WHOREPLY" | |
| 397 | ||
| 398 | commands = {}
| |
| 399 | commands["join"] = function(input) | |
| 400 | if not input[2] then | |
| 401 | errormsg("No channel specified!")
| |
| 402 | return true | |
| 403 | end | |
| 404 | local channel = input[2]:lower() | |
| 405 | if channel:sub(1,1) ~= "#" then | |
| 406 | errormsg("Invalid channel name!")
| |
| 407 | return true | |
| 408 | end | |
| 409 | currentChannel = channel | |
| 410 | if legacy then | |
| 411 | windows[channel] = {}
| |
| 412 | end | |
| 413 | windows["current"] = windows[currentChannel] | |
| 414 | table.insert(winnumbers,channel) | |
| 415 | seen[windows[channel]] = true | |
| 416 | send("JOIN "..channel)
| |
| 417 | return true | |
| 418 | end | |
| 419 | ||
| 420 | commands["who"] = function(input) | |
| 421 | local channel = input[2] | |
| 422 | send("WHO "..channel)
| |
| 423 | return true | |
| 424 | end | |
| 425 | ||
| 426 | commands["whois"] = function(input) | |
| 427 | local user = input[2] | |
| 428 | send("WHOIS "..user)
| |
| 429 | return true | |
| 430 | end | |
| 431 | ||
| 432 | commands["part"] = function(input) | |
| 433 | local channel = currentChannel | |
| 434 | if channel == "status" then | |
| 435 | errormsg("Can't part status window!")
| |
| 436 | else | |
| 437 | local nwin | |
| 438 | for i,v in pairs(winnumbers) do | |
| 439 | if v == channel then | |
| 440 | nwin = i | |
| 441 | break | |
| 442 | end | |
| 443 | end | |
| 444 | table.remove(winnumbers,nwin) | |
| 445 | seen[windows[channel]] = nil | |
| 446 | windows[channel] = nil | |
| 447 | currentChannel = winnumbers[#winnumbers] | |
| 448 | print(currentChannel) | |
| 449 | windows["current"] = windows[currentChannel] | |
| 450 | - | -- add lines to window |
| 450 | + | seen[windows["current"]] = true |
| 451 | - | local writeToWin = function(win, s) |
| 451 | + | if channel:sub(1,1) == "#" then |
| 452 | - | if win ~= windows["current"] then |
| 452 | + | send("PART "..channel)
|
| 453 | - | seen[win] = false |
| 453 | + | |
| 454 | end | |
| 455 | - | if legacy then |
| 455 | + | |
| 456 | - | while #s > w do |
| 456 | + | |
| 457 | - | table.insert(win,s:sub(1,w)) |
| 457 | + | |
| 458 | - | s = s:sub(w+1) |
| 458 | + | |
| 459 | quit(input[2]) | |
| 460 | - | table.insert(win,s) |
| 460 | + | return false |
| 461 | - | while #win > BUFFERSIZE do |
| 461 | + | |
| 462 | - | table.remove(win,1) |
| 462 | + | |
| 463 | commands["window"] = function(input) | |
| 464 | local nwin = tonumber(input[2]) | |
| 465 | if not nwin then | |
| 466 | errormsg("Invalid window number!")
| |
| 467 | elseif nwin > #winnumbers or nwin < 1 then | |
| 468 | errormsg("Invalid window number!")
| |
| 469 | - | if legacy then |
| 469 | + | |
| 470 | - | writeToWin(windows["status"],msg) |
| 470 | + | windows["current"] = windows[winnumbers[nwin]] |
| 471 | seen[windows["current"]] = true | |
| 472 | currentChannel = winnumbers[nwin] | |
| 473 | end | |
| 474 | - | -- draw the current window to the screen |
| 474 | + | |
| 475 | - | local drawWin = function(win) |
| 475 | + | |
| 476 | - | if legacy then |
| 476 | + | |
| 477 | - | local x,y = term.getCursorPos() |
| 477 | + | |
| 478 | - | for i = 2,h-2 do |
| 478 | + | local nickname = input[2] |
| 479 | - | term.setCursorPos(1,i) |
| 479 | + | if nickname then |
| 480 | - | term.clearLine() |
| 480 | + | send("NICK "..nickname)
|
| 481 | newNick = nickname | |
| 482 | - | if #win > 0 then |
| 482 | + | writeToWin(windows["status"],"Changed nick to: "..newNick) |
| 483 | - | local i = math.max(1,#win-h+4) |
| 483 | + | |
| 484 | - | iend = #win |
| 484 | + | errormsg("No nickname given.")
|
| 485 | - | local row = 2 |
| 485 | + | |
| 486 | - | while i <= iend do |
| 486 | + | |
| 487 | - | term.setCursorPos(1,row) |
| 487 | + | |
| 488 | - | local line = win[i] |
| 488 | + | |
| 489 | - | --[[ |
| 489 | + | |
| 490 | - | local n = 0 |
| 490 | + | local user = input[2] |
| 491 | - | while #line > w do |
| 491 | + | if user then |
| 492 | - | term.write(line:sub(1,w)) |
| 492 | + | currentChannel = user |
| 493 | - | row = row + 1 |
| 493 | + | if legacy then |
| 494 | - | n = n + 1 |
| 494 | + | windows[user] = {}
|
| 495 | - | term.setCursorPos(1,row) |
| 495 | + | |
| 496 | - | line = line:sub(w+1) |
| 496 | + | windows["current"] = windows[currentChannel] |
| 497 | - | end |
| 497 | + | table.insert(winnumbers,user) |
| 498 | - | ]]-- |
| 498 | + | seen[windows[user]] = true |
| 499 | - | term.write(line) |
| 499 | + | --send("JOIN "..channel)
|
| 500 | - | row = row + 1 |
| 500 | + | |
| 501 | - | i = i + 1 |
| 501 | + | errormsg("No username given.")
|
| 502 | end | |
| 503 | return true | |
| 504 | - | term.setCursorPos(x,y) |
| 504 | + | |
| 505 | ||
| 506 | commands["help"] = function(input) | |
| 507 | writeToWin(windows["status"],"Available commands:") | |
| 508 | - | -- draw the separator with list of windows and indicators of activity |
| 508 | + | for k,v in pairs(commands) do |
| 509 | writeToWin(windows["status"],"- "..k) | |
| 510 | - | local drawSeparator = function() |
| 510 | + | |
| 511 | - | if legacy then |
| 511 | + | |
| 512 | - | local x,y = term.getCursorPos() |
| 512 | + | |
| 513 | - | local nwin = #winnumbers |
| 513 | + | |
| 514 | - | --local left = math.floor(0.5*(w-2*nwin+1)) |
| 514 | + | |
| 515 | - | --local right = w-left-2*nwin-1 |
| 515 | + | |
| 516 | - | term.setCursorPos(1,h-1) |
| 516 | + | |
| 517 | - | term.write(string.rep("-",w))
|
| 517 | + | |
| 518 | - | local newcount = 0 |
| 518 | + | |
| 519 | - | for i = 1,nwin do |
| 519 | + | |
| 520 | - | if not seen[windows[winnumbers[i]]] then |
| 520 | + | |
| 521 | - | newcount = newcount + 1 |
| 521 | + | |
| 522 | alias["qr"] = "query" | |
| 523 | alias["h"] = "help" | |
| 524 | - | local start = math.ceil(0.5*(w-(2*nwin+1+newcount))) |
| 524 | + | |
| 525 | - | term.setCursorPos(start,h-1) |
| 525 | + | |
| 526 | - | for i=1,nwin do |
| 526 | + | |
| 527 | - | term.write(" ")
|
| 527 | + | print(msg) |
| 528 | - | if winnumbers[i] == currentChannel then |
| 528 | + | |
| 529 | - | term.setBackgroundColor(colors.white) |
| 529 | + | |
| 530 | - | term.setTextColor(colors.black) |
| 530 | + | |
| 531 | dat = data | |
| 532 | - | term.write(string.format("%d",i))
|
| 532 | + | |
| 533 | - | if not seen[windows[winnumbers[i]]] then |
| 533 | + | |
| 534 | - | term.write("+")
|
| 534 | + | |
| 535 | pong(data[i][4][1]) | |
| 536 | - | term.setBackgroundColor(colors.black) |
| 536 | + | |
| 537 | - | term.setTextColor(colors.white) |
| 537 | + | |
| 538 | local sender = senderDetails:sub(1,senderDetails:find("!")-1)
| |
| 539 | - | term.write(" ")
|
| 539 | + | |
| 540 | - | term.setCursorPos(x,y) |
| 540 | + | |
| 541 | channel = sender | |
| 542 | end | |
| 543 | local msg = data[i][4][2] | |
| 544 | - | -- top banner with channel name |
| 544 | + | print("<"..sender.."> "..msg)
|
| 545 | - | local drawBanner = function() |
| 545 | + | elseif id == "376" then |
| 546 | - | local x,y = term.getCursorPos() |
| 546 | + | print("Joining channel")
|
| 547 | send("JOIN #" .. channel)
| |
| 548 | - | term.clearLine() |
| 548 | + | |
| 549 | - | --local banner = "["..currentChannel.."]" |
| 549 | + | if newNick ~= "" then |
| 550 | - | local banner = currentChannel |
| 550 | + | local name = data[i][3]:sub(1,data[i][3]:find("!")-1)
|
| 551 | - | local start = math.ceil(0.5*(w-#banner)) |
| 551 | + | if name == nick then |
| 552 | - | term.setCursorPos(start,1) |
| 552 | + | nick = newNick |
| 553 | - | term.write(banner) |
| 553 | + | newNick = "" |
| 554 | - | term.setCursorPos(x,y) |
| 554 | + | end |
| 555 | end | |
| 556 | elseif id == "433" then | |
| 557 | - | local drawInput = function() |
| 557 | + | print("Nickname already in use!")
|
| 558 | - | if legacy then |
| 558 | + | newNick = "" |
| 559 | - | term.setCursorPos(1,h) |
| 559 | + | elseif codes[id] then |
| 560 | - | term.clearLine() |
| 560 | + | print(data[i][4][2]) |
| 561 | - | term.write("> ")
|
| 561 | + | |
| 562 | end | |
| 563 | end | |
| 564 | ||
| 565 | ||
| 566 | local receive = function() | |
| 567 | resp = recv() | |
| 568 | while resp do | |
| 569 | _data = resp.readAll() | |
| 570 | if #_data > 0 then | |
| 571 | data = decode(_data) | |
| 572 | handleResponse(data) | |
| 573 | end | |
| 574 | - | --print(textutils.serialize(data[i])) |
| 574 | + | |
| 575 | - | --print(channel) |
| 575 | + | |
| 576 | quitReason = "Disconnected!" | |
| 577 | end | |
| 578 | - | if not windows[sender] then |
| 578 | + | |
| 579 | - | if legacy then |
| 579 | + | local nick = "twitchplayzcomputercraft" |
| 580 | - | windows[channel] = {}
|
| 580 | + | local password = "oauth:pq386z6ttls22h7bmsqbiuil1i2ns3" |
| 581 | - | end |
| 581 | + | local channel = "rockettek" |
| 582 | - | table.insert(winnumbers,channel) |
| 582 | + | |
| 583 | - | seen[windows[channel]] = false |
| 583 | + | local resp = connect(password) |
| 584 | - | end |
| 584 | + | if not resp then printError("Unable to connect!") return end
|
| 585 | local _data = resp.readAll() | |
| 586 | local data = decode(_data) | |
| 587 | - | if legacy then |
| 587 | + | local sessionID = data[2] |
| 588 | - | writeToWin(windows[channel],"<"..sender.."> "..msg) |
| 588 | + | |
| 589 | ||
| 590 | - | --print("<"..sender.."> "..msg)
|
| 590 | + | receive() |