From 4c2337009b8c79b14ac10f49040ea4d9b88e8175 Mon Sep 17 00:00:00 2001 From: erumi321 <42208651+erumi321@users.noreply.github.com> Date: Sun, 27 Mar 2022 21:36:59 -0400 Subject: [PATCH] Adapt to use styx scribe Use styx scribe to not rely on x86 architecture of hades --- config.lua | 11 +- libs/lib/mime/core.dll | Bin 14848 -> 0 bytes libs/lib/socket/core.dll | Bin 43008 -> 0 bytes libs/share/ltn12.lua | 319 -------------------------- libs/share/mime.lua | 89 ------- libs/share/socket.lua | 149 ------------ libs/share/socket/ftp.lua | 329 -------------------------- libs/share/socket/headers.lua | 104 --------- libs/share/socket/http.lua | 420 ---------------------------------- libs/share/socket/smtp.lua | 256 --------------------- libs/share/socket/tp.lua | 134 ----------- libs/share/socket/url.lua | 331 --------------------------- modfile.txt | 4 +- twitch.lua | 110 ++++----- twitch_socket.py | 88 +++++++ 15 files changed, 148 insertions(+), 2196 deletions(-) delete mode 100644 libs/lib/mime/core.dll delete mode 100644 libs/lib/socket/core.dll delete mode 100644 libs/share/ltn12.lua delete mode 100644 libs/share/mime.lua delete mode 100644 libs/share/socket.lua delete mode 100644 libs/share/socket/ftp.lua delete mode 100644 libs/share/socket/headers.lua delete mode 100644 libs/share/socket/http.lua delete mode 100644 libs/share/socket/smtp.lua delete mode 100644 libs/share/socket/tp.lua delete mode 100644 libs/share/socket/url.lua create mode 100644 twitch_socket.py diff --git a/config.lua b/config.lua index 277e60b..ed3ba23 100644 --- a/config.lua +++ b/config.lua @@ -1,13 +1,22 @@ --[[ Config for Twitch Integration ]]-- -- Written By: Kriogenic +-- Adapted to StyxScribe by: erumi321 -- USAGE INSTRUCTIONS -- Enter your username in all lowercase in the Username field making sure to keep the " around it +-- Enter your OAuth token in the OAuth field, making sure that it has "oauth:" before the rest of the code +-- Change SendConfirmMessage to true or false, if true when a viewer makes a vote your account will send an automated message saying, "Thanks for the vote {user}" -- Enter the cooldown time between votes in seconds in the TwitchIntegration.Config.TimeBetweenVotes field -- Enter the time in seconds your viewers have to make their votes in the VotingTime field -- DO NOT TOUCH ANYTHING ELSE xD + +-- YOU MUST USE subsume_hades.py IN THE Hades FOLDER TO RUN THIS MOD! IT WILL NOT WORK OTHERWISE. +-- You will probably want to make a shortcut to the script as thats the easiest way to run it + TwitchIntegration.Config = { - Username = "0lrevolution", + Username = "", --your channel name (do not put # before it, that is handeled by the mod. If you have no idea why you would do this, continue not doing this) + OAuth = "", --Example: "oauth:123abc456def789ghi0123jkl456mno" + SendConfirmMessage = true, TimeBetweenVotes = 1, VotingTime = 10, OfferedChoices = 6, diff --git a/libs/lib/mime/core.dll b/libs/lib/mime/core.dll deleted file mode 100644 index 3e65e7cfdcc9e7cf6a378c0e862a6239f21b47a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14848 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 YyU}j4Pqe4o&2~G9$-n>pAE3bh2GF|)EdT%j diff --git a/libs/share/ltn12.lua b/libs/share/ltn12.lua deleted file mode 100644 index afa735d..0000000 --- a/libs/share/ltn12.lua +++ /dev/null @@ -1,319 +0,0 @@ ------------------------------------------------------------------------------ --- LTN12 - Filters, sources, sinks and pumps. --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module ------------------------------------------------------------------------------ -local string = require("string") -local table = require("table") -local unpack = unpack or table.unpack -local base = _G -local _M = {} -if module then -- heuristic for exporting a global package table - ltn12 = _M -end -local filter,source,sink,pump = {},{},{},{} - -_M.filter = filter -_M.source = source -_M.sink = sink -_M.pump = pump - -local unpack = unpack or table.unpack -local select = base.select - --- 2048 seems to be better in windows... -_M.BLOCKSIZE = 2048 -_M._VERSION = "LTN12 1.0.3" - ------------------------------------------------------------------------------ --- Filter stuff ------------------------------------------------------------------------------ --- returns a high level filter that cycles a low-level filter -function filter.cycle(low, ctx, extra) - base.assert(low) - return function(chunk) - local ret - ret, ctx = low(ctx, chunk, extra) - return ret - end -end - --- chains a bunch of filters together --- (thanks to Wim Couwenberg) -function filter.chain(...) - local arg = {...} - local n = base.select('#',...) - local top, index = 1, 1 - local retry = "" - return function(chunk) - retry = chunk and retry - while true do - if index == top then - chunk = arg[index](chunk) - if chunk == "" or top == n then return chunk - elseif chunk then index = index + 1 - else - top = top+1 - index = top - end - else - chunk = arg[index](chunk or "") - if chunk == "" then - index = index - 1 - chunk = retry - elseif chunk then - if index == n then return chunk - else index = index + 1 end - else base.error("filter returned inappropriate nil") end - end - end - end -end - ------------------------------------------------------------------------------ --- Source stuff ------------------------------------------------------------------------------ --- create an empty source -local function empty() - return nil -end - -function source.empty() - return empty -end - --- returns a source that just outputs an error -function source.error(err) - return function() - return nil, err - end -end - --- creates a file source -function source.file(handle, io_err) - if handle then - return function() - local chunk = handle:read(_M.BLOCKSIZE) - if not chunk then handle:close() end - return chunk - end - else return source.error(io_err or "unable to open file") end -end - --- turns a fancy source into a simple source -function source.simplify(src) - base.assert(src) - return function() - local chunk, err_or_new = src() - src = err_or_new or src - if not chunk then return nil, err_or_new - else return chunk end - end -end - --- creates string source -function source.string(s) - if s then - local i = 1 - return function() - local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) - i = i + _M.BLOCKSIZE - if chunk ~= "" then return chunk - else return nil end - end - else return source.empty() end -end - --- creates table source -function source.table(t) - base.assert('table' == type(t)) - local i = 0 - return function() - i = i + 1 - return t[i] - end -end - --- creates rewindable source -function source.rewind(src) - base.assert(src) - local t = {} - return function(chunk) - if not chunk then - chunk = table.remove(t) - if not chunk then return src() - else return chunk end - else - table.insert(t, chunk) - end - end -end - --- chains a source with one or several filter(s) -function source.chain(src, f, ...) - if ... then f=filter.chain(f, ...) end - base.assert(src and f) - local last_in, last_out = "", "" - local state = "feeding" - local err - return function() - if not last_out then - base.error('source is empty!', 2) - end - while true do - if state == "feeding" then - last_in, err = src() - if err then return nil, err end - last_out = f(last_in) - if not last_out then - if last_in then - base.error('filter returned inappropriate nil') - else - return nil - end - elseif last_out ~= "" then - state = "eating" - if last_in then last_in = "" end - return last_out - end - else - last_out = f(last_in) - if last_out == "" then - if last_in == "" then - state = "feeding" - else - base.error('filter returned ""') - end - elseif not last_out then - if last_in then - base.error('filter returned inappropriate nil') - else - return nil - end - else - return last_out - end - end - end - end -end - --- creates a source that produces contents of several sources, one after the --- other, as if they were concatenated --- (thanks to Wim Couwenberg) -function source.cat(...) - local arg = {...} - local src = table.remove(arg, 1) - return function() - while src do - local chunk, err = src() - if chunk then return chunk end - if err then return nil, err end - src = table.remove(arg, 1) - end - end -end - ------------------------------------------------------------------------------ --- Sink stuff ------------------------------------------------------------------------------ --- creates a sink that stores into a table -function sink.table(t) - t = t or {} - local f = function(chunk, err) - if chunk then table.insert(t, chunk) end - return 1 - end - return f, t -end - --- turns a fancy sink into a simple sink -function sink.simplify(snk) - base.assert(snk) - return function(chunk, err) - local ret, err_or_new = snk(chunk, err) - if not ret then return nil, err_or_new end - snk = err_or_new or snk - return 1 - end -end - --- creates a file sink -function sink.file(handle, io_err) - if handle then - return function(chunk, err) - if not chunk then - handle:close() - return 1 - else return handle:write(chunk) end - end - else return sink.error(io_err or "unable to open file") end -end - --- creates a sink that discards data -local function null() - return 1 -end - -function sink.null() - return null -end - --- creates a sink that just returns an error -function sink.error(err) - return function() - return nil, err - end -end - --- chains a sink with one or several filter(s) -function sink.chain(f, snk, ...) - if ... then - local args = { f, snk, ... } - snk = table.remove(args, #args) - f = filter.chain(unpack(args)) - end - base.assert(f and snk) - return function(chunk, err) - if chunk ~= "" then - local filtered = f(chunk) - local done = chunk and "" - while true do - local ret, snkerr = snk(filtered, err) - if not ret then return nil, snkerr end - if filtered == done then return 1 end - filtered = f(done) - end - else return 1 end - end -end - ------------------------------------------------------------------------------ --- Pump stuff ------------------------------------------------------------------------------ --- pumps one chunk from the source to the sink -function pump.step(src, snk) - local chunk, src_err = src() - local ret, snk_err = snk(chunk, src_err) - if chunk and ret then return 1 - else return nil, src_err or snk_err end -end - --- pumps all data from a source to a sink, using a step function -function pump.all(src, snk, step) - base.assert(src and snk) - step = step or pump.step - while true do - local ret, err = step(src, snk) - if not ret then - if err then return nil, err - else return 1 end - end - end -end - -return _M diff --git a/libs/share/mime.lua b/libs/share/mime.lua deleted file mode 100644 index d3abac5..0000000 --- a/libs/share/mime.lua +++ /dev/null @@ -1,89 +0,0 @@ ------------------------------------------------------------------------------ --- MIME support for the Lua language. --- Author: Diego Nehab --- Conforming to RFCs 2045-2049 ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies ------------------------------------------------------------------------------ -local base = _G -local ltn12 = require("ltn12") -local mime = require("mime.core") -local string = require("string") -local _M = mime - --- encode, decode and wrap algorithm tables -local encodet, decodet, wrapt = {},{},{} - -_M.encodet = encodet -_M.decodet = decodet -_M.wrapt = wrapt - --- creates a function that chooses a filter by name from a given table -local function choose(table) - return function(name, opt1, opt2) - if base.type(name) ~= "string" then - name, opt1, opt2 = "default", name, opt1 - end - local f = table[name or "nil"] - if not f then - base.error("unknown key (" .. base.tostring(name) .. ")", 3) - else return f(opt1, opt2) end - end -end - --- define the encoding filters -encodet['base64'] = function() - return ltn12.filter.cycle(_M.b64, "") -end - -encodet['quoted-printable'] = function(mode) - return ltn12.filter.cycle(_M.qp, "", - (mode == "binary") and "=0D=0A" or "\r\n") -end - --- define the decoding filters -decodet['base64'] = function() - return ltn12.filter.cycle(_M.unb64, "") -end - -decodet['quoted-printable'] = function() - return ltn12.filter.cycle(_M.unqp, "") -end - -local function format(chunk) - if chunk then - if chunk == "" then return "''" - else return string.len(chunk) end - else return "nil" end -end - --- define the line-wrap filters -wrapt['text'] = function(length) - length = length or 76 - return ltn12.filter.cycle(_M.wrp, length, length) -end -wrapt['base64'] = wrapt['text'] -wrapt['default'] = wrapt['text'] - -wrapt['quoted-printable'] = function() - return ltn12.filter.cycle(_M.qpwrp, 76, 76) -end - --- function that choose the encoding, decoding or wrap algorithm -_M.encode = choose(encodet) -_M.decode = choose(decodet) -_M.wrap = choose(wrapt) - --- define the end-of-line normalization filter -function _M.normalize(marker) - return ltn12.filter.cycle(_M.eol, 0, marker) -end - --- high level stuffing filter -function _M.stuff() - return ltn12.filter.cycle(_M.dot, 2) -end - -return _M diff --git a/libs/share/socket.lua b/libs/share/socket.lua deleted file mode 100644 index d1c0b16..0000000 --- a/libs/share/socket.lua +++ /dev/null @@ -1,149 +0,0 @@ ------------------------------------------------------------------------------ --- LuaSocket helper module --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies ------------------------------------------------------------------------------ -local base = _G -local string = require("string") -local math = require("math") -local socket = require("socket.core") - -local _M = socket - ------------------------------------------------------------------------------ --- Exported auxiliar functions ------------------------------------------------------------------------------ -function _M.connect4(address, port, laddress, lport) - return socket.connect(address, port, laddress, lport, "inet") -end - -function _M.connect6(address, port, laddress, lport) - return socket.connect(address, port, laddress, lport, "inet6") -end - -function _M.bind(host, port, backlog) - if host == "*" then host = "0.0.0.0" end - local addrinfo, err = socket.dns.getaddrinfo(host); - if not addrinfo then return nil, err end - local sock, res - err = "no info on address" - for i, alt in base.ipairs(addrinfo) do - if alt.family == "inet" then - sock, err = socket.tcp4() - else - sock, err = socket.tcp6() - end - if not sock then return nil, err end - sock:setoption("reuseaddr", true) - res, err = sock:bind(alt.addr, port) - if not res then - sock:close() - else - res, err = sock:listen(backlog) - if not res then - sock:close() - else - return sock - end - end - end - return nil, err -end - -_M.try = _M.newtry() - -function _M.choose(table) - return function(name, opt1, opt2) - if base.type(name) ~= "string" then - name, opt1, opt2 = "default", name, opt1 - end - local f = table[name or "nil"] - if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) - else return f(opt1, opt2) end - end -end - ------------------------------------------------------------------------------ --- Socket sources and sinks, conforming to LTN12 ------------------------------------------------------------------------------ --- create namespaces inside LuaSocket namespace -local sourcet, sinkt = {}, {} -_M.sourcet = sourcet -_M.sinkt = sinkt - -_M.BLOCKSIZE = 2048 - -sinkt["close-when-done"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if not chunk then - sock:close() - return 1 - else return sock:send(chunk) end - end - }) -end - -sinkt["keep-open"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if chunk then return sock:send(chunk) - else return 1 end - end - }) -end - -sinkt["default"] = sinkt["keep-open"] - -_M.sink = _M.choose(sinkt) - -sourcet["by-length"] = function(sock, length) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - if length <= 0 then return nil end - local size = math.min(socket.BLOCKSIZE, length) - local chunk, err = sock:receive(size) - if err then return nil, err end - length = length - string.len(chunk) - return chunk - end - }) -end - -sourcet["until-closed"] = function(sock) - local done - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - if done then return nil end - local chunk, err, partial = sock:receive(socket.BLOCKSIZE) - if not err then return chunk - elseif err == "closed" then - sock:close() - done = 1 - return partial - else return nil, err end - end - }) -end - - -sourcet["default"] = sourcet["until-closed"] - -_M.source = _M.choose(sourcet) - -return _M diff --git a/libs/share/socket/ftp.lua b/libs/share/socket/ftp.lua deleted file mode 100644 index bd528ca..0000000 --- a/libs/share/socket/ftp.lua +++ /dev/null @@ -1,329 +0,0 @@ ------------------------------------------------------------------------------ --- FTP support for the Lua language --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies ------------------------------------------------------------------------------ -local base = _G -local table = require("table") -local string = require("string") -local math = require("math") -local socket = require("socket") -local url = require("socket.url") -local tp = require("socket.tp") -local ltn12 = require("ltn12") -socket.ftp = {} -local _M = socket.ftp ------------------------------------------------------------------------------ --- Program constants ------------------------------------------------------------------------------ --- timeout in seconds before the program gives up on a connection -_M.TIMEOUT = 60 --- default port for ftp service -local PORT = 21 --- this is the default anonymous password. used when no password is --- provided in url. should be changed to your e-mail. -_M.USER = "ftp" -_M.PASSWORD = "anonymous@anonymous.org" - ------------------------------------------------------------------------------ --- Low level FTP API ------------------------------------------------------------------------------ -local metat = { __index = {} } - -function _M.open(server, port, create) - local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) - local f = base.setmetatable({ tp = tp }, metat) - -- make sure everything gets closed in an exception - f.try = socket.newtry(function() f:close() end) - return f -end - -function metat.__index:portconnect() - self.try(self.server:settimeout(_M.TIMEOUT)) - self.data = self.try(self.server:accept()) - self.try(self.data:settimeout(_M.TIMEOUT)) -end - -function metat.__index:pasvconnect() - self.data = self.try(socket.tcp()) - self.try(self.data:settimeout(_M.TIMEOUT)) - self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) -end - -function metat.__index:login(user, password) - self.try(self.tp:command("user", user or _M.USER)) - local code, reply = self.try(self.tp:check{"2..", 331}) - if code == 331 then - self.try(self.tp:command("pass", password or _M.PASSWORD)) - self.try(self.tp:check("2..")) - end - return 1 -end - -function metat.__index:pasv() - self.try(self.tp:command("pasv")) - local code, reply = self.try(self.tp:check("2..")) - local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" - local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) - self.try(a and b and c and d and p1 and p2, reply) - self.pasvt = { - address = string.format("%d.%d.%d.%d", a, b, c, d), - port = p1*256 + p2 - } - if self.server then - self.server:close() - self.server = nil - end - return self.pasvt.address, self.pasvt.port -end - -function metat.__index:epsv() - self.try(self.tp:command("epsv")) - local code, reply = self.try(self.tp:check("229")) - local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" - local d, prt, address, port = string.match(reply, pattern) - self.try(port, "invalid epsv response") - self.pasvt = { - address = self.tp:getpeername(), - port = port - } - if self.server then - self.server:close() - self.server = nil - end - return self.pasvt.address, self.pasvt.port -end - - -function metat.__index:port(address, port) - self.pasvt = nil - if not address then - address, port = self.try(self.tp:getsockname()) - self.server = self.try(socket.bind(address, 0)) - address, port = self.try(self.server:getsockname()) - self.try(self.server:settimeout(_M.TIMEOUT)) - end - local pl = math.mod(port, 256) - local ph = (port - pl)/256 - local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") - self.try(self.tp:command("port", arg)) - self.try(self.tp:check("2..")) - return 1 -end - -function metat.__index:eprt(family, address, port) - self.pasvt = nil - if not address then - address, port = self.try(self.tp:getsockname()) - self.server = self.try(socket.bind(address, 0)) - address, port = self.try(self.server:getsockname()) - self.try(self.server:settimeout(_M.TIMEOUT)) - end - local arg = string.format("|%s|%s|%d|", family, address, port) - self.try(self.tp:command("eprt", arg)) - self.try(self.tp:check("2..")) - return 1 -end - - -function metat.__index:send(sendt) - self.try(self.pasvt or self.server, "need port or pasv first") - -- if there is a pasvt table, we already sent a PASV command - -- we just get the data connection into self.data - if self.pasvt then self:pasvconnect() end - -- get the transfer argument and command - local argument = sendt.argument or - url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) - if argument == "" then argument = nil end - local command = sendt.command or "stor" - -- send the transfer command and check the reply - self.try(self.tp:command(command, argument)) - local code, reply = self.try(self.tp:check{"2..", "1.."}) - -- if there is not a pasvt table, then there is a server - -- and we already sent a PORT command - if not self.pasvt then self:portconnect() end - -- get the sink, source and step for the transfer - local step = sendt.step or ltn12.pump.step - local readt = { self.tp } - local checkstep = function(src, snk) - -- check status in control connection while downloading - local readyt = socket.select(readt, nil, 0) - if readyt[tp] then code = self.try(self.tp:check("2..")) end - return step(src, snk) - end - local sink = socket.sink("close-when-done", self.data) - -- transfer all data and check error - self.try(ltn12.pump.all(sendt.source, sink, checkstep)) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - -- done with data connection - self.data:close() - -- find out how many bytes were sent - local sent = socket.skip(1, self.data:getstats()) - self.data = nil - return sent -end - -function metat.__index:receive(recvt) - self.try(self.pasvt or self.server, "need port or pasv first") - if self.pasvt then self:pasvconnect() end - local argument = recvt.argument or - url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) - if argument == "" then argument = nil end - local command = recvt.command or "retr" - self.try(self.tp:command(command, argument)) - local code,reply = self.try(self.tp:check{"1..", "2.."}) - if (code >= 200) and (code <= 299) then - recvt.sink(reply) - return 1 - end - if not self.pasvt then self:portconnect() end - local source = socket.source("until-closed", self.data) - local step = recvt.step or ltn12.pump.step - self.try(ltn12.pump.all(source, recvt.sink, step)) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - self.data:close() - self.data = nil - return 1 -end - -function metat.__index:cwd(dir) - self.try(self.tp:command("cwd", dir)) - self.try(self.tp:check(250)) - return 1 -end - -function metat.__index:type(type) - self.try(self.tp:command("type", type)) - self.try(self.tp:check(200)) - return 1 -end - -function metat.__index:greet() - local code = self.try(self.tp:check{"1..", "2.."}) - if string.find(code, "1..") then self.try(self.tp:check("2..")) end - return 1 -end - -function metat.__index:quit() - self.try(self.tp:command("quit")) - self.try(self.tp:check("2..")) - return 1 -end - -function metat.__index:close() - if self.data then self.data:close() end - if self.server then self.server:close() end - return self.tp:close() -end - ------------------------------------------------------------------------------ --- High level FTP API ------------------------------------------------------------------------------ -local function override(t) - if t.url then - local u = url.parse(t.url) - for i,v in base.pairs(t) do - u[i] = v - end - return u - else return t end -end - -local function tput(putt) - putt = override(putt) - socket.try(putt.host, "missing hostname") - local f = _M.open(putt.host, putt.port, putt.create) - f:greet() - f:login(putt.user, putt.password) - if putt.type then f:type(putt.type) end - f:epsv() - local sent = f:send(putt) - f:quit() - f:close() - return sent -end - -local default = { - path = "/", - scheme = "ftp" -} - -local function genericform(u) - local t = socket.try(url.parse(u, default)) - socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") - socket.try(t.host, "missing hostname") - local pat = "^type=(.)$" - if t.params then - t.type = socket.skip(2, string.find(t.params, pat)) - socket.try(t.type == "a" or t.type == "i", - "invalid type '" .. t.type .. "'") - end - return t -end - -_M.genericform = genericform - -local function sput(u, body) - local putt = genericform(u) - putt.source = ltn12.source.string(body) - return tput(putt) -end - -_M.put = socket.protect(function(putt, body) - if base.type(putt) == "string" then return sput(putt, body) - else return tput(putt) end -end) - -local function tget(gett) - gett = override(gett) - socket.try(gett.host, "missing hostname") - local f = _M.open(gett.host, gett.port, gett.create) - f:greet() - f:login(gett.user, gett.password) - if gett.type then f:type(gett.type) end - f:epsv() - f:receive(gett) - f:quit() - return f:close() -end - -local function sget(u) - local gett = genericform(u) - local t = {} - gett.sink = ltn12.sink.table(t) - tget(gett) - return table.concat(t) -end - -_M.command = socket.protect(function(cmdt) - cmdt = override(cmdt) - socket.try(cmdt.host, "missing hostname") - socket.try(cmdt.command, "missing command") - local f = _M.open(cmdt.host, cmdt.port, cmdt.create) - f:greet() - f:login(cmdt.user, cmdt.password) - if type(cmdt.command) == "table" then - local argument = cmdt.argument or {} - local check = cmdt.check or {} - for i,cmd in ipairs(cmdt.command) do - f.try(f.tp:command(cmd, argument[i])) - if check[i] then f.try(f.tp:check(check[i])) end - end - else - f.try(f.tp:command(cmdt.command, cmdt.argument)) - if cmdt.check then f.try(f.tp:check(cmdt.check)) end - end - f:quit() - return f:close() -end) - -_M.get = socket.protect(function(gett) - if base.type(gett) == "string" then return sget(gett) - else return tget(gett) end -end) - -return _M diff --git a/libs/share/socket/headers.lua b/libs/share/socket/headers.lua deleted file mode 100644 index 1eb8223..0000000 --- a/libs/share/socket/headers.lua +++ /dev/null @@ -1,104 +0,0 @@ ------------------------------------------------------------------------------ --- Canonic header field capitalization --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ -local socket = require("socket") -socket.headers = {} -local _M = socket.headers - -_M.canonic = { - ["accept"] = "Accept", - ["accept-charset"] = "Accept-Charset", - ["accept-encoding"] = "Accept-Encoding", - ["accept-language"] = "Accept-Language", - ["accept-ranges"] = "Accept-Ranges", - ["action"] = "Action", - ["alternate-recipient"] = "Alternate-Recipient", - ["age"] = "Age", - ["allow"] = "Allow", - ["arrival-date"] = "Arrival-Date", - ["authorization"] = "Authorization", - ["bcc"] = "Bcc", - ["cache-control"] = "Cache-Control", - ["cc"] = "Cc", - ["comments"] = "Comments", - ["connection"] = "Connection", - ["content-description"] = "Content-Description", - ["content-disposition"] = "Content-Disposition", - ["content-encoding"] = "Content-Encoding", - ["content-id"] = "Content-ID", - ["content-language"] = "Content-Language", - ["content-length"] = "Content-Length", - ["content-location"] = "Content-Location", - ["content-md5"] = "Content-MD5", - ["content-range"] = "Content-Range", - ["content-transfer-encoding"] = "Content-Transfer-Encoding", - ["content-type"] = "Content-Type", - ["cookie"] = "Cookie", - ["date"] = "Date", - ["diagnostic-code"] = "Diagnostic-Code", - ["dsn-gateway"] = "DSN-Gateway", - ["etag"] = "ETag", - ["expect"] = "Expect", - ["expires"] = "Expires", - ["final-log-id"] = "Final-Log-ID", - ["final-recipient"] = "Final-Recipient", - ["from"] = "From", - ["host"] = "Host", - ["if-match"] = "If-Match", - ["if-modified-since"] = "If-Modified-Since", - ["if-none-match"] = "If-None-Match", - ["if-range"] = "If-Range", - ["if-unmodified-since"] = "If-Unmodified-Since", - ["in-reply-to"] = "In-Reply-To", - ["keywords"] = "Keywords", - ["last-attempt-date"] = "Last-Attempt-Date", - ["last-modified"] = "Last-Modified", - ["location"] = "Location", - ["max-forwards"] = "Max-Forwards", - ["message-id"] = "Message-ID", - ["mime-version"] = "MIME-Version", - ["original-envelope-id"] = "Original-Envelope-ID", - ["original-recipient"] = "Original-Recipient", - ["pragma"] = "Pragma", - ["proxy-authenticate"] = "Proxy-Authenticate", - ["proxy-authorization"] = "Proxy-Authorization", - ["range"] = "Range", - ["received"] = "Received", - ["received-from-mta"] = "Received-From-MTA", - ["references"] = "References", - ["referer"] = "Referer", - ["remote-mta"] = "Remote-MTA", - ["reply-to"] = "Reply-To", - ["reporting-mta"] = "Reporting-MTA", - ["resent-bcc"] = "Resent-Bcc", - ["resent-cc"] = "Resent-Cc", - ["resent-date"] = "Resent-Date", - ["resent-from"] = "Resent-From", - ["resent-message-id"] = "Resent-Message-ID", - ["resent-reply-to"] = "Resent-Reply-To", - ["resent-sender"] = "Resent-Sender", - ["resent-to"] = "Resent-To", - ["retry-after"] = "Retry-After", - ["return-path"] = "Return-Path", - ["sender"] = "Sender", - ["server"] = "Server", - ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", - ["status"] = "Status", - ["subject"] = "Subject", - ["te"] = "TE", - ["to"] = "To", - ["trailer"] = "Trailer", - ["transfer-encoding"] = "Transfer-Encoding", - ["upgrade"] = "Upgrade", - ["user-agent"] = "User-Agent", - ["vary"] = "Vary", - ["via"] = "Via", - ["warning"] = "Warning", - ["will-retry-until"] = "Will-Retry-Until", - ["www-authenticate"] = "WWW-Authenticate", - ["x-mailer"] = "X-Mailer", -} - -return _M \ No newline at end of file diff --git a/libs/share/socket/http.lua b/libs/share/socket/http.lua deleted file mode 100644 index 6a3416e..0000000 --- a/libs/share/socket/http.lua +++ /dev/null @@ -1,420 +0,0 @@ ------------------------------------------------------------------------------ --- HTTP/1.1 client support for the Lua language. --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies -------------------------------------------------------------------------------- -local socket = require("socket") -local url = require("socket.url") -local ltn12 = require("ltn12") -local mime = require("mime") -local string = require("string") -local headers = require("socket.headers") -local base = _G -local table = require("table") -socket.http = {} -local _M = socket.http - ------------------------------------------------------------------------------ --- Program constants ------------------------------------------------------------------------------ --- connection timeout in seconds -_M.TIMEOUT = 60 --- user agent field sent in request -_M.USERAGENT = socket._VERSION - --- supported schemes and their particulars -local SCHEMES = { - http = { - port = 80 - , create = function(t) - return socket.tcp end } - , https = { - port = 443 - , create = function(t) - local https = assert( - require("ssl.https"), 'LuaSocket: LuaSec not found') - local tcp = assert( - https.tcp, 'LuaSocket: Function tcp() not available from LuaSec') - return tcp(t) end }} - --- default scheme and port for document retrieval -local SCHEME = 'http' -local PORT = SCHEMES[SCHEME].port ------------------------------------------------------------------------------ --- Reads MIME headers from a connection, unfolding where needed ------------------------------------------------------------------------------ -local function receiveheaders(sock, headers) - local line, name, value, err - headers = headers or {} - -- get first line - line, err = sock:receive() - if err then return nil, err end - -- headers go until a blank line is found - while line ~= "" do - -- get field-name and value - name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) - if not (name and value) then return nil, "malformed reponse headers" end - name = string.lower(name) - -- get next line (value might be folded) - line, err = sock:receive() - if err then return nil, err end - -- unfold any folded values - while string.find(line, "^%s") do - value = value .. line - line = sock:receive() - if err then return nil, err end - end - -- save pair in table - if headers[name] then headers[name] = headers[name] .. ", " .. value - else headers[name] = value end - end - return headers -end - ------------------------------------------------------------------------------ --- Extra sources and sinks ------------------------------------------------------------------------------ -socket.sourcet["http-chunked"] = function(sock, headers) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function() - -- get chunk size, skip extention - local line, err = sock:receive() - if err then return nil, err end - local size = base.tonumber(string.gsub(line, ";.*", ""), 16) - if not size then return nil, "invalid chunk size" end - -- was it the last chunk? - if size > 0 then - -- if not, get chunk and skip terminating CRLF - local chunk, err, part = sock:receive(size) - if chunk then sock:receive() end - return chunk, err - else - -- if it was, read trailers into headers table - headers, err = receiveheaders(sock, headers) - if not headers then return nil, err end - end - end - }) -end - -socket.sinkt["http-chunked"] = function(sock) - return base.setmetatable({ - getfd = function() return sock:getfd() end, - dirty = function() return sock:dirty() end - }, { - __call = function(self, chunk, err) - if not chunk then return sock:send("0\r\n\r\n") end - local size = string.format("%X\r\n", string.len(chunk)) - return sock:send(size .. chunk .. "\r\n") - end - }) -end - ------------------------------------------------------------------------------ --- Low level HTTP API ------------------------------------------------------------------------------ -local metat = { __index = {} } - -function _M.open(host, port, create) - -- create socket with user connect function, or with default - local c = socket.try(create()) - local h = base.setmetatable({ c = c }, metat) - -- create finalized try - h.try = socket.newtry(function() h:close() end) - -- set timeout before connecting - h.try(c:settimeout(_M.TIMEOUT)) - h.try(c:connect(host, port)) - -- here everything worked - return h -end - -function metat.__index:sendrequestline(method, uri) - local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) - return self.try(self.c:send(reqline)) -end - -function metat.__index:sendheaders(tosend) - local canonic = headers.canonic - local h = "\r\n" - for f, v in base.pairs(tosend) do - h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h - end - self.try(self.c:send(h)) - return 1 -end - -function metat.__index:sendbody(headers, source, step) - source = source or ltn12.source.empty() - step = step or ltn12.pump.step - -- if we don't know the size in advance, send chunked and hope for the best - local mode = "http-chunked" - if headers["content-length"] then mode = "keep-open" end - return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) -end - -function metat.__index:receivestatusline() - local status,ec = self.try(self.c:receive(5)) - -- identify HTTP/0.9 responses, which do not contain a status line - -- this is just a heuristic, but is what the RFC recommends - if status ~= "HTTP/" then - if ec == "timeout" then - return 408 - end - return nil, status - end - -- otherwise proceed reading a status line - status = self.try(self.c:receive("*l", status)) - local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) - return self.try(base.tonumber(code), status) -end - -function metat.__index:receiveheaders() - return self.try(receiveheaders(self.c)) -end - -function metat.__index:receivebody(headers, sink, step) - sink = sink or ltn12.sink.null() - step = step or ltn12.pump.step - local length = base.tonumber(headers["content-length"]) - local t = headers["transfer-encoding"] -- shortcut - local mode = "default" -- connection close - if t and t ~= "identity" then mode = "http-chunked" - elseif base.tonumber(headers["content-length"]) then mode = "by-length" end - return self.try(ltn12.pump.all(socket.source(mode, self.c, length), - sink, step)) -end - -function metat.__index:receive09body(status, sink, step) - local source = ltn12.source.rewind(socket.source("until-closed", self.c)) - source(status) - return self.try(ltn12.pump.all(source, sink, step)) -end - -function metat.__index:close() - return self.c:close() -end - ------------------------------------------------------------------------------ --- High level HTTP API ------------------------------------------------------------------------------ -local function adjusturi(reqt) - local u = reqt - -- if there is a proxy, we need the full url. otherwise, just a part. - if not reqt.proxy and not _M.PROXY then - u = { - path = socket.try(reqt.path, "invalid path 'nil'"), - params = reqt.params, - query = reqt.query, - fragment = reqt.fragment - } - end - return url.build(u) -end - -local function adjustproxy(reqt) - local proxy = reqt.proxy or _M.PROXY - if proxy then - proxy = url.parse(proxy) - return proxy.host, proxy.port or 3128 - else - return reqt.host, reqt.port - end -end - -local function adjustheaders(reqt) - -- default headers - local host = reqt.host - local port = tostring(reqt.port) - if port ~= tostring(SCHEMES[reqt.scheme].port) then - host = host .. ':' .. port end - local lower = { - ["user-agent"] = _M.USERAGENT, - ["host"] = host, - ["connection"] = "close, TE", - ["te"] = "trailers" - } - -- if we have authentication information, pass it along - if reqt.user and reqt.password then - lower["authorization"] = - "Basic " .. (mime.b64(reqt.user .. ":" .. - url.unescape(reqt.password))) - end - -- if we have proxy authentication information, pass it along - local proxy = reqt.proxy or _M.PROXY - if proxy then - proxy = url.parse(proxy) - if proxy.user and proxy.password then - lower["proxy-authorization"] = - "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) - end - end - -- override with user headers - for i,v in base.pairs(reqt.headers or lower) do - lower[string.lower(i)] = v - end - return lower -end - --- default url parts -local default = { - path ="/" - , scheme = "http" -} - -local function adjustrequest(reqt) - -- parse url if provided - local nreqt = reqt.url and url.parse(reqt.url, default) or {} - -- explicit components override url - for i,v in base.pairs(reqt) do nreqt[i] = v end - -- default to scheme particulars - local schemedefs, host, port, method - = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method - if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end - if not (port and port ~= '') then nreqt.port = schemedefs.port end - if not (method and method ~= '') then nreqt.method = 'GET' end - if not (host and host ~= "") then - socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") - end - -- compute uri if user hasn't overriden - nreqt.uri = reqt.uri or adjusturi(nreqt) - -- adjust headers in request - nreqt.headers = adjustheaders(nreqt) - -- ajust host and port if there is a proxy - nreqt.host, nreqt.port = adjustproxy(nreqt) - return nreqt -end - -local function shouldredirect(reqt, code, headers) - local location = headers.location - if not location then return false end - location = string.gsub(location, "%s", "") - if location == "" then return false end - local scheme = url.parse(location).scheme - if scheme and (not SCHEMES[scheme]) then return false end - -- avoid https downgrades - if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end - return (reqt.redirect ~= false) and - (code == 301 or code == 302 or code == 303 or code == 307) and - (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") - and ((false == reqt.maxredirects) - or ((reqt.nredirects or 0) - < (reqt.maxredirects or 5))) -end - -local function shouldreceivebody(reqt, code) - if reqt.method == "HEAD" then return nil end - if code == 204 or code == 304 then return nil end - if code >= 100 and code < 200 then return nil end - return 1 -end - --- forward declarations -local trequest, tredirect - ---[[local]] function tredirect(reqt, location) - -- the RFC says the redirect URL has to be absolute, but some - -- servers do not respect that - local newurl = url.absolute(reqt.url, location) - -- if switching schemes, reset port and create function - if url.parse(newurl).scheme ~= reqt.scheme then - reqt.port = nil - reqt.create = nil end - -- make new request - local result, code, headers, status = trequest { - url = newurl, - source = reqt.source, - sink = reqt.sink, - headers = reqt.headers, - proxy = reqt.proxy, - maxredirects = reqt.maxredirects, - nredirects = (reqt.nredirects or 0) + 1, - create = reqt.create - } - -- pass location header back as a hint we redirected - headers = headers or {} - headers.location = headers.location or location - return result, code, headers, status -end - ---[[local]] function trequest(reqt) - -- we loop until we get what we want, or - -- until we are sure there is no way to get it - local nreqt = adjustrequest(reqt) - local h = _M.open(nreqt.host, nreqt.port, nreqt.create) - -- send request line and headers - h:sendrequestline(nreqt.method, nreqt.uri) - h:sendheaders(nreqt.headers) - -- if there is a body, send it - if nreqt.source then - h:sendbody(nreqt.headers, nreqt.source, nreqt.step) - end - local code, status = h:receivestatusline() - -- if it is an HTTP/0.9 server, simply get the body and we are done - if not code then - h:receive09body(status, nreqt.sink, nreqt.step) - return 1, 200 - elseif code == 408 then - return 1, code - end - local headers - -- ignore any 100-continue messages - while code == 100 do - headers = h:receiveheaders() - code, status = h:receivestatusline() - end - headers = h:receiveheaders() - -- at this point we should have a honest reply from the server - -- we can't redirect if we already used the source, so we report the error - if shouldredirect(nreqt, code, headers) and not nreqt.source then - h:close() - return tredirect(reqt, headers.location) - end - -- here we are finally done - if shouldreceivebody(nreqt, code) then - h:receivebody(headers, nreqt.sink, nreqt.step) - end - h:close() - return 1, code, headers, status -end - --- turns an url and a body into a generic request -local function genericform(u, b) - local t = {} - local reqt = { - url = u, - sink = ltn12.sink.table(t), - target = t - } - if b then - reqt.source = ltn12.source.string(b) - reqt.headers = { - ["content-length"] = string.len(b), - ["content-type"] = "application/x-www-form-urlencoded" - } - reqt.method = "POST" - end - return reqt -end - -_M.genericform = genericform - -local function srequest(u, b) - local reqt = genericform(u, b) - local _, code, headers, status = trequest(reqt) - return table.concat(reqt.target), code, headers, status -end - -_M.request = socket.protect(function(reqt, body) - if base.type(reqt) == "string" then return srequest(reqt, body) - else return trequest(reqt) end -end) - -_M.schemes = SCHEMES -return _M diff --git a/libs/share/socket/smtp.lua b/libs/share/socket/smtp.lua deleted file mode 100644 index b113d00..0000000 --- a/libs/share/socket/smtp.lua +++ /dev/null @@ -1,256 +0,0 @@ ------------------------------------------------------------------------------ --- SMTP client support for the Lua language. --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies ------------------------------------------------------------------------------ -local base = _G -local coroutine = require("coroutine") -local string = require("string") -local math = require("math") -local os = require("os") -local socket = require("socket") -local tp = require("socket.tp") -local ltn12 = require("ltn12") -local headers = require("socket.headers") -local mime = require("mime") - -socket.smtp = {} -local _M = socket.smtp - ------------------------------------------------------------------------------ --- Program constants ------------------------------------------------------------------------------ --- timeout for connection -_M.TIMEOUT = 60 --- default server used to send e-mails -_M.SERVER = "localhost" --- default port -_M.PORT = 25 --- domain used in HELO command and default sendmail --- If we are under a CGI, try to get from environment -_M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" --- default time zone (means we don't know) -_M.ZONE = "-0000" - ---------------------------------------------------------------------------- --- Low level SMTP API ------------------------------------------------------------------------------ -local metat = { __index = {} } - -function metat.__index:greet(domain) - self.try(self.tp:check("2..")) - self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) - return socket.skip(1, self.try(self.tp:check("2.."))) -end - -function metat.__index:mail(from) - self.try(self.tp:command("MAIL", "FROM:" .. from)) - return self.try(self.tp:check("2..")) -end - -function metat.__index:rcpt(to) - self.try(self.tp:command("RCPT", "TO:" .. to)) - return self.try(self.tp:check("2..")) -end - -function metat.__index:data(src, step) - self.try(self.tp:command("DATA")) - self.try(self.tp:check("3..")) - self.try(self.tp:source(src, step)) - self.try(self.tp:send("\r\n.\r\n")) - return self.try(self.tp:check("2..")) -end - -function metat.__index:quit() - self.try(self.tp:command("QUIT")) - return self.try(self.tp:check("2..")) -end - -function metat.__index:close() - return self.tp:close() -end - -function metat.__index:login(user, password) - self.try(self.tp:command("AUTH", "LOGIN")) - self.try(self.tp:check("3..")) - self.try(self.tp:send(mime.b64(user) .. "\r\n")) - self.try(self.tp:check("3..")) - self.try(self.tp:send(mime.b64(password) .. "\r\n")) - return self.try(self.tp:check("2..")) -end - -function metat.__index:plain(user, password) - local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) - self.try(self.tp:command("AUTH", auth)) - return self.try(self.tp:check("2..")) -end - -function metat.__index:auth(user, password, ext) - if not user or not password then return 1 end - if string.find(ext, "AUTH[^\n]+LOGIN") then - return self:login(user, password) - elseif string.find(ext, "AUTH[^\n]+PLAIN") then - return self:plain(user, password) - else - self.try(nil, "authentication not supported") - end -end - --- send message or throw an exception -function metat.__index:send(mailt) - self:mail(mailt.from) - if base.type(mailt.rcpt) == "table" then - for i,v in base.ipairs(mailt.rcpt) do - self:rcpt(v) - end - else - self:rcpt(mailt.rcpt) - end - self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) -end - -function _M.open(server, port, create) - local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, - _M.TIMEOUT, create)) - local s = base.setmetatable({tp = tp}, metat) - -- make sure tp is closed if we get an exception - s.try = socket.newtry(function() - s:close() - end) - return s -end - --- convert headers to lowercase -local function lower_headers(headers) - local lower = {} - for i,v in base.pairs(headers or lower) do - lower[string.lower(i)] = v - end - return lower -end - ---------------------------------------------------------------------------- --- Multipart message source ------------------------------------------------------------------------------ --- returns a hopefully unique mime boundary -local seqno = 0 -local function newboundary() - seqno = seqno + 1 - return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), - math.random(0, 99999), seqno) -end - --- send_message forward declaration -local send_message - --- yield the headers all at once, it's faster -local function send_headers(tosend) - local canonic = headers.canonic - local h = "\r\n" - for f,v in base.pairs(tosend) do - h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h - end - coroutine.yield(h) -end - --- yield multipart message body from a multipart message table -local function send_multipart(mesgt) - -- make sure we have our boundary and send headers - local bd = newboundary() - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or 'multipart/mixed' - headers['content-type'] = headers['content-type'] .. - '; boundary="' .. bd .. '"' - send_headers(headers) - -- send preamble - if mesgt.body.preamble then - coroutine.yield(mesgt.body.preamble) - coroutine.yield("\r\n") - end - -- send each part separated by a boundary - for i, m in base.ipairs(mesgt.body) do - coroutine.yield("\r\n--" .. bd .. "\r\n") - send_message(m) - end - -- send last boundary - coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") - -- send epilogue - if mesgt.body.epilogue then - coroutine.yield(mesgt.body.epilogue) - coroutine.yield("\r\n") - end -end - --- yield message body from a source -local function send_source(mesgt) - -- make sure we have a content-type - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or - 'text/plain; charset="iso-8859-1"' - send_headers(headers) - -- send body from source - while true do - local chunk, err = mesgt.body() - if err then coroutine.yield(nil, err) - elseif chunk then coroutine.yield(chunk) - else break end - end -end - --- yield message body from a string -local function send_string(mesgt) - -- make sure we have a content-type - local headers = lower_headers(mesgt.headers or {}) - headers['content-type'] = headers['content-type'] or - 'text/plain; charset="iso-8859-1"' - send_headers(headers) - -- send body from string - coroutine.yield(mesgt.body) -end - --- message source -function send_message(mesgt) - if base.type(mesgt.body) == "table" then send_multipart(mesgt) - elseif base.type(mesgt.body) == "function" then send_source(mesgt) - else send_string(mesgt) end -end - --- set defaul headers -local function adjust_headers(mesgt) - local lower = lower_headers(mesgt.headers) - lower["date"] = lower["date"] or - os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) - lower["x-mailer"] = lower["x-mailer"] or socket._VERSION - -- this can't be overriden - lower["mime-version"] = "1.0" - return lower -end - -function _M.message(mesgt) - mesgt.headers = adjust_headers(mesgt) - -- create and return message source - local co = coroutine.create(function() send_message(mesgt) end) - return function() - local ret, a, b = coroutine.resume(co) - if ret then return a, b - else return nil, a end - end -end - ---------------------------------------------------------------------------- --- High level SMTP API ------------------------------------------------------------------------------ -_M.send = socket.protect(function(mailt) - local s = _M.open(mailt.server, mailt.port, mailt.create) - local ext = s:greet(mailt.domain) - s:auth(mailt.user, mailt.password, ext) - s:send(mailt) - s:quit() - return s:close() -end) - -return _M \ No newline at end of file diff --git a/libs/share/socket/tp.lua b/libs/share/socket/tp.lua deleted file mode 100644 index b8ebc56..0000000 --- a/libs/share/socket/tp.lua +++ /dev/null @@ -1,134 +0,0 @@ ------------------------------------------------------------------------------ --- Unified SMTP/FTP subsystem --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module and import dependencies ------------------------------------------------------------------------------ -local base = _G -local string = require("string") -local socket = require("socket") -local ltn12 = require("ltn12") - -socket.tp = {} -local _M = socket.tp - ------------------------------------------------------------------------------ --- Program constants ------------------------------------------------------------------------------ -_M.TIMEOUT = 60 - ------------------------------------------------------------------------------ --- Implementation ------------------------------------------------------------------------------ --- gets server reply (works for SMTP and FTP) -local function get_reply(c) - local code, current, sep - local line, err = c:receive() - local reply = line - if err then return nil, err end - code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) - if not code then return nil, "invalid server reply" end - if sep == "-" then -- reply is multiline - repeat - line, err = c:receive() - if err then return nil, err end - current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) - reply = reply .. "\n" .. line - -- reply ends with same code - until code == current and sep == " " - end - return code, reply -end - --- metatable for sock object -local metat = { __index = {} } - -function metat.__index:getpeername() - return self.c:getpeername() -end - -function metat.__index:getsockname() - return self.c:getpeername() -end - -function metat.__index:check(ok) - local code, reply = get_reply(self.c) - if not code then return nil, reply end - if base.type(ok) ~= "function" then - if base.type(ok) == "table" then - for i, v in base.ipairs(ok) do - if string.find(code, v) then - return base.tonumber(code), reply - end - end - return nil, reply - else - if string.find(code, ok) then return base.tonumber(code), reply - else return nil, reply end - end - else return ok(base.tonumber(code), reply) end -end - -function metat.__index:command(cmd, arg) - cmd = string.upper(cmd) - if arg then - return self.c:send(cmd .. " " .. arg.. "\r\n") - else - return self.c:send(cmd .. "\r\n") - end -end - -function metat.__index:sink(snk, pat) - local chunk, err = self.c:receive(pat) - return snk(chunk, err) -end - -function metat.__index:send(data) - return self.c:send(data) -end - -function metat.__index:receive(pat) - return self.c:receive(pat) -end - -function metat.__index:getfd() - return self.c:getfd() -end - -function metat.__index:dirty() - return self.c:dirty() -end - -function metat.__index:getcontrol() - return self.c -end - -function metat.__index:source(source, step) - local sink = socket.sink("keep-open", self.c) - local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) - return ret, err -end - --- closes the underlying c -function metat.__index:close() - self.c:close() - return 1 -end - --- connect with server and return c object -function _M.connect(host, port, timeout, create) - local c, e = (create or socket.tcp)() - if not c then return nil, e end - c:settimeout(timeout or _M.TIMEOUT) - local r, e = c:connect(host, port) - if not r then - c:close() - return nil, e - end - return base.setmetatable({c = c}, metat) -end - -return _M diff --git a/libs/share/socket/url.lua b/libs/share/socket/url.lua deleted file mode 100644 index 0a3a80a..0000000 --- a/libs/share/socket/url.lua +++ /dev/null @@ -1,331 +0,0 @@ ------------------------------------------------------------------------------ --- URI parsing, composition and relative URL resolution --- LuaSocket toolkit. --- Author: Diego Nehab ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Declare module ------------------------------------------------------------------------------ -local string = require("string") -local base = _G -local table = require("table") -local socket = require("socket") - -socket.url = {} -local _M = socket.url - ------------------------------------------------------------------------------ --- Module version ------------------------------------------------------------------------------ -_M._VERSION = "URL 1.0.3" - ------------------------------------------------------------------------------ --- Encodes a string into its escaped hexadecimal representation --- Input --- s: binary string to be encoded --- Returns --- escaped representation of string binary ------------------------------------------------------------------------------ -function _M.escape(s) - return (string.gsub(s, "([^A-Za-z0-9_])", function(c) - return string.format("%%%02x", string.byte(c)) - end)) -end - ------------------------------------------------------------------------------ --- Protects a path segment, to prevent it from interfering with the --- url parsing. --- Input --- s: binary string to be encoded --- Returns --- escaped representation of string binary ------------------------------------------------------------------------------ -local function make_set(t) - local s = {} - for i,v in base.ipairs(t) do - s[t[i]] = 1 - end - return s -end - --- these are allowed within a path segment, along with alphanum --- other characters must be escaped -local segment_set = make_set { - "-", "_", ".", "!", "~", "*", "'", "(", - ")", ":", "@", "&", "=", "+", "$", ",", -} - -local function protect_segment(s) - return string.gsub(s, "([^A-Za-z0-9_])", function (c) - if segment_set[c] then return c - else return string.format("%%%02X", string.byte(c)) end - end) -end - ------------------------------------------------------------------------------ --- Unencodes a escaped hexadecimal string into its binary representation --- Input --- s: escaped hexadecimal string to be unencoded --- Returns --- unescaped binary representation of escaped hexadecimal binary ------------------------------------------------------------------------------ -function _M.unescape(s) - return (string.gsub(s, "%%(%x%x)", function(hex) - return string.char(base.tonumber(hex, 16)) - end)) -end - ------------------------------------------------------------------------------ --- Removes '..' and '.' components appropriately from a path. --- Input --- path --- Returns --- dot-normalized path -local function remove_dot_components(path) - local marker = string.char(1) - repeat - local was = path - path = path:gsub('//', '/'..marker..'/', 1) - until path == was - repeat - local was = path - path = path:gsub('/%./', '/', 1) - until path == was - repeat - local was = path - path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) - until path == was - path = path:gsub('[^/]+/%.%./*$', '') - path = path:gsub('/%.%.$', '/') - path = path:gsub('/%.$', '/') - path = path:gsub('^/%.%./', '/') - path = path:gsub(marker, '') - return path -end - ------------------------------------------------------------------------------ --- Builds a path from a base path and a relative path --- Input --- base_path --- relative_path --- Returns --- corresponding absolute path ------------------------------------------------------------------------------ -local function absolute_path(base_path, relative_path) - if string.sub(relative_path, 1, 1) == "/" then - return remove_dot_components(relative_path) end - base_path = base_path:gsub("[^/]*$", "") - if not base_path:find'/$' then base_path = base_path .. '/' end - local path = base_path .. relative_path - path = remove_dot_components(path) - return path -end - ------------------------------------------------------------------------------ --- Parses a url and returns a table with all its parts according to RFC 2396 --- The following grammar describes the names given to the URL parts --- ::= :///;?# --- ::= @: --- ::= [:] --- :: = {/} --- Input --- url: uniform resource locator of request --- default: table with default values for each field --- Returns --- table with the following fields, where RFC naming conventions have --- been preserved: --- scheme, authority, userinfo, user, password, host, port, --- path, params, query, fragment --- Obs: --- the leading '/' in {/} is considered part of ------------------------------------------------------------------------------ -function _M.parse(url, default) - -- initialize default parameters - local parsed = {} - for i,v in base.pairs(default or parsed) do parsed[i] = v end - -- empty url is parsed to nil - if not url or url == "" then return nil, "invalid url" end - -- remove whitespace - -- url = string.gsub(url, "%s", "") - -- get scheme - url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", - function(s) parsed.scheme = s; return "" end) - -- get authority - url = string.gsub(url, "^//([^/]*)", function(n) - parsed.authority = n - return "" - end) - -- get fragment - url = string.gsub(url, "#(.*)$", function(f) - parsed.fragment = f - return "" - end) - -- get query string - url = string.gsub(url, "%?(.*)", function(q) - parsed.query = q - return "" - end) - -- get params - url = string.gsub(url, "%;(.*)", function(p) - parsed.params = p - return "" - end) - -- path is whatever was left - if url ~= "" then parsed.path = url end - local authority = parsed.authority - if not authority then return parsed end - authority = string.gsub(authority,"^([^@]*)@", - function(u) parsed.userinfo = u; return "" end) - authority = string.gsub(authority, ":([^:%]]*)$", - function(p) parsed.port = p; return "" end) - if authority ~= "" then - -- IPv6? - parsed.host = string.match(authority, "^%[(.+)%]$") or authority - end - local userinfo = parsed.userinfo - if not userinfo then return parsed end - userinfo = string.gsub(userinfo, ":([^:]*)$", - function(p) parsed.password = p; return "" end) - parsed.user = userinfo - return parsed -end - ------------------------------------------------------------------------------ --- Rebuilds a parsed URL from its components. --- Components are protected if any reserved or unallowed characters are found --- Input --- parsed: parsed URL, as returned by parse --- Returns --- a stringing with the corresponding URL ------------------------------------------------------------------------------ -function _M.build(parsed) - --local ppath = _M.parse_path(parsed.path or "") - --local url = _M.build_path(ppath) - local url = parsed.path or "" - if parsed.params then url = url .. ";" .. parsed.params end - if parsed.query then url = url .. "?" .. parsed.query end - local authority = parsed.authority - if parsed.host then - authority = parsed.host - if string.find(authority, ":") then -- IPv6? - authority = "[" .. authority .. "]" - end - if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end - local userinfo = parsed.userinfo - if parsed.user then - userinfo = parsed.user - if parsed.password then - userinfo = userinfo .. ":" .. parsed.password - end - end - if userinfo then authority = userinfo .. "@" .. authority end - end - if authority then url = "//" .. authority .. url end - if parsed.scheme then url = parsed.scheme .. ":" .. url end - if parsed.fragment then url = url .. "#" .. parsed.fragment end - -- url = string.gsub(url, "%s", "") - return url -end - ------------------------------------------------------------------------------ --- Builds a absolute URL from a base and a relative URL according to RFC 2396 --- Input --- base_url --- relative_url --- Returns --- corresponding absolute url ------------------------------------------------------------------------------ -function _M.absolute(base_url, relative_url) - local base_parsed - if base.type(base_url) == "table" then - base_parsed = base_url - base_url = _M.build(base_parsed) - else - base_parsed = _M.parse(base_url) - end - local result - local relative_parsed = _M.parse(relative_url) - if not base_parsed then - result = relative_url - elseif not relative_parsed then - result = base_url - elseif relative_parsed.scheme then - result = relative_url - else - relative_parsed.scheme = base_parsed.scheme - if not relative_parsed.authority then - relative_parsed.authority = base_parsed.authority - if not relative_parsed.path then - relative_parsed.path = base_parsed.path - if not relative_parsed.params then - relative_parsed.params = base_parsed.params - if not relative_parsed.query then - relative_parsed.query = base_parsed.query - end - end - else - relative_parsed.path = absolute_path(base_parsed.path or "", - relative_parsed.path) - end - end - result = _M.build(relative_parsed) - end - return remove_dot_components(result) -end - ------------------------------------------------------------------------------ --- Breaks a path into its segments, unescaping the segments --- Input --- path --- Returns --- segment: a table with one entry per segment ------------------------------------------------------------------------------ -function _M.parse_path(path) - local parsed = {} - path = path or "" - --path = string.gsub(path, "%s", "") - string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) - for i = 1, #parsed do - parsed[i] = _M.unescape(parsed[i]) - end - if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end - if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end - return parsed -end - ------------------------------------------------------------------------------ --- Builds a path component from its segments, escaping protected characters. --- Input --- parsed: path segments --- unsafe: if true, segments are not protected before path is built --- Returns --- path: corresponding path stringing ------------------------------------------------------------------------------ -function _M.build_path(parsed, unsafe) - local path = "" - local n = #parsed - if unsafe then - for i = 1, n-1 do - path = path .. parsed[i] - path = path .. "/" - end - if n > 0 then - path = path .. parsed[n] - if parsed.is_directory then path = path .. "/" end - end - else - for i = 1, n-1 do - path = path .. protect_segment(parsed[i]) - path = path .. "/" - end - if n > 0 then - path = path .. protect_segment(parsed[n]) - if parsed.is_directory then path = path .. "/" end - end - end - if parsed.is_absolute then path = "/" .. path end - return path -end - -return _M diff --git a/modfile.txt b/modfile.txt index 15f7de3..d12f4f9 100644 --- a/modfile.txt +++ b/modfile.txt @@ -9,4 +9,6 @@ SJSON "helptext.en.sjson" To "Game/Text/fr/HelpText.fr.sjson" SJSON "helptext.fr.sjson" To "Game/Text/zh-CN/HelpText.zh-CN.sjson" -SJSON "helptext.zh-CN.sjson" \ No newline at end of file +SJSON "helptext.zh-CN.sjson" +To "StyxScribeScripts/twitch_socket.py" +Replace "twitch_socket.py" \ No newline at end of file diff --git a/twitch.lua b/twitch.lua index 8c661fb..fe669a3 100644 --- a/twitch.lua +++ b/twitch.lua @@ -1,11 +1,5 @@ -package.path = "../Content/Mods/TwitchIntegration/libs/share/?.lua;" .. package.path -package.cpath = "../Content/Mods/TwitchIntegration/libs/lib/?.dll;" .. package.cpath -local socket = require("socket") - local voting = 0 local isconnected = 0 -local channeljoined = 0 -local client = nil local chosentable = {} local votes = {} @@ -20,7 +14,7 @@ TwitchIntegration.NoVoteRooms = { "RoomPreRun", "DeathAreaBedroom", "DeathArea", TwitchIntegration.Data.VotingWindow = { Components = {} } TwitchIntegration.Data.NextVotingWindow = { Components = {} } -function TwitchIntegration.TwitchIntegration.IsInArray(array,value) +function TwitchIntegration.IsInArray(array,value) for _,k in ipairs(array) do if k == value then return true @@ -38,7 +32,7 @@ function TwitchIntegration.IsInGame() end -- If in an invalid room - for _,roomname in ipairs(NoVoteRooms) do + for _,roomname in ipairs(TwitchIntegration.NoVoteRooms) do if string.find(croomname, roomname) then return false end @@ -308,6 +302,8 @@ function TwitchIntegration.CountdownVote() TwitchIntegration.OpenVotingWindow() local votingtime = TwitchIntegration.Config.VotingTime + + for i=votingtime,1,-1 do if TwitchIntegration.IsInGame() then @@ -379,67 +375,42 @@ function TwitchIntegration.CountdownVote() thread(TwitchIntegration.TimeBetweenVote) end -function TwitchIntegration.TwitchConnect() -- Here we start the twitch integration on a thread - client = socket.tcp() - host = "irc.chat.twitch.tv" - nick = "justinfan1893" - - --Connect. Please connect. - isconnected = client:connect(host, 6667) - client:settimeout(0, t) - - --If we fail a connection - if isconnected ~= 1 then - client:close() - isconnected = 0 - end - - client:send("NICK " .. nick .. "\r\n") - - local resp, err = nil - while err == nil do --This is the main loop for recieving data - resp, err = client:receive() - if resp ~= nil then - if string.find(resp,"PRIVMSG") and voting == 1 then -- If we are accepting votes and a twitch message comes in - local a,b = string.find(resp,"PRIVMSG #" .. TwitchIntegration.Config.Username .. " :",1,true) - local incmessage = string.sub(resp,b+1) - - local startindex = string.find(resp,':') - local endindex = string.find(resp,'!') - local sender = string.sub(resp,startindex + 1, endindex-1) - - --If sender has not already made a vote - if not TwitchIntegration.IsInArray(voters,sender) then - for i=1,TwitchIntegration.Config.OfferedChoices do - if incmessage:find("^" .. i) ~= nil then -- If a 1 (or x emote in future) was found at BEGINNING - votes[i] = votes[i] + 1 - table.insert(voters,sender) - break - end +function TwitchIntegration.ReceiveNewMessage(resp) + if string.find(resp,"PRIVMSG") and voting == 1 then -- If we are accepting votes and a twitch message comes in + local a,b = string.find(resp,"PRIVMSG #" .. TwitchIntegration.Config.Username .. " :",1,true) + local incmessage = string.sub(resp,b+1) + + local startindex = string.find(resp,':') + local endindex = string.find(resp,'!') + local sender = string.sub(resp,startindex + 1, endindex-1) + + --If sender has not already made a vote + if not TwitchIntegration.IsInArray(voters,sender) then + for i=1,TwitchIntegration.Config.OfferedChoices do + if incmessage:find("^" .. i) ~= nil then -- If a 1 (or x emote in future) was found at BEGINNING + votes[i] = votes[i] + 1 + if TwitchIntegration.Config.SendConfirmMessage then + print("TwitchSocket:SendMessage:Thanks for the vote " .. sender) end + table.insert(voters,sender) + break end - - --This needs more filtering, we have the message but now we need to extract the first number that is 1-4 - elseif string.find(resp,"tmi.twitch.tv 376") then -- We got the Hello Message... Join a channel - client:send("JOIN #" .. TwitchIntegration.Config.Username .."\r\n") - elseif string.find(resp, "tmi.twitch.tv JOIN") then -- We finished joining a channel. - channeljoined = 1 - thread(TwitchIntegration.TimeBetweenVote) - elseif string.find(resp,"PING :tmi.twitch.tv") then -- We reveived a PING, reply back with PONG! - client:send("PONG :tmi.twitch.tv\r\n") end end - if err == 'timeout' then -- We expect timeouts because receive will timeout while waiting for incomming messages. This allows it to be non blocking. - err = nil - end - wait(1) + --This needs more filtering, we have the message but now we need to extract the first number that is 1-4 + elseif string.find(resp, "tmi.twitch.tv JOIN") then -- We finished joining a channel. + thread(TwitchIntegration.TimeBetweenVote) + isconnected = 1 + thread(function() + wait(TwitchIntegration.Config.TimeBetweenVotes) + isconnected = 0 + end) end - client:close() - isconnected = 0 - channeljoined = 0 end +StyxScribe.AddHook(TwitchIntegration.ReceiveNewMessage, "TwitchIntegration:NewMessage:", TwitchIntegration) + function TwitchIntegration.LoadTrigger(triggerArgs) if triggerArgs ~= nil and triggerArgs.name ~= nil then croomname = triggerArgs.name @@ -449,9 +420,22 @@ function TwitchIntegration.LoadTrigger(triggerArgs) if isconnected == 0 and CurrentRun ~= nil then if CurrentRun.CurrentRoom ~= nil then - thread(TwitchIntegration.TwitchConnect) + --close the socket so we dont accidentally start it over again + print("TwitchSocket:Stop") + --start the socket + print("TwitchSocket:SetUsername:" .. TwitchIntegration.Config.Username) + print("TwitchSocket:SetChannel:#" .. TwitchIntegration.Config.Username) + print("TwitchSocket:SetToken:" .. TwitchIntegration.Config.OAuth) + print("TwitchSocket:Start") + end end end -OnAnyLoad{TwitchIntegration.LoadTrigger} +OnAnyLoad{function() + print(GetMapName({})) + if not string.find(GetMapName({}), "DeathArea") and GetMapName({}) ~= "RoomPreRun" then + print("Load") + TwitchIntegration.LoadTrigger() + end +end} \ No newline at end of file diff --git a/twitch_socket.py b/twitch_socket.py new file mode 100644 index 0000000..f24a193 --- /dev/null +++ b/twitch_socket.py @@ -0,0 +1,88 @@ +import socket +import threading + +sock = None +thread = None + +server = 'irc.chat.twitch.tv' +port = 6667 +nickname = '' +token = '' +channel = '' +is_listening = False +is_connected = False + +def listen_for_message(): + print(is_listening) + print(is_connected) + while is_connected and is_listening: + print("Twitch Socket Waiting For Message") + resp = sock.recv(2048).decode('utf-8') + + if len(resp) == 0: + print("No length") + + if resp.startswith('PING'): + sock.send("PONG\n".encode('utf-8')) + elif len(resp) > 0: + print(resp) + scribe.send("TwitchIntegration:NewMessage:" + resp) + +def send_twitch_message(message): + twitchMessage = "PRIVMSG " + channel + " :" + message + "\r\n" + print("Outgoing Message: " + twitchMessage) + + #sock.sendto(message.encode('utf-8'),(channel, port)) + sock.send(twitchMessage.encode('utf-8')) + +def set_username(message): + global nickname + nickname = message + +def set_channel(message): + global channel + channel = message + +def set_token(message): + global token + token = message + +def start_listener(message): + global is_connected + global is_listening + global thread + global sock + + if sock is None: + sock = socket.socket() + + sock.connect((server, port)) + + sock.send(f"PASS {token}\n".encode('utf-8')) + sock.send(f"NICK {nickname}\n".encode('utf-8')) + sock.send(f"JOIN {channel}\n".encode('utf-8')) + + is_connected = True + is_listening = True + + thread = threading.Thread(target=listen_for_message, args=()) + thread.start() + +def stop_listener(message): + global sock + print("Stop") + if sock is not None: + is_connected = False + is_listening = False + sock.shutdown(socket.SHUT_RDWR) + sock.close() + sock = socket.socket() + +def load( ): + scribe.add_hook( send_twitch_message, "TwitchSocket:SendMessage:", __name__ ) + scribe.add_hook( set_username, "TwitchSocket:SetUsername:", __name__ ) + scribe.add_hook( set_channel, "TwitchSocket:SetChannel:", __name__ ) + scribe.add_hook( set_token, "TwitchSocket:SetToken:", __name__ ) + scribe.add_hook( start_listener, "TwitchSocket:Start", __name__ ) + scribe.add_hook( stop_listener, "TwitchSocket:Stop", __name__ ) +