diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51f897d --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +auth.txt + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +.vs/slnx.sqlite +.vs \ No newline at end of file diff --git a/README.md b/README.md index de733fa..51f1376 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,56 @@ -# discord -A Minetest Discord bridge with flexibility. +# Discord for Minetest +____ +######_Part of the SolarSail family._ +### Notice: + +This mod requires LuaJIT; attempts at running it as a standard Lua interpreter will result in Minetest failing to execute this mod. + +### Preparation: + +`git clone` or manually download a zip of this folder and extract it to `minetest/mods` resulting in `minetest/mods/discord`. + +Install cURL. You can find this in your package manager, in the case of Windows, this is provided for you. + +### Golang Bot Setup Procedure: + +Install the latest version of [Golang](https://golang.org/). + +Using the terminal, execute `go get github.com/bwmarrin/discordgo` + +Afterwards, you can test that the Golang is installed and functioning correctly for your environment by running `go run main.go` inside the `discord` folder. + +By default, the bot will complain of a missing `auth.txt` and generate an empty file for you. + +The error, will always come out as this in the terminal: + +``` +$ go run main.go +Failed to load auth.txt, a file named auth.txt has been created. +The file should contain the following lines: + + + + +``` + +The auth.txt contents should look like this example, but not exactly as the token, server ID and channel ID can differ dramatically: + +``` +abcdefghijklmop1234.5 +1234567890987654321 +0987654321234567890 +``` + +### Minetest Mod Setup Procedure + +Add this mod to the chosen world via `worldmods` or by including it from the `mods` directory. + +Grant this mod insecure environment access via `security.trusted_mods = discord`. + +### Licensing: + +This mod (`main.go` and `init.lua`) is licensed as MIT. + +Discord.lua is licensed as MIT and can be found here: https://github.com/videah/discord.lua + +`luajit-request`'s license can be found at `discord/discord/luajit-request/LICENSE` \ No newline at end of file diff --git a/bin/curl.exe b/bin/curl.exe new file mode 100644 index 0000000..c310955 Binary files /dev/null and b/bin/curl.exe differ diff --git a/bin/libcurl.exe b/bin/libcurl.exe new file mode 100644 index 0000000..ede2325 Binary files /dev/null and b/bin/libcurl.exe differ diff --git a/discord/class.lua b/discord/class.lua new file mode 100644 index 0000000..c5ad353 --- /dev/null +++ b/discord/class.lua @@ -0,0 +1,182 @@ +local middleclass = { + _VERSION = 'middleclass v3.1.0', + _DESCRIPTION = 'Object Orientation for Lua', + _URL = 'https://github.com/kikito/middleclass', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2011 Enrique GarcĂ­a Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local function _setClassDictionariesMetatables(aClass) + local dict = aClass.__instanceDict + dict.__index = dict + + local super = aClass.super + if super then + local superStatic = super.static + setmetatable(dict, super.__instanceDict) + setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or superStatic[k] end }) + else + setmetatable(aClass.static, { __index = function(_,k) return dict[k] end }) + end +end + +local function _setClassMetatable(aClass) + setmetatable(aClass, { + __tostring = function() return "class " .. aClass.name end, + __index = aClass.static, + __newindex = aClass.__instanceDict, + __call = function(self, ...) return self:new(...) end + }) +end + +local function _createClass(name, super) + local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} } + aClass.subclasses = setmetatable({}, {__mode = "k"}) + + _setClassDictionariesMetatables(aClass) + _setClassMetatable(aClass) + + return aClass +end + +local function _createLookupMetamethod(aClass, name) + return function(...) + local method = aClass.super[name] + assert( type(method)=='function', tostring(aClass) .. " doesn't implement metamethod '" .. name .. "'" ) + return method(...) + end +end + +local function _setClassMetamethods(aClass) + for _,m in ipairs(aClass.__metamethods) do + aClass[m]= _createLookupMetamethod(aClass, m) + end +end + +local function _setDefaultInitializeMethod(aClass, super) + aClass.initialize = function(instance, ...) + return super.initialize(instance, ...) + end +end + +local function _includeMixin(aClass, mixin) + assert(type(mixin)=='table', "mixin must be a table") + for name,method in pairs(mixin) do + if name ~= "included" and name ~= "static" then aClass[name] = method end + end + if mixin.static then + for name,method in pairs(mixin.static) do + aClass.static[name] = method + end + end + if type(mixin.included)=="function" then mixin:included(aClass) end + aClass.__mixins[mixin] = true +end + +local Object = _createClass("Object", nil) + +Object.static.__metamethods = { '__add', '__band', '__bor', '__bxor', '__bnot', '__call', '__concat', + '__div', '__eq', '__ipairs', '__idiv', '__le', '__len', '__lt', '__mod', + '__mul', '__pairs', '__pow', '__shl', '__shr', '__sub', '__tostring', '__unm' } + +function Object.static:allocate() + assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") + return setmetatable({ class = self }, self.__instanceDict) +end + +function Object.static:new(...) + local instance = self:allocate() + instance:initialize(...) + return instance +end + +function Object.static:subclass(name) + assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") + assert(type(name) == "string", "You must provide a name(string) for your class") + + local subclass = _createClass(name, self) + _setClassMetamethods(subclass) + _setDefaultInitializeMethod(subclass, self) + self.subclasses[subclass] = true + self:subclassed(subclass) + + return subclass +end + +function Object.static:subclassed(other) end + +function Object.static:isSubclassOf(other) + return type(other) == 'table' and + type(self) == 'table' and + type(self.super) == 'table' and + ( self.super == other or + type(self.super.isSubclassOf) == 'function' and + self.super:isSubclassOf(other) + ) +end + +function Object.static:include( ... ) + assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'") + for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end + return self +end + +function Object.static:includes(mixin) + return type(mixin) == 'table' and + type(self) == 'table' and + type(self.__mixins) == 'table' and + ( self.__mixins[mixin] or + type(self.super) == 'table' and + type(self.super.includes) == 'function' and + self.super:includes(mixin) + ) +end + +function Object:initialize() end + +function Object:__tostring() return "instance of " .. tostring(self.class) end + +function Object:isInstanceOf(aClass) + return type(self) == 'table' and + type(self.class) == 'table' and + type(aClass) == 'table' and + ( aClass == self.class or + type(aClass.isSubclassOf) == 'function' and + self.class:isSubclassOf(aClass) + ) +end + + + +function middleclass.class(name, super, ...) + super = super or Object + return super:subclass(name, ...) +end + +middleclass.Object = Object + +setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) + +return middleclass \ No newline at end of file diff --git a/discord/client.lua b/discord/client.lua new file mode 100644 index 0000000..bbdbda1 --- /dev/null +++ b/discord/client.lua @@ -0,0 +1,299 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +------------------------------------- +-- Discord Client Class +-- @module Client + +local path = (...):match('(.-)[^%.]+$') + +local request = require(path .. 'wrapper') + +local class = require(path .. 'class') +local json = require(path .. 'json') +local endpoints = require(path .. 'endpoints') +local util = require(path .. 'utils') + +local Message = require(path .. 'message') + +print('Loaded client') + +local Client = class('ClientObject') + +--- Internally initialize's the Client class. +--- Please use Client:new(options) instead. +-- @param options Options table (Currently useless.) +function Client:initialize(options) + + self.isLoggedIn = false + self.options = options + self.token = '' + self.email = '' + self.user = {} + + self.headers = {} + + self.headers['authorization'] = self.token + self.headers['Content-Type'] = 'application/json' + + self.callbacks = {} + + self.socket = nil + +end + +--- Logs the Client into Discord's servers. +--- This is required to use most of the Clients functions. +-- @param email E-mail Address of the account to log in. +-- @param password Password of the account to log in. +-- (WARNING: Do NOT store this password in a GitHub repo or anything of the sorts.) +-- @return True or False depending on success. +function Client:login(email, password) + + local payload = { + email = email, + password = password + } + + local response = request.send(endpoints.login, 'POST', payload, self.headers) + local success = util.responseIsSuccessful(response) + + if success then + + self.token = json.decode(response.body).token + self.isLoggedIn = true + self.headers['authorization'] = self.token + self.email = email + + self.user = self:getCurrentUser() + + end + + return self.token + +end + +function Client:loginWithToken(token) + + self.token = token + self.isLoggedIn = true + self.headers['authorization'] = "Bot "..token + + self.user = self:getCurrentUser() + + return token +end + +--- Logs the Client out of the Discord server. +--- (This is currently useless/does nothing) +-- @param email E-mail Address of the account to log in. +-- @param password Password of the account to log in. +-- (WARNING: Do NOT store this password in a GitHub repo or anything of the sorts.) +-- @return True or False depending on success. +function Client:logout() + + if self.isLoggedIn then + + local payload = { + token = self.token + } + + local response = request.send(endpoints.login, 'POST', payload) + local success = util.responseIsSuccessful(response) + + if success then + self.isLoggedIn = false + self.token = nil + end + + return success + + else + return false + end + +end + +--- Gets the current authentication token. +--- (Only if logged in of course.) +-- @return Authentication Token +function Client:getToken() + + if self.isLoggedIn then + return self.token + else + return nil + end + +end + +--- Gets the current Gateway we are connected to. +--- (Only if logged in of course.) +-- @return Gateway URL +function Client:getGateway() + + if self.isLoggedIn then + + local response = request.send(endpoints.gateway, 'GET', nil, self.headers) + + if util.responseIsSuccessful(response) then + return json.decode(response.body).url + else + return nil + end + + end + +end + +--- Gets the current User information. +--- (Only if logged in of course.) +-- @return User Table +function Client:getCurrentUser() + + if self.isLoggedIn then + + local response = request.send(endpoints.users .. '/@me', 'GET', nil, self.headers) + if util.responseIsSuccessful(response) then + return json.decode(response.body) + else + return nil + end + + else + return nil + end + +end + +--- Gets a table of Servers the current User is connected to. +--- (Only if logged in of course.) +-- @return Server Table +function Client:getServerList() + + if self.isLoggedIn then + + local response = request.send(endpoints.users .. '/@me/guilds', 'GET', nil, self.headers) + + if util.responseIsSuccessful(response) then + self.user = json.decode(response.body) + return json.decode(response.body) + else + return nil + end + + else + return nil + end + +end + +--- Gets a table of Channels from the provided Server id. +--- (Only if logged in of course.) +-- @param id Server ID +-- @return Channel Table +function Client:getChannelList(id) + + if self.isLoggedIn then + + local response = request.send(endpoints.servers .. '/' .. id .. '/channels', 'GET', nil, self.headers) + + if util.responseIsSuccessful(response) then + return json.decode(response.body) + else + return nil + end + + else + return nil + end + +end + +--- Sends a Message from the Client to a Channel. +--- (Only if logged in of course.) +-- @param message Message to be sent. +-- @param id ID for Channel to send to. +-- @return Message Class +function Client:sendMessage(message, id) + + if self.isLoggedIn then + + local payload = { + content = tostring(message) + } + + local response = request.send(endpoints.channels .. '/' .. id .. '/messages', 'POST', payload, self.headers) + + return Message:new(json.decode(response.body), self.token) + + else + return nil + end + +end + +--- Edits a sent Message. +--- (Only if logged in of course.) +-- @param message The new message to replace the old one. +-- @param msgClass Message Class to edit. +-- @return Message Class +function Client:editMessage(message, msgClass) + + if self.isLoggedIn then + + msgClass:edit(message) + + end + +end + +--- Deletes a sent Message. +--- (Only if logged in of course.) +-- @param msgClass Message Class to delete. +-- @return Message Class +function Client:deleteMessage(msgClass) + + if self.isLoggedIn then + + msgClass:delete() + + end + +end + +--- Starts the Client loop. +--- (Does nothing of real use at the moment, will interact with WebSockets in the future) +function Client:run() + + if self.isLoggedIn then + + while true do + if self.callbacks.think then self.callbacks.think() end + + end + + end + +end + +return Client diff --git a/discord/endpoints.lua b/discord/endpoints.lua new file mode 100644 index 0000000..6d1370b --- /dev/null +++ b/discord/endpoints.lua @@ -0,0 +1,35 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local endp = {} + +endp.base = 'https://discordapp.com' +endp.apiBase = endp.base .. '/api' +endp.gateway = endp.apiBase .. '/gateway' +endp.users = endp.apiBase .. '/users' +endp.register = endp.apiBase .. '/auth/register' +endp.login = endp.apiBase .. '/auth/login' +endp.logout = endp.apiBase .. '/auth/logout' +endp.servers = endp.apiBase .. '/guilds' +endp.channels = endp.apiBase .. '/channels' + +return endp \ No newline at end of file diff --git a/discord/init.lua b/discord/init.lua new file mode 100644 index 0000000..c1c7cd7 --- /dev/null +++ b/discord/init.lua @@ -0,0 +1,35 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local path = (...):match('(.-)[^%.]+$') + +local discord = { + + _VERSION = '0.0.1', + _DESCRIPTION = 'Lua API Wrapper for Discord' + +} + +discord.Client = require(path .. 'client') +discord.Message = require(path .. 'message') + +return discord \ No newline at end of file diff --git a/discord/json.lua b/discord/json.lua new file mode 100644 index 0000000..c11a39d --- /dev/null +++ b/discord/json.lua @@ -0,0 +1,375 @@ +-- +-- json.lua +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- + +local json = { _version = "0.1.0" } + +-- Encode + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + +-- Decode + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + return ( parse(str, next_char(str, 1, space_chars, true)) ) +end + + +return json \ No newline at end of file diff --git a/discord/luajit-request/LICENSE.txt b/discord/luajit-request/LICENSE.txt new file mode 100644 index 0000000..0753074 --- /dev/null +++ b/discord/luajit-request/LICENSE.txt @@ -0,0 +1,11 @@ +Copyright (c) 2014 Lucien Greathouse + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/discord/luajit-request/init.lua b/discord/luajit-request/init.lua new file mode 100644 index 0000000..84c14ca --- /dev/null +++ b/discord/luajit-request/init.lua @@ -0,0 +1,363 @@ +--[[ +LuaJIT-Request +Lucien Greathouse +Wrapper for LuaJIT-cURL for easy HTTP(S) requests. + +Copyright (c) 2016 Lucien Greathouse + +This software is provided 'as-is', without any express +or implied warranty. In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, andto alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must +not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. +]] + +local path = (...):gsub("%.init$", ""):match("%.?(.-)$") .. "." + +local ffi = require("ffi") +local curl = require(path .. "luajit-curl") +local request + +local function url_encode(str) + if (str) then + str = str:gsub("\n", "\r\n") + str = str:gsub("([^%w %-%_%.%~])", function(c) + return string.format ("%%%02X", string.byte(c)) + end) + str = str:gsub(" ", "%%20") + end + return str +end + +local function cookie_encode(str, name) + str = str:gsub("[,;%s]", "") + + if (name) then + str = str:gsub("=", "") + end + + return str +end + +local auth_map = { + BASIC = ffi.cast("long", curl.CURLAUTH_BASIC), + DIGEST = ffi.cast("long", curl.CURLAUTH_DIGEST), + NEGOTIATE = ffi.cast("long", curl.CURLAUTH_NEGOTIATE) +} + +local errors = { + unknown = 0, + timeout = 1, + connect = 2, + resolve_host = 3 +} + +local code_map = { + [curl.CURLE_OPERATION_TIMEDOUT] = { + errors.timeout, "Connection timed out" + }, + [curl.CURLE_COULDNT_RESOLVE_HOST] = { + errors.resolve_host, "Couldn't resolve host" + }, + [curl.CURLE_COULDNT_CONNECT] = { + errors.connect, "Couldn't connect to host" + } +} + +request = { + error = errors, + + version = "2.4.0", + version_major = 2, + version_minor = 4, + version_patch = 0, + + --[[ + Send an HTTP(S) request to the URL at 'url' using the HTTP method 'method'. + Use the 'args' parameter to optionally configure the request: + - method: HTTP method to use. Defaults to "GET", but can be any HTTP verb like "POST" or "PUT" + - headers: Dictionary of additional HTTP headers to send with request + - data: Dictionary or string to send as request body + - cookies: Dictionary table of cookies to send + - timeout: How long to wait for the connection to be made before giving up + - allow_redirects: Whether or not to allow redirection. Defaults to true + - body_stream_callback: A method to call with each piece of the response body. + - header_stream_callback: A method to call with each piece of the resulting header. + - transfer_info_callback: A method to call with transfer progress data. + - auth_type: Authentication method to use. Defaults to "none", but can also be "basic", "digest" or "negotiate" + - username: A username to use with authentication. 'auth_type' must also be specified. + - password: A password to use with authentication. 'auth_type' must also be specified. + - files: A dictionary of file names to their paths on disk to upload via stream. + + If both body_stream_callback and header_stream_callback are defined, a boolean true will be returned instead of the following object. + + The return object is a dictionary with the following members: + - code: The HTTP status code the response gave. Will not exist if header_stream_callback is defined above. + - body: The body of the response. Will not exist if body_stream_callback is defined above. + - headers: A dictionary of headers and their values. Will not exist if header_stream_callback is defined above. + - headers_raw: A raw string containing the actual headers the server sent back. Will not exist if header_stream_callback is defined above. + - set_cookies: A dictionary of cookies given by the "Set-Cookie" header from the server. Will not exist if the server did not set any cookies. + + ]] + send = function(url, args) + local handle = curl.curl_easy_init() + local header_chunk + local out_buffer + local headers_buffer + args = args or {} + + local callbacks = {} + local gc_handles = {} + + curl.curl_easy_setopt(handle, curl.CURLOPT_URL, url) + curl.curl_easy_setopt(handle, curl.CURLOPT_SSL_VERIFYPEER, 1) + curl.curl_easy_setopt(handle, curl.CURLOPT_SSL_VERIFYHOST, 2) + + if (args.method) then + local method = string.upper(tostring(args.method)) + + if (method == "GET") then + curl.curl_easy_setopt(handle, curl.CURLOPT_HTTPGET, 1) + elseif (method == "POST") then + curl.curl_easy_setopt(handle, curl.CURLOPT_POST, 1) + else + curl.curl_easy_setopt(handle, curl.CURLOPT_CUSTOMREQUEST, method) + end + end + + if (args.headers) then + for key, value in pairs(args.headers) do + header_chunk = curl.curl_slist_append(header_chunk, tostring(key) .. ":" .. tostring(value)) + end + + curl.curl_easy_setopt(handle, curl.CURLOPT_HTTPHEADER, header_chunk) + end + + if (args.auth_type) then + local auth = string.upper(tostring(args.auth_type)) + + if (auth_map[auth]) then + curl.curl_easy_setopt(handle, curl.CURLOPT_HTTPAUTH, auth_map[auth]) + curl.curl_easy_setopt(handle, curl.CURLOPT_USERNAME, tostring(args.username)) + curl.curl_easy_setopt(handle, curl.CURLOPT_PASSWORD, tostring(args.password or "")) + elseif (auth ~= "NONE") then + error("Unsupported authentication type '" .. auth .. "'") + end + end + + if (args.body_stream_callback) then + local callback = ffi.cast("curl_callback", function(data, size, nmeb, user) + args.body_stream_callback(ffi.string(data, size * nmeb)) + return size * nmeb + end) + + table.insert(callbacks, callback) + + curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEFUNCTION, callback) + else + out_buffer = {} + + local callback = ffi.cast("curl_callback", function(data, size, nmeb, user) + table.insert(out_buffer, ffi.string(data, size * nmeb)) + return size * nmeb + end) + + table.insert(callbacks, callback) + + curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEFUNCTION, callback) + end + + if (args.header_stream_callback) then + local callback = ffi.cast("curl_callback", function(data, size, nmeb, user) + args.header_stream_callback(ffi.string(data, size * nmeb)) + return size * nmeb + end) + + table.insert(callbacks, callback) + + curl.curl_easy_setopt(handle, curl.CURLOPT_HEADERFUNCTION, callback) + else + headers_buffer = {} + + local callback = ffi.cast("curl_callback", function(data, size, nmeb, user) + table.insert(headers_buffer, ffi.string(data, size * nmeb)) + return size * nmeb + end) + + table.insert(callbacks, callback) + + curl.curl_easy_setopt(handle, curl.CURLOPT_HEADERFUNCTION, callback) + end + + if (args.transfer_info_callback) then + local callback = ffi.cast("curl_xferinfo_callback", function(client, dltotal, dlnow, ultotal, ulnow) + args.transfer_info_callback(tonumber(dltotal), tonumber(dlnow), tonumber(ultotal), tonumber(ulnow)) + return 0 + end) + + table.insert(callbacks, callback) + + curl.curl_easy_setopt(handle, curl.CURLOPT_NOPROGRESS, 0) + curl.curl_easy_setopt(handle, curl.CURLOPT_XFERINFOFUNCTION, callback) + end + + if (args.follow_redirects == nil) then + curl.curl_easy_setopt(handle, curl.CURLOPT_FOLLOWLOCATION, true) + else + curl.curl_easy_setopt(handle, curl.CURLOPT_FOLLOWLOCATION, not not args.follow_redirects) + end + + if (args.data) then + if (type(args.data) == "table") then + local buffer = {} + for key, value in pairs(args.data) do + table.insert(buffer, ("%s=%s"):format(url_encode(key), url_encode(value))) + end + + curl.curl_easy_setopt(handle, curl.CURLOPT_POSTFIELDS, table.concat(buffer, "&")) + else + curl.curl_easy_setopt(handle, curl.CURLOPT_POSTFIELDS, tostring(args.data)) + end + end + + local post + if (args.files) then + post = ffi.new("struct curl_httppost*[1]") + local lastptr = ffi.new("struct curl_httppost*[1]") + + for key, value in pairs(args.files) do + local file = ffi.new("char[?]", #value, value) + + table.insert(gc_handles, file) + + local res = curl.curl_formadd( + post, lastptr, + ffi.new("int", curl.CURLFORM_COPYNAME), key, + ffi.new("int", curl.CURLFORM_FILE), file, + ffi.new("int", curl.CURLFORM_END) + ) + end + + curl.curl_easy_setopt(handle, curl.CURLOPT_HTTPPOST, post[0]) + end + + -- Enable the cookie engine + curl.curl_easy_setopt(handle, curl.CURLOPT_COOKIEFILE, "") + + if (args.cookies) then + local cookie_out + + if (type(args.cookies) == "table") then + local buffer = {} + for key, value in pairs(args.cookies) do + table.insert(buffer, ("%s=%s"):format(cookie_encode(key, true), cookie_encode(value))) + end + + cookie_out = table.concat(buffer, "; ") + else + cookie_out = tostring(args.cookies) + end + + curl.curl_easy_setopt(handle, curl.CURLOPT_COOKIE, cookie_out) + end + + if (tonumber(args.timeout)) then + curl.curl_easy_setopt(handle, curl.CURLOPT_CONNECTTIMEOUT, tonumber(args.timeout)) + end + + local code = curl.curl_easy_perform(handle) + + if (code ~= curl.CURLE_OK) then + local num = tonumber(code) + + if (code_map[num]) then + return false, code_map[num][1], code_map[num][2] + end + + return false, request.error.unknown, "Unknown error", num + end + + local out + + if (out_buffer or headers_buffer) then + local headers, status, parsed_headers, raw_cookies, set_cookies + + if (headers_buffer) then + headers = table.concat(headers_buffer) + status = headers:match("%s+(%d+)%s+") + + parsed_headers = {} + + for key, value in headers:gmatch("\n([^:]+): *([^\r\n]*)") do + parsed_headers[key] = value + end + end + + local cookielist = ffi.new("struct curl_slist*[1]") + curl.curl_easy_getinfo(handle, curl.CURLINFO_COOKIELIST, cookielist) + if cookielist[0] ~= nil then + raw_cookies, set_cookies = {}, {} + local cookielist = ffi.gc(cookielist[0], curl.curl_slist_free_all) + local cookie = cookielist + + repeat + local raw = ffi.string(cookie[0].data) + table.insert(raw_cookies, raw) + + local domain, subdomains, path, secure, expiration, name, value = raw:match("^(.-)\t(.-)\t(.-)\t(.-)\t(.-)\t(.-)\t(.*)$") + set_cookies[name] = value + cookie = cookie[0].next + until cookie == nil + end + + out = { + body = table.concat(out_buffer), + headers = parsed_headers, + raw_cookies = raw_cookies, + set_cookies = set_cookies, + code = status, + raw_headers = headers + } + else + out = true + end + + curl.curl_easy_cleanup(handle) + curl.curl_slist_free_all(header_chunk) + + if (post) then + curl.curl_formfree(post[0]) + end + gc_handles = {} + + for i, v in ipairs(callbacks) do + v:free() + end + + return out + end, + + init = function() + curl.curl_global_init(curl.CURL_GLOBAL_ALL) + end, + + close = function() + curl.curl_global_cleanup() + end +} + +request.init() + +return request diff --git a/discord/luajit-request/luajit-curl.lua b/discord/luajit-request/luajit-curl.lua new file mode 100644 index 0000000..662a116 --- /dev/null +++ b/discord/luajit-request/luajit-curl.lua @@ -0,0 +1,1016 @@ +--[[ +LuaJIT-cURL +Lucien Greathouse +LuaJIT FFI cURL binding aimed at cURL version 7.38.0. + +Copyright (c) 2014 lucien Greathouse + +This software is provided 'as-is', without any express +or implied warranty. In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, andto alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must +not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. +]] + +local ffi = require("ffi") +local curl = ffi.load("libcurl") +--"C:\Users\jorda\Desktop\minetest-0.4.15\games\Solar_Plains\mods\discord\curl.exe" +if (jit.os == "Windows") then + --Windows! + ffi.cdef([[ + //windows layering + enum { + INVALID_SOCKET = ~0, + SOCKET_BAD = ~0 + }; + ]]) +else + --Not Windows! + ffi.cdef([[ + typedef int socket_t; + + enum { + SOCKET_BAD = -1 + }; + ]]) +end + +ffi.cdef([[ + typedef int64_t time_t; + typedef unsigned int size_t; + + typedef size_t (*curl_callback)(char *data, size_t size, size_t nmeb, void *userdata); +]]) + +--curlver.h +ffi.cdef([[ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +enum { + LIBCURL_VERSION_MAJOR = 7, + LIBCURL_VERSION_MINOR = 38, + LIBCURL_VERSION_PATCH = 0, + LIBCURL_VERSION_NUM = 0x072600 +} +]]) + +--cURL's type aliasing, built around curlbuild.h +ffi.cdef([[ + typedef int64_t curl_off_t; +]]) + +--Constants +ffi.cdef([[ +enum { + CURL_GLOBAL_SSL = (1<<0), + CURL_GLOBAL_WIN32 = (1<<1), + CURL_GLOBAL_ALL = (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32), + CURL_GLOBAL_NOTHING = 0, + CURL_GLOBAL_DEFAULT = CURL_GLOBAL_ALL, + CURL_GLOBAL_ACK_EINTR = (1<<2) +}; + +enum { + CURLAUTH_NONE = 0, + CURLAUTH_BASIC = 1, + CURLAUTH_DIGEST = 1<<1, + CURLAUTH_NEGOTIATE = 1<<2 +}; +]]) + +ffi.cdef([[ +typedef void CURL; +typedef int curl_socket_t; +typedef struct curl_httppost { +struct curl_httppost *next; +char *name; +long namelength; +char *contents; +long contentslength; +char *buffer; +long bufferlength; +char *contenttype; +struct curl_slist* contentheader; +struct curl_httppost *more; +long flags; +char *showfilename; +void *userp; +}; +typedef int (*curl_progress_callback)(void *clientp, +double dltotal, +double dlnow, +double ultotal, +double ulnow); +typedef int (*curl_xferinfo_callback)(void *clientp, +curl_off_t dltotal, +curl_off_t dlnow, +curl_off_t ultotal, +curl_off_t ulnow); +typedef size_t (*curl_write_callback)(char *buffer, +size_t size, +size_t nitems, +void *outstream); +typedef enum { +CURLFILETYPE_FILE = 0, +CURLFILETYPE_DIRECTORY, +CURLFILETYPE_SYMLINK, +CURLFILETYPE_DEVICE_BLOCK, +CURLFILETYPE_DEVICE_CHAR, +CURLFILETYPE_NAMEDPIPE, +CURLFILETYPE_SOCKET, +CURLFILETYPE_DOOR, +CURLFILETYPE_UNKNOWN +} curlfiletype; +struct curl_fileinfo { +char *filename; +curlfiletype filetype; +time_t time; +unsigned int perm; +int uid; +int gid; +curl_off_t size; +long int hardlinks; +struct { +char *time; +char *perm; +char *user; +char *group; +char *target; +} strings; +unsigned int flags; +char * b_data; +size_t b_size; +size_t b_used; +}; +typedef long (*curl_chunk_bgn_callback)(const void *transfer_info, +void *ptr, +int remains); +typedef long (*curl_chunk_end_callback)(void *ptr); +typedef int (*curl_fnmatch_callback)(void *ptr, +const char *pattern, +const char *string); +typedef int (*curl_seek_callback)(void *instream, +curl_off_t offset, +int origin); +typedef size_t (*curl_read_callback)(char *buffer, +size_t size, +size_t nitems, +void *instream); +typedef enum { +CURLSOCKTYPE_IPCXN, +CURLSOCKTYPE_ACCEPT, +CURLSOCKTYPE_LAST +} curlsocktype; +typedef int (*curl_sockopt_callback)(void *clientp, +curl_socket_t curlfd, +curlsocktype purpose); +struct sockaddr { +uint8_t sa_family; +char sa_data[14]; +}; +struct curl_sockaddr { +int family; +int socktype; +int protocol; +unsigned int addrlen; +struct sockaddr addr; +}; +typedef curl_socket_t +(*curl_opensocket_callback)(void *clientp, +curlsocktype purpose, +struct curl_sockaddr *address); +typedef int +(*curl_closesocket_callback)(void *clientp, curl_socket_t item); +typedef enum { +CURLIOE_OK, +CURLIOE_UNKNOWNCMD, +CURLIOE_FAILRESTART, +CURLIOE_LAST +} curlioerr; +typedef enum { +CURLIOCMD_NOP, +CURLIOCMD_RESTARTREAD, +CURLIOCMD_LAST +} curliocmd; +typedef curlioerr (*curl_ioctl_callback)(CURL *handle, +int cmd, +void *clientp); +typedef void *(*curl_malloc_callback)(size_t size); +typedef void (*curl_free_callback)(void *ptr); +typedef void *(*curl_realloc_callback)(void *ptr, size_t size); +typedef char *(*curl_strdup_callback)(const char *str); +typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); +typedef enum { +CURLINFO_TEXT = 0, +CURLINFO_HEADER_IN, +CURLINFO_HEADER_OUT, +CURLINFO_DATA_IN, +CURLINFO_DATA_OUT, +CURLINFO_SSL_DATA_IN, +CURLINFO_SSL_DATA_OUT, +CURLINFO_END +} curl_infotype; +typedef int (*curl_debug_callback) +(CURL *handle, +curl_infotype type, +char *data, +size_t size, +void *userptr); +typedef enum { +CURLE_OK = 0, +CURLE_UNSUPPORTED_PROTOCOL, +CURLE_FAILED_INIT, +CURLE_URL_MALFORMAT, +CURLE_NOT_BUILT_IN, +CURLE_COULDNT_RESOLVE_PROXY, +CURLE_COULDNT_RESOLVE_HOST, +CURLE_COULDNT_CONNECT, +CURLE_FTP_WEIRD_SERVER_REPLY, +CURLE_REMOTE_ACCESS_DENIED, +CURLE_FTP_ACCEPT_FAILED, +CURLE_FTP_WEIRD_PASS_REPLY, +CURLE_FTP_ACCEPT_TIMEOUT, +CURLE_FTP_WEIRD_PASV_REPLY, +CURLE_FTP_WEIRD_227_FORMAT, +CURLE_FTP_CANT_GET_HOST, +CURLE_HTTP2, +CURLE_FTP_COULDNT_SET_TYPE, +CURLE_PARTIAL_FILE, +CURLE_FTP_COULDNT_RETR_FILE, +CURLE_OBSOLETE20, +CURLE_QUOTE_ERROR, +CURLE_HTTP_RETURNED_ERROR, +CURLE_WRITE_ERROR, +CURLE_OBSOLETE24, +CURLE_UPLOAD_FAILED, +CURLE_READ_ERROR, +CURLE_OUT_OF_MEMORY, +CURLE_OPERATION_TIMEDOUT, +CURLE_OBSOLETE29, +CURLE_FTP_PORT_FAILED, +CURLE_FTP_COULDNT_USE_REST, +CURLE_OBSOLETE32, +CURLE_RANGE_ERROR, +CURLE_HTTP_POST_ERROR, +CURLE_SSL_CONNECT_ERROR, +CURLE_BAD_DOWNLOAD_RESUME, +CURLE_FILE_COULDNT_READ_FILE, +CURLE_LDAP_CANNOT_BIND, +CURLE_LDAP_SEARCH_FAILED, +CURLE_OBSOLETE40, +CURLE_FUNCTION_NOT_FOUND, +CURLE_ABORTED_BY_CALLBACK, +CURLE_BAD_FUNCTION_ARGUMENT, +CURLE_OBSOLETE44, +CURLE_INTERFACE_FAILED, +CURLE_OBSOLETE46, +CURLE_TOO_MANY_REDIRECTS , +CURLE_UNKNOWN_OPTION, +CURLE_TELNET_OPTION_SYNTAX , +CURLE_OBSOLETE50, +CURLE_PEER_FAILED_VERIFICATION, +CURLE_GOT_NOTHING, +CURLE_SSL_ENGINE_NOTFOUND, +CURLE_SSL_ENGINE_SETFAILED, +CURLE_SEND_ERROR, +CURLE_RECV_ERROR, +CURLE_OBSOLETE57, +CURLE_SSL_CERTPROBLEM, +CURLE_SSL_CIPHER, +CURLE_SSL_CACERT, +CURLE_BAD_CONTENT_ENCODING, +CURLE_LDAP_INVALID_URL, +CURLE_FILESIZE_EXCEEDED, +CURLE_USE_SSL_FAILED, +CURLE_SEND_FAIL_REWIND, +CURLE_SSL_ENGINE_INITFAILED, +CURLE_LOGIN_DENIED, +CURLE_TFTP_NOTFOUND, +CURLE_TFTP_PERM, +CURLE_REMOTE_DISK_FULL, +CURLE_TFTP_ILLEGAL, +CURLE_TFTP_UNKNOWNID, +CURLE_REMOTE_FILE_EXISTS, +CURLE_TFTP_NOSUCHUSER, +CURLE_CONV_FAILED, +CURLE_CONV_REQD, +CURLE_SSL_CACERT_BADFILE, +CURLE_REMOTE_FILE_NOT_FOUND, +CURLE_SSH, +CURLE_SSL_SHUTDOWN_FAILED, +CURLE_AGAIN, +CURLE_SSL_CRL_BADFILE, +CURLE_SSL_ISSUER_ERROR, +CURLE_FTP_PRET_FAILED, +CURLE_RTSP_CSEQ_ERROR, +CURLE_RTSP_SESSION_ERROR, +CURLE_FTP_BAD_FILE_LIST, +CURLE_CHUNK_FAILED, +CURLE_NO_CONNECTION_AVAILABLE, +CURL_LAST +} CURLcode; +typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); +typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, +void *ssl_ctx, +void *userptr); +typedef enum { +CURLPROXY_HTTP = 0, +CURLPROXY_HTTP_1_0 = 1, +CURLPROXY_SOCKS4 = 4, +CURLPROXY_SOCKS5 = 5, +CURLPROXY_SOCKS4A = 6, +CURLPROXY_SOCKS5_HOSTNAME = 7 +} curl_proxytype; +enum curl_khtype { +CURLKHTYPE_UNKNOWN, +CURLKHTYPE_RSA1, +CURLKHTYPE_RSA, +CURLKHTYPE_DSS +}; +struct curl_khkey { +const char *key; +size_t len; +enum curl_khtype keytype; +}; +enum curl_khstat { +CURLKHSTAT_FINE_ADD_TO_FILE, +CURLKHSTAT_FINE, +CURLKHSTAT_REJECT, +CURLKHSTAT_DEFER, +CURLKHSTAT_LAST +}; +enum curl_khmatch { +CURLKHMATCH_OK, +CURLKHMATCH_MISMATCH, +CURLKHMATCH_MISSING, +CURLKHMATCH_LAST +}; +typedef int +(*curl_sshkeycallback) (CURL *easy, +const struct curl_khkey *knownkey, +const struct curl_khkey *foundkey, +enum curl_khmatch, +void *clientp); +typedef enum { +CURLUSESSL_NONE, +CURLUSESSL_TRY, +CURLUSESSL_CONTROL, +CURLUSESSL_ALL, +CURLUSESSL_LAST +} curl_usessl; +typedef enum { +CURLFTPSSL_CCC_NONE, +CURLFTPSSL_CCC_PASSIVE, +CURLFTPSSL_CCC_ACTIVE, +CURLFTPSSL_CCC_LAST +} curl_ftpccc; +typedef enum { +CURLFTPAUTH_DEFAULT, +CURLFTPAUTH_SSL, +CURLFTPAUTH_TLS, +CURLFTPAUTH_LAST +} curl_ftpauth; +typedef enum { +CURLFTP_CREATE_DIR_NONE, +CURLFTP_CREATE_DIR, +CURLFTP_CREATE_DIR_RETRY, +CURLFTP_CREATE_DIR_LAST +} curl_ftpcreatedir; +typedef enum { +CURLFTPMETHOD_DEFAULT, +CURLFTPMETHOD_MULTICWD, +CURLFTPMETHOD_NOCWD, +CURLFTPMETHOD_SINGLECWD, +CURLFTPMETHOD_LAST +} curl_ftpmethod; +typedef enum { +CURLOPT_WRITEDATA = 10000 + 1, +CURLOPT_URL = 10000 + 2, +CURLOPT_PORT = 0 + 3, +CURLOPT_PROXY = 10000 + 4, +CURLOPT_USERPWD = 10000 + 5, +CURLOPT_PROXYUSERPWD = 10000 + 6, +CURLOPT_RANGE = 10000 + 7, +CURLOPT_READDATA = 10000 + 9, +CURLOPT_ERRORBUFFER = 10000 + 10, +CURLOPT_WRITEFUNCTION = 20000 + 11, +CURLOPT_READFUNCTION = 20000 + 12, +CURLOPT_TIMEOUT = 0 + 13, +CURLOPT_INFILESIZE = 0 + 14, +CURLOPT_POSTFIELDS = 10000 + 15, +CURLOPT_REFERER = 10000 + 16, +CURLOPT_FTPPORT = 10000 + 17, +CURLOPT_USERAGENT = 10000 + 18, +CURLOPT_LOW_SPEED_LIMIT = 0 + 19, +CURLOPT_LOW_SPEED_TIME = 0 + 20, +CURLOPT_RESUME_FROM = 0 + 21, +CURLOPT_COOKIE = 10000 + 22, +CURLOPT_HTTPHEADER = 10000 + 23, +CURLOPT_HTTPPOST = 10000 + 24, +CURLOPT_SSLCERT = 10000 + 25, +CURLOPT_KEYPASSWD = 10000 + 26, +CURLOPT_CRLF = 0 + 27, +CURLOPT_QUOTE = 10000 + 28, +CURLOPT_HEADERDATA = 10000 + 29, +CURLOPT_COOKIEFILE = 10000 + 31, +CURLOPT_SSLVERSION = 0 + 32, +CURLOPT_TIMECONDITION = 0 + 33, +CURLOPT_TIMEVALUE = 0 + 34, +CURLOPT_CUSTOMREQUEST = 10000 + 36, +CURLOPT_STDERR = 10000 + 37, +CURLOPT_POSTQUOTE = 10000 + 39, +CURLOPT_OBSOLETE40 = 10000 + 40, +CURLOPT_VERBOSE = 0 + 41, +CURLOPT_HEADER = 0 + 42, +CURLOPT_NOPROGRESS = 0 + 43, +CURLOPT_NOBODY = 0 + 44, +CURLOPT_FAILONERROR = 0 + 45, +CURLOPT_UPLOAD = 0 + 46, +CURLOPT_POST = 0 + 47, +CURLOPT_DIRLISTONLY = 0 + 48, +CURLOPT_APPEND = 0 + 50, +CURLOPT_NETRC = 0 + 51, +CURLOPT_FOLLOWLOCATION = 0 + 52, +CURLOPT_TRANSFERTEXT = 0 + 53, +CURLOPT_PUT = 0 + 54, +CURLOPT_PROGRESSFUNCTION = 20000 + 56, +CURLOPT_PROGRESSDATA = 10000 + 57, +CURLOPT_AUTOREFERER = 0 + 58, +CURLOPT_PROXYPORT = 0 + 59, +CURLOPT_POSTFIELDSIZE = 0 + 60, +CURLOPT_HTTPPROXYTUNNEL = 0 + 61, +CURLOPT_INTERFACE = 10000 + 62, +CURLOPT_KRBLEVEL = 10000 + 63, +CURLOPT_SSL_VERIFYPEER = 0 + 64, +CURLOPT_CAINFO = 10000 + 65, +CURLOPT_MAXREDIRS = 0 + 68, +CURLOPT_FILETIME = 0 + 69, +CURLOPT_TELNETOPTIONS = 10000 + 70, +CURLOPT_MAXCONNECTS = 0 + 71, +CURLOPT_OBSOLETE72 = 0 + 72, +CURLOPT_FRESH_CONNECT = 0 + 74, +CURLOPT_FORBID_REUSE = 0 + 75, +CURLOPT_RANDOM_FILE = 10000 + 76, +CURLOPT_EGDSOCKET = 10000 + 77, +CURLOPT_CONNECTTIMEOUT = 0 + 78, +CURLOPT_HEADERFUNCTION = 20000 + 79, +CURLOPT_HTTPGET = 0 + 80, +CURLOPT_SSL_VERIFYHOST = 0 + 81, +CURLOPT_COOKIEJAR = 10000 + 82, +CURLOPT_SSL_CIPHER_LIST = 10000 + 83, +CURLOPT_HTTP_VERSION = 0 + 84, +CURLOPT_FTP_USE_EPSV = 0 + 85, +CURLOPT_SSLCERTTYPE = 10000 + 86, +CURLOPT_SSLKEY = 10000 + 87, +CURLOPT_SSLKEYTYPE = 10000 + 88, +CURLOPT_SSLENGINE = 10000 + 89, +CURLOPT_SSLENGINE_DEFAULT = 0 + 90, +CURLOPT_DNS_USE_GLOBAL_CACHE = 0 + 91, +CURLOPT_DNS_CACHE_TIMEOUT = 0 + 92, +CURLOPT_PREQUOTE = 10000 + 93, +CURLOPT_DEBUGFUNCTION = 20000 + 94, +CURLOPT_DEBUGDATA = 10000 + 95, +CURLOPT_COOKIESESSION = 0 + 96, +CURLOPT_CAPATH = 10000 + 97, +CURLOPT_BUFFERSIZE = 0 + 98, +CURLOPT_NOSIGNAL = 0 + 99, +CURLOPT_SHARE = 10000 + 100, +CURLOPT_PROXYTYPE = 0 + 101, +CURLOPT_ACCEPT_ENCODING = 10000 + 102, +CURLOPT_PRIVATE = 10000 + 103, +CURLOPT_HTTP200ALIASES = 10000 + 104, +CURLOPT_UNRESTRICTED_AUTH = 0 + 105, +CURLOPT_FTP_USE_EPRT = 0 + 106, +CURLOPT_HTTPAUTH = 0 + 107, +CURLOPT_SSL_CTX_FUNCTION = 20000 + 108, +CURLOPT_SSL_CTX_DATA = 10000 + 109, +CURLOPT_FTP_CREATE_MISSING_DIRS = 0 + 110, +CURLOPT_PROXYAUTH = 0 + 111, +CURLOPT_FTP_RESPONSE_TIMEOUT = 0 + 112, +CURLOPT_IPRESOLVE = 0 + 113, +CURLOPT_MAXFILESIZE = 0 + 114, +CURLOPT_INFILESIZE_LARGE = 30000 + 115, +CURLOPT_RESUME_FROM_LARGE = 30000 + 116, +CURLOPT_MAXFILESIZE_LARGE = 30000 + 117, +CURLOPT_NETRC_FILE = 10000 + 118, +CURLOPT_USE_SSL = 0 + 119, +CURLOPT_POSTFIELDSIZE_LARGE = 30000 + 120, +CURLOPT_TCP_NODELAY = 0 + 121, +CURLOPT_FTPSSLAUTH = 0 + 129, +CURLOPT_IOCTLFUNCTION = 20000 + 130, +CURLOPT_IOCTLDATA = 10000 + 131, +CURLOPT_FTP_ACCOUNT = 10000 + 134, +CURLOPT_COOKIELIST = 10000 + 135, +CURLOPT_IGNORE_CONTENT_LENGTH = 0 + 136, +CURLOPT_FTP_SKIP_PASV_IP = 0 + 137, +CURLOPT_FTP_FILEMETHOD = 0 + 138, +CURLOPT_LOCALPORT = 0 + 139, +CURLOPT_LOCALPORTRANGE = 0 + 140, +CURLOPT_CONNECT_ONLY = 0 + 141, +CURLOPT_CONV_FROM_NETWORK_FUNCTION = 20000 + 142, +CURLOPT_CONV_TO_NETWORK_FUNCTION = 20000 + 143, +CURLOPT_CONV_FROM_UTF8_FUNCTION = 20000 + 144, +CURLOPT_MAX_SEND_SPEED_LARGE = 30000 + 145, +CURLOPT_MAX_RECV_SPEED_LARGE = 30000 + 146, +CURLOPT_FTP_ALTERNATIVE_TO_USER = 10000 + 147, +CURLOPT_SOCKOPTFUNCTION = 20000 + 148, +CURLOPT_SOCKOPTDATA = 10000 + 149, +CURLOPT_SSL_SESSIONID_CACHE = 0 + 150, +CURLOPT_SSH_AUTH_TYPES = 0 + 151, +CURLOPT_SSH_PUBLIC_KEYFILE = 10000 + 152, +CURLOPT_SSH_PRIVATE_KEYFILE = 10000 + 153, +CURLOPT_FTP_SSL_CCC = 0 + 154, +CURLOPT_TIMEOUT_MS = 0 + 155, +CURLOPT_CONNECTTIMEOUT_MS = 0 + 156, +CURLOPT_HTTP_TRANSFER_DECODING = 0 + 157, +CURLOPT_HTTP_CONTENT_DECODING = 0 + 158, +CURLOPT_NEW_FILE_PERMS = 0 + 159, +CURLOPT_NEW_DIRECTORY_PERMS = 0 + 160, +CURLOPT_POSTREDIR = 0 + 161, +CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 = 10000 + 162, +CURLOPT_OPENSOCKETFUNCTION = 20000 + 163, +CURLOPT_OPENSOCKETDATA = 10000 + 164, +CURLOPT_COPYPOSTFIELDS = 10000 + 165, +CURLOPT_PROXY_TRANSFER_MODE = 0 + 166, +CURLOPT_SEEKFUNCTION = 20000 + 167, +CURLOPT_SEEKDATA = 10000 + 168, +CURLOPT_CRLFILE = 10000 + 169, +CURLOPT_ISSUERCERT = 10000 + 170, +CURLOPT_ADDRESS_SCOPE = 0 + 171, +CURLOPT_CERTINFO = 0 + 172, +CURLOPT_USERNAME = 10000 + 173, +CURLOPT_PASSWORD = 10000 + 174, +CURLOPT_PROXYUSERNAME = 10000 + 175, +CURLOPT_PROXYPASSWORD = 10000 + 176, +CURLOPT_NOPROXY = 10000 + 177, +CURLOPT_TFTP_BLKSIZE = 0 + 178, +CURLOPT_SOCKS5_GSSAPI_SERVICE = 10000 + 179, +CURLOPT_SOCKS5_GSSAPI_NEC = 0 + 180, +CURLOPT_PROTOCOLS = 0 + 181, +CURLOPT_REDIR_PROTOCOLS = 0 + 182, +CURLOPT_SSH_KNOWNHOSTS = 10000 + 183, +CURLOPT_SSH_KEYFUNCTION = 20000 + 184, +CURLOPT_SSH_KEYDATA = 10000 + 185, +CURLOPT_MAIL_FROM = 10000 + 186, +CURLOPT_MAIL_RCPT = 10000 + 187, +CURLOPT_FTP_USE_PRET = 0 + 188, +CURLOPT_RTSP_REQUEST = 0 + 189, +CURLOPT_RTSP_SESSION_ID = 10000 + 190, +CURLOPT_RTSP_STREAM_URI = 10000 + 191, +CURLOPT_RTSP_TRANSPORT = 10000 + 192, +CURLOPT_RTSP_CLIENT_CSEQ = 0 + 193, +CURLOPT_RTSP_SERVER_CSEQ = 0 + 194, +CURLOPT_INTERLEAVEDATA = 10000 + 195, +CURLOPT_INTERLEAVEFUNCTION = 20000 + 196, +CURLOPT_WILDCARDMATCH = 0 + 197, +CURLOPT_CHUNK_BGN_FUNCTION = 20000 + 198, +CURLOPT_CHUNK_END_FUNCTION = 20000 + 199, +CURLOPT_FNMATCH_FUNCTION = 20000 + 200, +CURLOPT_CHUNK_DATA = 10000 + 201, +CURLOPT_FNMATCH_DATA = 10000 + 202, +CURLOPT_RESOLVE = 10000 + 203, +CURLOPT_TLSAUTH_USERNAME = 10000 + 204, +CURLOPT_TLSAUTH_PASSWORD = 10000 + 205, +CURLOPT_TLSAUTH_TYPE = 10000 + 206, +CURLOPT_TRANSFER_ENCODING = 0 + 207, +CURLOPT_CLOSESOCKETFUNCTION = 20000 + 208, +CURLOPT_CLOSESOCKETDATA = 10000 + 209, +CURLOPT_GSSAPI_DELEGATION = 0 + 210, +CURLOPT_DNS_SERVERS = 10000 + 211, +CURLOPT_ACCEPTTIMEOUT_MS = 0 + 212, +CURLOPT_TCP_KEEPALIVE = 0 + 213, +CURLOPT_TCP_KEEPIDLE = 0 + 214, +CURLOPT_TCP_KEEPINTVL = 0 + 215, +CURLOPT_SSL_OPTIONS = 0 + 216, +CURLOPT_MAIL_AUTH = 10000 + 217, +CURLOPT_SASL_IR = 0 + 218, +CURLOPT_XFERINFOFUNCTION = 20000 + 219, +CURLOPT_XOAUTH2_BEARER = 10000 + 220, +CURLOPT_DNS_INTERFACE = 10000 + 221, +CURLOPT_DNS_LOCAL_IP4 = 10000 + 222, +CURLOPT_DNS_LOCAL_IP6 = 10000 + 223, +CURLOPT_LOGIN_OPTIONS = 10000 + 224, +CURLOPT_SSL_ENABLE_NPN = 0 + 225, +CURLOPT_SSL_ENABLE_ALPN = 0 + 226, +CURLOPT_EXPECT_100_TIMEOUT_MS = 0 + 227, +CURLOPT_PROXYHEADER = 10000 + 228, +CURLOPT_HEADEROPT = 0 + 229, +CURLOPT_LASTENTRY +} CURLoption; +enum { +CURL_HTTP_VERSION_NONE, +CURL_HTTP_VERSION_1_0, +CURL_HTTP_VERSION_1_1, +CURL_HTTP_VERSION_2_0, +CURL_HTTP_VERSION_LAST +}; +enum { +CURL_RTSPREQ_NONE, +CURL_RTSPREQ_OPTIONS, +CURL_RTSPREQ_DESCRIBE, +CURL_RTSPREQ_ANNOUNCE, +CURL_RTSPREQ_SETUP, +CURL_RTSPREQ_PLAY, +CURL_RTSPREQ_PAUSE, +CURL_RTSPREQ_TEARDOWN, +CURL_RTSPREQ_GET_PARAMETER, +CURL_RTSPREQ_SET_PARAMETER, +CURL_RTSPREQ_RECORD, +CURL_RTSPREQ_RECEIVE, +CURL_RTSPREQ_LAST +}; +enum CURL_NETRC_OPTION { +CURL_NETRC_IGNORED, +CURL_NETRC_OPTIONAL, +CURL_NETRC_REQUIRED, +CURL_NETRC_LAST +}; +enum { +CURL_SSLVERSION_DEFAULT, +CURL_SSLVERSION_TLSv1, +CURL_SSLVERSION_SSLv2, +CURL_SSLVERSION_SSLv3, +CURL_SSLVERSION_TLSv1_0, +CURL_SSLVERSION_TLSv1_1, +CURL_SSLVERSION_TLSv1_2, +CURL_SSLVERSION_LAST +}; +enum CURL_TLSAUTH { +CURL_TLSAUTH_NONE, +CURL_TLSAUTH_SRP, +CURL_TLSAUTH_LAST +}; +typedef enum { +CURL_TIMECOND_NONE, +CURL_TIMECOND_IFMODSINCE, +CURL_TIMECOND_IFUNMODSINCE, +CURL_TIMECOND_LASTMOD, +CURL_TIMECOND_LAST +} curl_TimeCond; + int (curl_strequal)(const char *s1, const char *s2); + int (curl_strnequal)(const char *s1, const char *s2, size_t n); +typedef enum { +CURLFORM_NOTHING, +CURLFORM_COPYNAME, +CURLFORM_PTRNAME, +CURLFORM_NAMELENGTH, +CURLFORM_COPYCONTENTS, +CURLFORM_PTRCONTENTS, +CURLFORM_CONTENTSLENGTH, +CURLFORM_FILECONTENT, +CURLFORM_ARRAY, +CURLFORM_OBSOLETE, +CURLFORM_FILE, +CURLFORM_BUFFER, +CURLFORM_BUFFERPTR, +CURLFORM_BUFFERLENGTH, +CURLFORM_CONTENTTYPE, +CURLFORM_CONTENTHEADER, +CURLFORM_FILENAME, +CURLFORM_END, +CURLFORM_OBSOLETE2, +CURLFORM_STREAM, +CURLFORM_LASTENTRY +} CURLformoption; +struct curl_forms { +CURLformoption option; +const char *value; +}; +typedef enum { +CURL_FORMADD_OK, +CURL_FORMADD_MEMORY, +CURL_FORMADD_OPTION_TWICE, +CURL_FORMADD_NULL, +CURL_FORMADD_UNKNOWN_OPTION, +CURL_FORMADD_INCOMPLETE, +CURL_FORMADD_ILLEGAL_ARRAY, +CURL_FORMADD_DISABLED, +CURL_FORMADD_LAST +} CURLFORMcode; + CURLFORMcode curl_formadd(struct curl_httppost **httppost, +struct curl_httppost **last_post, +...); +typedef size_t (*curl_formget_callback)(void *arg, const char *buf, +size_t len); + int curl_formget(struct curl_httppost *form, void *arg, +curl_formget_callback append); + void curl_formfree(struct curl_httppost *form); + char *curl_getenv(const char *variable); + char *curl_version(void); + char *curl_easy_escape(CURL *handle, +const char *string, +int length); + char *curl_escape(const char *string, +int length); + char *curl_easy_unescape(CURL *handle, +const char *string, +int length, +int *outlength); + char *curl_unescape(const char *string, +int length); + void curl_free(void *p); + CURLcode curl_global_init(long flags); + CURLcode curl_global_init_mem(long flags, +curl_malloc_callback m, +curl_free_callback f, +curl_realloc_callback r, +curl_strdup_callback s, +curl_calloc_callback c); + void curl_global_cleanup(void); +struct curl_slist { +char *data; +struct curl_slist *next; +}; + struct curl_slist *curl_slist_append(struct curl_slist *, +const char *); + void curl_slist_free_all(struct curl_slist *); + time_t curl_getdate(const char *p, const time_t *unused); +struct curl_certinfo { +int num_of_certs; +struct curl_slist **certinfo; +}; +typedef enum { +CURLSSLBACKEND_NONE = 0, +CURLSSLBACKEND_OPENSSL = 1, +CURLSSLBACKEND_GNUTLS = 2, +CURLSSLBACKEND_NSS = 3, +CURLSSLBACKEND_QSOSSL = 4, +CURLSSLBACKEND_GSKIT = 5, +CURLSSLBACKEND_POLARSSL = 6, +CURLSSLBACKEND_CYASSL = 7, +CURLSSLBACKEND_SCHANNEL = 8, +CURLSSLBACKEND_DARWINSSL = 9, +CURLSSLBACKEND_AXTLS = 10 +} curl_sslbackend; +struct curl_tlssessioninfo { +curl_sslbackend backend; +void *internals; +}; +typedef enum { +CURLINFO_NONE, +CURLINFO_EFFECTIVE_URL =1048576 + 1, +CURLINFO_RESPONSE_CODE =2097152 + 2, +CURLINFO_TOTAL_TIME =3145728 + 3, +CURLINFO_NAMELOOKUP_TIME =3145728 + 4, +CURLINFO_CONNECT_TIME =3145728 + 5, +CURLINFO_PRETRANSFER_TIME =3145728 + 6, +CURLINFO_SIZE_UPLOAD =3145728 + 7, +CURLINFO_SIZE_DOWNLOAD =3145728 + 8, +CURLINFO_SPEED_DOWNLOAD =3145728 + 9, +CURLINFO_SPEED_UPLOAD =3145728 + 10, +CURLINFO_HEADER_SIZE =2097152 + 11, +CURLINFO_REQUEST_SIZE =2097152 + 12, +CURLINFO_SSL_VERIFYRESULT =2097152 + 13, +CURLINFO_FILETIME =2097152 + 14, +CURLINFO_CONTENT_LENGTH_DOWNLOAD =3145728 + 15, +CURLINFO_CONTENT_LENGTH_UPLOAD =3145728 + 16, +CURLINFO_STARTTRANSFER_TIME =3145728 + 17, +CURLINFO_CONTENT_TYPE =1048576 + 18, +CURLINFO_REDIRECT_TIME =3145728 + 19, +CURLINFO_REDIRECT_COUNT =2097152 + 20, +CURLINFO_PRIVATE =1048576 + 21, +CURLINFO_HTTP_CONNECTCODE =2097152 + 22, +CURLINFO_HTTPAUTH_AVAIL =2097152 + 23, +CURLINFO_PROXYAUTH_AVAIL =2097152 + 24, +CURLINFO_OS_ERRNO =2097152 + 25, +CURLINFO_NUM_CONNECTS =2097152 + 26, +CURLINFO_SSL_ENGINES =4194304 + 27, +CURLINFO_COOKIELIST =4194304 + 28, +CURLINFO_LASTSOCKET =2097152 + 29, +CURLINFO_FTP_ENTRY_PATH =1048576 + 30, +CURLINFO_REDIRECT_URL =1048576 + 31, +CURLINFO_PRIMARY_IP =1048576 + 32, +CURLINFO_APPCONNECT_TIME =3145728 + 33, +CURLINFO_CERTINFO =4194304 + 34, +CURLINFO_CONDITION_UNMET =2097152 + 35, +CURLINFO_RTSP_SESSION_ID =1048576 + 36, +CURLINFO_RTSP_CLIENT_CSEQ =2097152 + 37, +CURLINFO_RTSP_SERVER_CSEQ =2097152 + 38, +CURLINFO_RTSP_CSEQ_RECV =2097152 + 39, +CURLINFO_PRIMARY_PORT =2097152 + 40, +CURLINFO_LOCAL_IP =1048576 + 41, +CURLINFO_LOCAL_PORT =2097152 + 42, +CURLINFO_TLS_SESSION =4194304 + 43, +CURLINFO_LASTONE = 43 +} CURLINFO; +typedef enum { +CURLCLOSEPOLICY_NONE, +CURLCLOSEPOLICY_OLDEST, +CURLCLOSEPOLICY_LEAST_RECENTLY_USED, +CURLCLOSEPOLICY_LEAST_TRAFFIC, +CURLCLOSEPOLICY_SLOWEST, +CURLCLOSEPOLICY_CALLBACK, +CURLCLOSEPOLICY_LAST +} curl_closepolicy; +typedef enum { +CURL_LOCK_DATA_NONE = 0, +CURL_LOCK_DATA_SHARE, +CURL_LOCK_DATA_COOKIE, +CURL_LOCK_DATA_DNS, +CURL_LOCK_DATA_SSL_SESSION, +CURL_LOCK_DATA_CONNECT, +CURL_LOCK_DATA_LAST +} curl_lock_data; +typedef enum { +CURL_LOCK_ACCESS_NONE = 0, +CURL_LOCK_ACCESS_SHARED = 1, +CURL_LOCK_ACCESS_SINGLE = 2, +CURL_LOCK_ACCESS_LAST +} curl_lock_access; +typedef void (*curl_lock_function)(CURL *handle, +curl_lock_data data, +curl_lock_access locktype, +void *userptr); +typedef void (*curl_unlock_function)(CURL *handle, +curl_lock_data data, +void *userptr); +typedef void CURLSH; +typedef enum { +CURLSHE_OK, +CURLSHE_BAD_OPTION, +CURLSHE_IN_USE, +CURLSHE_INVALID, +CURLSHE_NOMEM, +CURLSHE_NOT_BUILT_IN, +CURLSHE_LAST +} CURLSHcode; +typedef enum { +CURLSHOPT_NONE, +CURLSHOPT_SHARE, +CURLSHOPT_UNSHARE, +CURLSHOPT_LOCKFUNC, +CURLSHOPT_UNLOCKFUNC, +CURLSHOPT_USERDATA, +CURLSHOPT_LAST +} CURLSHoption; + CURLSH *curl_share_init(void); + CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option, ...); + CURLSHcode curl_share_cleanup(CURLSH *); +typedef enum { +CURLVERSION_FIRST, +CURLVERSION_SECOND, +CURLVERSION_THIRD, +CURLVERSION_FOURTH, +CURLVERSION_LAST +} CURLversion; +typedef struct { +CURLversion age; +const char *version; +unsigned int version_num; +const char *host; +int features; +const char *ssl_version; +long ssl_version_num; +const char *libz_version; +const char * const *protocols; +const char *ares; +int ares_num; +const char *libidn; +int iconv_ver_num; +const char *libssh_version; +} curl_version_info_data; + curl_version_info_data *curl_version_info(CURLversion); + const char *curl_easy_strerror(CURLcode); + const char *curl_share_strerror(CURLSHcode); + CURLcode curl_easy_pause(CURL *handle, int bitmask); + CURL *curl_easy_init(void); + CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); + CURLcode curl_easy_perform(CURL *curl); + void curl_easy_cleanup(CURL *curl); + CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); + CURL* curl_easy_duphandle(CURL *curl); + void curl_easy_reset(CURL *curl); + CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, +size_t *n); + CURLcode curl_easy_send(CURL *curl, const void *buffer, +size_t buflen, size_t *n); +typedef void CURLM; +typedef enum { +CURLM_CALL_MULTI_PERFORM = -1, +CURLM_OK, +CURLM_BAD_HANDLE, +CURLM_BAD_EASY_HANDLE, +CURLM_OUT_OF_MEMORY, +CURLM_INTERNAL_ERROR, +CURLM_BAD_SOCKET, +CURLM_UNKNOWN_OPTION, +CURLM_ADDED_ALREADY, +CURLM_LAST +} CURLMcode; +typedef enum { +CURLMSG_NONE, +CURLMSG_DONE, +CURLMSG_LAST +} CURLMSG; +struct CURLMsg { +CURLMSG msg; +CURL *easy_handle; +union { +void *whatever; +CURLcode result; +} data; +}; +typedef struct CURLMsg CURLMsg; +struct curl_waitfd { +curl_socket_t fd; +short events; +short revents; +}; +typedef struct fd_set { + unsigned int fd_count; /* how many are SET? */ + curl_socket_t fd_array[64]; //FD_SETSIZE, 64 on my machine, TOFIX +} fd_set; + CURLM *curl_multi_init(void); + CURLMcode curl_multi_add_handle(CURLM *multi_handle, +CURL *curl_handle); + CURLMcode curl_multi_remove_handle(CURLM *multi_handle, +CURL *curl_handle); + CURLMcode curl_multi_fdset(CURLM *multi_handle, +fd_set *read_fd_set, +fd_set *write_fd_set, +fd_set *exc_fd_set, +int *max_fd); + CURLMcode curl_multi_wait(CURLM *multi_handle, +struct curl_waitfd extra_fds[], +unsigned int extra_nfds, +int timeout_ms, +int *ret); + CURLMcode curl_multi_perform(CURLM *multi_handle, +int *running_handles); + CURLMcode curl_multi_cleanup(CURLM *multi_handle); + CURLMsg *curl_multi_info_read(CURLM *multi_handle, +int *msgs_in_queue); + const char *curl_multi_strerror(CURLMcode); +typedef int (*curl_socket_callback)(CURL *easy, +curl_socket_t s, +int what, +void *userp, +void *socketp); +typedef int (*curl_multi_timer_callback)(CURLM *multi, +long timeout_ms, +void *userp); + CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, +int *running_handles); + CURLMcode curl_multi_socket_action(CURLM *multi_handle, +curl_socket_t s, +int ev_bitmask, +int *running_handles); + CURLMcode curl_multi_socket_all(CURLM *multi_handle, +int *running_handles); + CURLMcode curl_multi_timeout(CURLM *multi_handle, +long *milliseconds); +typedef enum { +CURLMOPT_SOCKETFUNCTION = 20000 + 1, +CURLMOPT_SOCKETDATA = 10000 + 2, +CURLMOPT_PIPELINING = 0 + 3, +CURLMOPT_TIMERFUNCTION = 20000 + 4, +CURLMOPT_TIMERDATA = 10000 + 5, +CURLMOPT_MAXCONNECTS = 0 + 6, +CURLMOPT_MAX_HOST_CONNECTIONS = 0 + 7, +CURLMOPT_MAX_PIPELINE_LENGTH = 0 + 8, +CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE = 30000 + 9, +CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE = 30000 + 10, +CURLMOPT_PIPELINING_SITE_BL = 10000 + 11, +CURLMOPT_PIPELINING_SERVER_BL = 10000 + 12, +CURLMOPT_MAX_TOTAL_CONNECTIONS = 0 + 13, +CURLMOPT_LASTENTRY +} CURLMoption; + CURLMcode curl_multi_setopt(CURLM *multi_handle, +CURLMoption option, ...); + CURLMcode curl_multi_assign(CURLM *multi_handle, +curl_socket_t sockfd, void *sockp); +]]) + +return curl \ No newline at end of file diff --git a/discord/message.lua b/discord/message.lua new file mode 100644 index 0000000..91ad7cf --- /dev/null +++ b/discord/message.lua @@ -0,0 +1,86 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +------------------------------------- +-- Discord Message Class +-- @module Message + +local path = (...):match('(.-)[^%.]+$') + +local request = require(path .. 'wrapper') + +local class = require(path .. 'class') +local json = require(path .. 'json') +local endpoints = require(path .. 'endpoints') +local util = require(path .. 'utils') + +local Message = class('MessageObject') + +--- Internally initialize's the Message class. +--- Please use Message:new(packet, token) instead. +-- @param packet Packet to be sent as the message. +-- @param token Authentication token from login. +function Message:initialize(packet, token) + + self.packet = packet or {} + self.token = token or '' + + self.headers = {} + self.headers['authorization'] = self.token + self.headers['Content-Type'] = 'application/json' + +end + +--- Edits a sent message. +-- @param msg The new message to replace the old one. +-- @return Response Packet received from Discord +function Message:edit(msg) + + local payload = { + content = tostring(msg) + } + + local response = request.send(endpoints.channels .. '/' .. self.packet.channel_id .. '/messages/' .. self.packet.id, 'PATCH', payload, self.headers) + + if util.responseIsSuccessful(response) then + self.packet = json.decode(response.body) + end + + return util.responseIsSuccessful(response) + +end + +--- Deletes a sent message. +-- @return Response Packet received from Discord +function Message:delete() + + local response = request.send(endpoints.channels .. '/' .. self.packet.channel_id .. '/messages/' .. self.packet.id, 'DELETE', nil, self.headers) + + if util.responseIsSuccessful(response) then + self = nil + end + + return util.responseIsSuccessful(response) + +end + +return Message \ No newline at end of file diff --git a/discord/utils.lua b/discord/utils.lua new file mode 100644 index 0000000..2c63e3a --- /dev/null +++ b/discord/utils.lua @@ -0,0 +1,36 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +------------------------------------- +-- Various Internal Utilitys +-- @module Utils + +local utils = {} + +--- Returns true if the response was successful. +-- @param response Response from Discord server. +-- @return True or False depending on response +function utils.responseIsSuccessful(response) + return tonumber(response.code) >= 200 and tonumber(response.code) < 300 +end + +return utils \ No newline at end of file diff --git a/discord/wrapper.lua b/discord/wrapper.lua new file mode 100644 index 0000000..f6ea194 --- /dev/null +++ b/discord/wrapper.lua @@ -0,0 +1,42 @@ +-- This code is licensed under the MIT Open Source License. + +-- Copyright (c) 2015 Ruairidh Carmichael - ruairidhcarmichael@live.co.uk + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local path = (...):match('(.-)[^%.]+$') + +local request = require(path .. 'luajit-request.init') +local json = require(path .. 'json') + +local requestWrapper = {} + +function requestWrapper.send(endpoint, method, data, headers) + + local payload = {} + + if headers then payload['headers'] = headers end + if method then payload['method'] = method end + if data then payload['data'] = json.encode(data) end + + return request.send(endpoint, payload) + +end + +return requestWrapper \ No newline at end of file diff --git a/dlls/libcurl.dll b/dlls/libcurl.dll new file mode 100644 index 0000000..ede2325 Binary files /dev/null and b/dlls/libcurl.dll differ diff --git a/dlls/libeay32.dll b/dlls/libeay32.dll new file mode 100644 index 0000000..b18d6f9 Binary files /dev/null and b/dlls/libeay32.dll differ diff --git a/dlls/libssh2.dll b/dlls/libssh2.dll new file mode 100644 index 0000000..21a4d3f Binary files /dev/null and b/dlls/libssh2.dll differ diff --git a/dlls/msvcr120.dll b/dlls/msvcr120.dll new file mode 100644 index 0000000..d711c92 Binary files /dev/null and b/dlls/msvcr120.dll differ diff --git a/dlls/ssleay32.dll b/dlls/ssleay32.dll new file mode 100644 index 0000000..792f7ac Binary files /dev/null and b/dlls/ssleay32.dll differ diff --git a/dlls/zlib1.dll b/dlls/zlib1.dll new file mode 100644 index 0000000..a01e7cb Binary files /dev/null and b/dlls/zlib1.dll differ diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..941b168 --- /dev/null +++ b/init.lua @@ -0,0 +1,110 @@ +-- prototype discord bridge for minetest +local discord_token = "" +local channel_id = "" + +local line = 1 + +for lines in io.lines(minetest.get_modpath("discord") .. "/auth.txt") do + if line == 1 then + discord_token = tostring(lines) + else + channel_id = tostring(lines) + break + end + line = line + 1 +end + +local ie = minetest.request_insecure_environment() +local old_require = require + +require = ie.require + +ie.package.path = package.path .. ";" .. minetest.get_modpath(minetest.get_current_modname()) .. "/?.lua" + +local discord_c = require "discord.init" +require = old_require + +discord = {} +mt_client = discord_c.Client:new() + +discord.accepted_token = false +if mt_client:loginWithToken(discord_token) then + mt_client:sendMessage('[Server] Server started.', channel_id) + discord.accepted_token = true +end + +function discord.send_message(message) + if discord.accepted_token then + mt_client:sendMessage(message, channel_id) + end +end + +minetest.register_on_chat_message(function(name, message) + if discord.accepted_token then + mt_client:sendMessage("<**" .. name .. "**> " .. minetest.strip_colors(message), channel_id) + end +end) + +minetest.register_on_joinplayer(function(player) + if discord.accepted_token then + mt_client:sendMessage("**" .. player:get_player_name() .. "**" .. " joined the game.", channel_id) + end +end) + +minetest.register_on_leaveplayer(function(player) + if discord.accepted_token then + mt_client:sendMessage("**" .. player:get_player_name() .. "**" .. " left the game.", channel_id) + end +end) + +minetest.register_on_shutdown(function() + if discord.accepted_token then + mt_client:sendMessage("[Server] Server shutting down.", channel_id) + end +end) + +-- Destroy the file on game boot: +local log_file = minetest.get_modpath("discord").."/discord.log" +local chat = {} +local increment = 0 +local discord_colour = "#7289DA" +os.remove(log_file) + +local function get_discord_messages() + local log = io.open(log_file) + if log == nil then + else + log:close() + local num_lines = 0 + for line in io.lines(log_file) do + -- Deserialise chat information coming from discord.go + local desel = minetest.deserialize(line) + if chat[num_lines] == nil then + if desel.message == "" then + -- Create an empty line if the deserialised message text is empty + -- we do this because uploading an image or file with no text causes + -- an anomalous message with an empty string + chat[num_lines] = "" + else + -- Colourise the [Discord] text and the of the Discord member + -- according to their role. + chat[num_lines] = minetest.colorize(discord_colour, desel.server) .. " " .. + minetest.colorize(desel.colour, desel.nick) .. " " .. desel.message + end + end + num_lines = num_lines + 1 + end + + for i=increment, #chat do + -- Fixes empty lines being sent to chat + if chat[i] == "" then + else + minetest.chat_send_all(chat[i]) + end + end + increment = #chat + 1 + end + minetest.after(1, get_discord_messages) +end + +minetest.after(2, get_discord_messages) \ No newline at end of file diff --git a/lib/libcurl.exp b/lib/libcurl.exp new file mode 100644 index 0000000..8a7de96 Binary files /dev/null and b/lib/libcurl.exp differ diff --git a/lib/libcurl.lib b/lib/libcurl.lib new file mode 100644 index 0000000..51d2b76 Binary files /dev/null and b/lib/libcurl.lib differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..dfc4daa --- /dev/null +++ b/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + + "github.com/bwmarrin/discordgo" +) + +// Token for both auth +var Token string = "" + +// Channel ID +var Channel string = "" + +// Server ID +var Server string = "" + +func main() { + + line := 1 + file, err := os.Open("auth.txt") + if err != nil { + _, err = os.OpenFile("auth.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + fmt.Println("Failed to load auth.txt, a file named auth.txt has been created.\nThe file should contain the following lines:\n\n\n\n") + return + } + fscanner := bufio.NewScanner(file) + for fscanner.Scan() { + switch line { + case 1: + Token = fscanner.Text() + case 2: + Channel = fscanner.Text() + case 3: + Server = fscanner.Text() + } + line = line + 1 + } + + // Create a new Discord session using the provided bot token. + dg, err := discordgo.New("Bot " + Token) + if err != nil { + fmt.Println("Error using provided auth token.", err) + return + } + + dg.StateEnabled = true + + // Add a handler that manages reading of incoming messages. + dg.AddHandler(messageCreate) + + // Open a websocket connection to Discord and begin listening. + err = dg.Open() + if err != nil { + fmt.Println("Failed to open websocket to Discord.,", err) + return + } + + // Wait here until CTRL-C or other term signal is received. + fmt.Println("Bot is now running. Press CTRL-C to exit. \nUse GNU screen or tmux to keep the heartbeat alive; \notherwise the heartbeat may randomly terminate.") + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + <-sc + + // Cleanly close down the Discord session. + dg.Close() +} + +func getRoleColour(userID string, s *discordgo.Session) int { + member, _ := s.GuildMember(Server, userID) + for _, roleID := range member.Roles { + role, err := s.State.Role(Server, roleID) + if err == nil { + return role.Color + } + } + return int(16777215) +} + +func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + + // Ignore all messages created by the bot itself + if m.Author.ID == s.State.User.ID { + return + } + + if m.ChannelID == Channel { + member, err := s.State.Member(Server, m.Author.ID) + userColour := "#ffffff" + + member, err = s.GuildMember(Server, m.Author.ID) + roleColour := getRoleColour(m.Author.ID, s) + if roleColour != 0 { + blue := roleColour & 0xFF + green := (roleColour >> 8) & 0xFF + red := (roleColour >> 16) & 0xFF + userColour = "#" + strconv.FormatInt(int64(red), 16) + strconv.FormatInt(int64(green), 16) + strconv.FormatInt(int64(blue), 16) + } + + serverNick := m.Author.Username + if member.Nick != "" { + serverNick = member.Nick + } + + messageAppend := "return {[\"server\"] = \"[Discord]\", [\"colour\"] = \"" + userColour + "\", [\"nick\"] = \"<" + serverNick + ">\", [\"message\"] = \"" + strings.ReplaceAll(m.Content, "\n", " ") + "\"}\n" + + if m.Message.Attachments != nil { + for k := range m.Message.Attachments { + messageAppend = messageAppend + "return {[\"server\"] = \"[Discord]\", [\"colour\"] = \"" + userColour + "\", [\"nick\"] = \"<" + serverNick + ">\", [\"message\"] = \"" + m.Message.Attachments[k].URL + "\"}\n" + } + } + + // Lua may randomly delete this file so always attempt to re-create it. + f, err := os.OpenFile("discord.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return + } + + f.WriteString(messageAppend) + f.Close() + } +}