initial commit
This commit is contained in:
parent
117fe951c5
commit
b75e0cdcf4
108
addon/LinkCloud/LinkCloud.lua
Normal file
108
addon/LinkCloud/LinkCloud.lua
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
_addon.name = "LinkCloud";
|
||||||
|
_addon.author = "James Alexander";
|
||||||
|
_addon.version = "0.1.2w";
|
||||||
|
_addon.commands = {
|
||||||
|
"lcloud",
|
||||||
|
"linkcloud",
|
||||||
|
"lc"
|
||||||
|
};
|
||||||
|
require("logger");
|
||||||
|
require("chat");
|
||||||
|
defaults = {};
|
||||||
|
defaults.HostAddress = "linkcloud.drunken.games";
|
||||||
|
defaults.HostPort = 6033;
|
||||||
|
defaults.TCPTimeout = 2;
|
||||||
|
defaults.AuthKey = "NONE";
|
||||||
|
defaults.AutoConnect = true;
|
||||||
|
defaults.AutoReconnect = true;
|
||||||
|
defaults.Platform = 1;
|
||||||
|
defaults.ProcessShouts = false;
|
||||||
|
json = require("lunajson");
|
||||||
|
files = require("files");
|
||||||
|
config = require("config");
|
||||||
|
settings = config.load(defaults);
|
||||||
|
linkCloud = require("LinkCloudRoutine")
|
||||||
|
|
||||||
|
function getPlayerData()
|
||||||
|
return windower.ffxi.get_player()
|
||||||
|
end
|
||||||
|
function getServerData()
|
||||||
|
return windower.ffxi.get_info()
|
||||||
|
end
|
||||||
|
function sendInputCommand(command)
|
||||||
|
return windower.chat.input(command)
|
||||||
|
end
|
||||||
|
function sendNoticeMessage(message)
|
||||||
|
notice(message)
|
||||||
|
end
|
||||||
|
function sendErrorMessage(message)
|
||||||
|
error(message)
|
||||||
|
end
|
||||||
|
function saveSettings()
|
||||||
|
settings:save()
|
||||||
|
end
|
||||||
|
function playerIsLsOwner()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
linkCloudParams = {
|
||||||
|
json = json,
|
||||||
|
settings = settings,
|
||||||
|
getPlayerData = getPlayerData, --must gave .id, .name
|
||||||
|
getServerData = getServerData, --must have .time .server
|
||||||
|
sendInputCommand = sendInputCommand, --method to write a console command
|
||||||
|
sendNoticeMessage = sendNoticeMessage, --method to send chat notice to player
|
||||||
|
sendErrorMessage = sendErrorMessage, --method to send error notice to player
|
||||||
|
playerIsLsOwner = playerIsLsOwner, --method to determine if player is ls owner
|
||||||
|
saveAddonConfig = saveSettings, --method to save addon config
|
||||||
|
consoleLog = print
|
||||||
|
}
|
||||||
|
|
||||||
|
linkCloud.hook(linkCloudParams)
|
||||||
|
|
||||||
|
|
||||||
|
windower.register_event("incoming text", function(_, text, mode, modemod, blocked)
|
||||||
|
if blocked or text == "" then
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
if mode == 217 then --lsmes2 update
|
||||||
|
linkCloud.onLSMesUpdate(2, text:strip_format())
|
||||||
|
elseif mode == 205 then --lsmes update
|
||||||
|
linkCloud.onLSMesUpdate(1, text:strip_format())
|
||||||
|
elseif (mode == 6 or mode == 14) then --ls1 chat
|
||||||
|
linkCloud.onLSMessageReceived(1, text:strip_format())
|
||||||
|
elseif (mode == 213 or mode == 214) then --ls2 chat
|
||||||
|
linkCloud.onLSMessageReceived(2, text:strip_format())
|
||||||
|
elseif mode == 11 then --bot spa...i mean shouts
|
||||||
|
linkCloud.onShoutReceived(text:strip_format())
|
||||||
|
end;
|
||||||
|
end);
|
||||||
|
|
||||||
|
|
||||||
|
windower.register_event("addon command", function(command, ...)
|
||||||
|
command = command and command:lower() or "help";
|
||||||
|
local args = T({
|
||||||
|
...
|
||||||
|
});
|
||||||
|
if command == "connect" or command == "c" then
|
||||||
|
linkCloud.ConnectToDiscordBot();
|
||||||
|
elseif command == "status" or command == "s" then
|
||||||
|
linkCloud.showstatus();
|
||||||
|
elseif command == "test" then
|
||||||
|
print(windower.ffxi.get_player().linkshell_slot)
|
||||||
|
elseif command == "addlinkshell" then
|
||||||
|
linkCloud.sendLinkshellAddRequest(args[1]);
|
||||||
|
elseif command == "autoconnect" then
|
||||||
|
linkCloud.cmdToggleAutoConnect()
|
||||||
|
elseif command == "autoreconnect" then
|
||||||
|
linkCloud.cmdToggleAutoReconnect()
|
||||||
|
elseif command == "shouts" then
|
||||||
|
linkCloud.cmdToggleShouts()
|
||||||
|
elseif command == "v" or command == "ver" or command == "version" then
|
||||||
|
linkCloud.cmdShowVersion(_addon.version)
|
||||||
|
elseif command == "help" or command == "h" then
|
||||||
|
linkCloud.cmdShowHelpMenu()
|
||||||
|
else
|
||||||
|
error(" ***** That is not a valid LinkCloud command. See //lcloud help. *****");
|
||||||
|
end;
|
||||||
|
end);
|
401
addon/LinkCloud/LinkCloudRoutine.lua
Normal file
401
addon/LinkCloud/LinkCloudRoutine.lua
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
local _version = '0.1.2'
|
||||||
|
local socket = require("socket");
|
||||||
|
|
||||||
|
local nextBuffer = 1;
|
||||||
|
local connected = false;
|
||||||
|
local shouldRetryConnection = false;
|
||||||
|
local authed = false;
|
||||||
|
--lsOneBuffer = {};
|
||||||
|
--lsTwoBuffer = {};
|
||||||
|
local otherbuffer = {};
|
||||||
|
--lsOne = {};
|
||||||
|
--lsTwo = {};
|
||||||
|
local echos = {};
|
||||||
|
|
||||||
|
local linkShells = {}
|
||||||
|
linkShells[1] = {}
|
||||||
|
linkShells[2] = {}
|
||||||
|
local lsBuffers = {}
|
||||||
|
lsBuffers[1] = {}
|
||||||
|
lsBuffers[2] = {}
|
||||||
|
local tcp = assert(socket.tcp());
|
||||||
|
local framework = nil
|
||||||
|
|
||||||
|
function hook (params)
|
||||||
|
framework = params
|
||||||
|
sendNotice('LinkCloud Version: ' .. _version .. ' loaded.')
|
||||||
|
if framework.settings.AutoConnect then
|
||||||
|
ConnectToDiscordBot();
|
||||||
|
else
|
||||||
|
sendNotice('AutoConnect is disabled. Use //lc connect to start streaming.')
|
||||||
|
end;
|
||||||
|
SendBuffer()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remove send packet from buffer (called after server confirms receipt)
|
||||||
|
function removePacketFromBuffers(packetId)
|
||||||
|
for k, v in pairs(otherbuffer) do
|
||||||
|
if (framework.json.decode(v)).packetId == packetId then
|
||||||
|
table.remove(otherbuffer, k);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
for k, v in pairs(lsBuffers[2]) do
|
||||||
|
if (framework.json.decode(v)).packetId == packetId then
|
||||||
|
table.remove(lsBuffers[2], k);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
for k, v in pairs(lsBuffers[1]) do
|
||||||
|
if (framework.json.decode(v)).packetId == packetId then
|
||||||
|
table.remove(lsBuffers[1], k);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
-- Ticks every 250ms sends the next buffered item to the server
|
||||||
|
function SendBuffer()
|
||||||
|
if authed then
|
||||||
|
if tcp then
|
||||||
|
nextMessage = nil;
|
||||||
|
if nextBuffer == 1 and linkShells[1].name ~= nil then
|
||||||
|
nextMessage = lsBuffers[1][1];
|
||||||
|
elseif nextBuffer == 2 then
|
||||||
|
nextMessage = otherbuffer[1];
|
||||||
|
elseif nextBuffer == 3 and linkShells[2].name ~= nil then
|
||||||
|
nextMessage = lsBuffers[2][1];
|
||||||
|
end;
|
||||||
|
if nextMessage and connected then
|
||||||
|
sendToBot(nextMessage);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
nextBuffer = nextBuffer + 1;
|
||||||
|
if nextBuffer > 3 then
|
||||||
|
nextBuffer = 1;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
coroutine.schedule(SendBuffer, 0.25);
|
||||||
|
end;
|
||||||
|
|
||||||
|
--Notify the user the connection has been lost.
|
||||||
|
--If auto reconnect is enabled, retry the connection in 30 seconds
|
||||||
|
function notifiyConnectionLost()
|
||||||
|
connected = false;
|
||||||
|
authed = false;
|
||||||
|
tcp:close();
|
||||||
|
if framework.settings.AutoReconnect then
|
||||||
|
sendError("You have lost connection to the LinkCloud server. The connection attempt will be retried shortly.");
|
||||||
|
coroutine.schedule(ConnectToDiscordBot, 30);
|
||||||
|
else
|
||||||
|
sendError("You have lost connection to the LinkCloud server. Use //lc connect to retry the connection.");
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--Parse /lsmes message
|
||||||
|
function getLSFromLsmes(str)
|
||||||
|
lsData = {};
|
||||||
|
lsData.shellNumber = 0;
|
||||||
|
slugs = split(str, " ");
|
||||||
|
if string.match(slugs[1], "(%d:)") then
|
||||||
|
sendError("timestamp addon must be disabled.");
|
||||||
|
notifiyConnectionLost();
|
||||||
|
elseif slugs[1] == "[1]<" then
|
||||||
|
lsData.shellNumber = 1;
|
||||||
|
lsData.name = string.sub(slugs[2], 0, slugs[2]:len() - 1);
|
||||||
|
elseif slugs[1] == "[2]<" then
|
||||||
|
lsData.shellNumber = 2;
|
||||||
|
lsData.name = string.sub(slugs[2], 0, slugs[2]:len() - 1);
|
||||||
|
end;
|
||||||
|
return lsData;
|
||||||
|
end;
|
||||||
|
--javascript like split function (thanks google)
|
||||||
|
function split(s, sep)
|
||||||
|
local fields = {};
|
||||||
|
local sep = sep or " ";
|
||||||
|
local pattern = string.format("([^%s]+)", sep);
|
||||||
|
string.gsub(s, pattern, function(c)
|
||||||
|
fields[(#fields) + 1] = c;
|
||||||
|
end);
|
||||||
|
return fields;
|
||||||
|
end;
|
||||||
|
--uuidv4 style random generator
|
||||||
|
function uuid()
|
||||||
|
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
||||||
|
return string.gsub(template, "[xy]", function(c)
|
||||||
|
local v = c == "x" and math.random(0, 15) or math.random(8, 11);
|
||||||
|
return string.format("%x", v);
|
||||||
|
end);
|
||||||
|
end;
|
||||||
|
--Put all the packet peices together into one packet, convert to json
|
||||||
|
function buildPacket(type, payload)
|
||||||
|
packet = {};
|
||||||
|
packet.metaData = metaData();
|
||||||
|
packet.type = type;
|
||||||
|
packet.payload = payload;
|
||||||
|
packet.packetId = uuid();
|
||||||
|
return framework.json.encode(packet) .. "\n";
|
||||||
|
end;
|
||||||
|
--sets the authkey
|
||||||
|
function buildConnectionPacket()
|
||||||
|
packet = {};
|
||||||
|
packet.authId = framework.settings.AuthKey;
|
||||||
|
return packet;
|
||||||
|
end;
|
||||||
|
--builds metadata to send along with the packet
|
||||||
|
|
||||||
|
|
||||||
|
function metaData()
|
||||||
|
info = framework.getServerData();
|
||||||
|
data = {};
|
||||||
|
playerData = framework.getPlayerData()
|
||||||
|
data.character = playerData and playerData.name;
|
||||||
|
data.client_id = playerData and playerData.id;
|
||||||
|
data.server = info.server;
|
||||||
|
data.gameTime = info.time;
|
||||||
|
data.clientTime = os.time();
|
||||||
|
data.platform = framework.settings.Platform;
|
||||||
|
return data;
|
||||||
|
end;
|
||||||
|
--builds a packet containing ls message data
|
||||||
|
function buildMessagePayload(message, ls)
|
||||||
|
slugs = split(message, "> ");
|
||||||
|
slugs = split(slugs[1], " <");
|
||||||
|
nameparsed = slugs[2];
|
||||||
|
message = message:gsub("%" .. slugs[1] .. "<" .. nameparsed .. "> ", "");
|
||||||
|
payload = {};
|
||||||
|
payload.name = nameparsed:gsub("[\n\r]", "");
|
||||||
|
payload.message = message:gsub("[\n\r]", "");
|
||||||
|
payload.linkshellname = ls:gsub("[\n\r]", "");
|
||||||
|
return payload;
|
||||||
|
end;
|
||||||
|
--builds a packet containing shout data
|
||||||
|
function buildShoutPayload(msg)
|
||||||
|
slugs = split(msg, ":");
|
||||||
|
nameArea = slugs[1];
|
||||||
|
table.remove(slugs, 1);
|
||||||
|
nameAreaSlugs = split(nameArea, "[");
|
||||||
|
message = table.concat(slugs, ":");
|
||||||
|
name = nameAreaSlugs[1];
|
||||||
|
area = nameAreaSlugs[2]:gsub("]", "");
|
||||||
|
payload = {};
|
||||||
|
payload.name = name;
|
||||||
|
payload.area = area;
|
||||||
|
payload.message = string.gsub(message, "^%s*(.-)%s*$", "%1");
|
||||||
|
return payload;
|
||||||
|
end;
|
||||||
|
--build the "other" payload (testing only)
|
||||||
|
function buildOtherPayload(msg, mode, modemod, type)
|
||||||
|
payload = {};
|
||||||
|
payload.message = msg;
|
||||||
|
payload.type = type;
|
||||||
|
payload.mode = mode;
|
||||||
|
payload.modemod = modemod;
|
||||||
|
return payload;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--force the game to display the lsmes for both linkshells (used to determine which ls is in which slot)
|
||||||
|
function getLSMes()
|
||||||
|
framework.sendInputCommand("/lsmes")
|
||||||
|
coroutine.sleep(2);
|
||||||
|
framework.sendInputCommand("/ls2mes");
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
|
--restart the receiver coroutine
|
||||||
|
function setupReceiver()
|
||||||
|
RECEIVER_ROUTINE = coroutine.schedule(listen, 0.25);
|
||||||
|
end;
|
||||||
|
|
||||||
|
--closes the tcp connection if its open, resets connection and auth bits, recreate the tcp connection, perform auth challenge if connection is successful
|
||||||
|
function ConnectToDiscordBot()
|
||||||
|
tcp:close();
|
||||||
|
connected = false;
|
||||||
|
authed = false;
|
||||||
|
tcp = assert(socket.tcp());
|
||||||
|
tcp:connect(framework.settings.HostAddress, framework.settings.HostPort);
|
||||||
|
tcp:setoption("keepalive", true);
|
||||||
|
tcp:settimeout(framework.settings.TCPTimeout);
|
||||||
|
connectionpacket = tcp:receive("*l");
|
||||||
|
if connectionpacket == "CHALLENGE" then
|
||||||
|
connected = true;
|
||||||
|
sendToBot(buildPacket("HANDSHAKE", buildConnectionPacket()));
|
||||||
|
setupReceiver();
|
||||||
|
elseif framework.settings.AutoReconnect then
|
||||||
|
sendError("Unable to connect.")
|
||||||
|
sendError("The discord bot is either offline or your connection information is incorrect.")
|
||||||
|
sendError("Use //lc autoreconnect to stop reconnection attempts.");
|
||||||
|
RECONNECT_ROUTINE = coroutine.schedule(ConnectToDiscordBot, 30);
|
||||||
|
else
|
||||||
|
sendError("Unable to connect. The discord bot is either offline or your connection information is incorrect.");
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
--wrapper for tcp:send
|
||||||
|
function sendToBot(payload)
|
||||||
|
tcp:send(payload);
|
||||||
|
end;
|
||||||
|
--called on a coroutine on an interval when connected to retreive and process new socket data
|
||||||
|
function handleResponse()
|
||||||
|
tcp:settimeout(0);
|
||||||
|
response = tcp:receive("*l");
|
||||||
|
packet = response and framework.json.decode(response);
|
||||||
|
if packet then
|
||||||
|
heartbeatReply = true;
|
||||||
|
if packet.type == "HANDSHAKE" then
|
||||||
|
if packet.payload == "ACCEPTED" then
|
||||||
|
authed = true;
|
||||||
|
coroutine.schedule(heartbeat, 1);
|
||||||
|
sendNotice("Streaming to discord enabled.");
|
||||||
|
getLSMes();
|
||||||
|
SendBuffer();
|
||||||
|
else
|
||||||
|
sendError("LinkCloud handshake failed. Please check your API key and try again.");
|
||||||
|
end;
|
||||||
|
elseif packet.type == "SYSTEM_MESSAGE" then
|
||||||
|
if packet.payload.isError then
|
||||||
|
sendError(packet.payload.message);
|
||||||
|
else
|
||||||
|
sendNotice(packet.payload.message);
|
||||||
|
end;
|
||||||
|
elseif packet.type == "LS_ECHO" then
|
||||||
|
if packet.payload.linkshell == linkShells[1].name then
|
||||||
|
windower.chat.input("/l [" .. packet.payload.from .. "] " .. packet.payload.message:gsub("[\n\r]", " "));
|
||||||
|
elseif packet.payload.linkshell == linkShells[2].name then
|
||||||
|
windower.chat.input("/l2 [" .. packet.payload.from .. "] " .. packet.payload.message:gsub("[\n\r]", " "));
|
||||||
|
end;
|
||||||
|
--some stuff to make sure im not sending the message the server told me to send back to the server again
|
||||||
|
table.insert(echos, "[" .. packet.payload.from .. "] " .. packet.payload.message:gsub("[\n\r]", " "));
|
||||||
|
if #echos > 10 then
|
||||||
|
table.remove(echos, 1);
|
||||||
|
end;
|
||||||
|
else
|
||||||
|
removePacketFromBuffers(packet.packetId);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
--array.includes
|
||||||
|
function has_value(tab, val)
|
||||||
|
for index, value in ipairs(tab) do
|
||||||
|
if value == val:gsub("[\n\r]", " ") then
|
||||||
|
return true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
return false;
|
||||||
|
end;
|
||||||
|
--listen wrapper
|
||||||
|
function listen()
|
||||||
|
handleResponse();
|
||||||
|
setupReceiver();
|
||||||
|
end;
|
||||||
|
--sends a heartbeat every second
|
||||||
|
function heartbeat()
|
||||||
|
if heartbeatReply then
|
||||||
|
heartbeatReply = false;
|
||||||
|
sendToBot(buildPacket("HEARTBEAT", {}));
|
||||||
|
coroutine.schedule(heartbeat, 1);
|
||||||
|
else
|
||||||
|
notifiyConnectionLost();
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
--make sure the user is the Linkshell holder before sending the add request for the linkshell to the server
|
||||||
|
function sendLinkshellAddRequest(requestId)
|
||||||
|
if framework.playerIsLsOwner() then
|
||||||
|
playerData = framework.getPlayerData()
|
||||||
|
sendNotice("Sending request to add " .. playerData.linkshell .. " to LinkCloud...");
|
||||||
|
sendToBot(buildPacket("ADD_LINKSHELL", {
|
||||||
|
linkId = requestId,
|
||||||
|
lsName = playerData.linkshell
|
||||||
|
}));
|
||||||
|
else
|
||||||
|
sendError("You must be the Linkshell owner to use this command. Please ensure you only have YOUR linkshell equipped in the #1 slot.");
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onLSMesUpdate (lsIndex, text)
|
||||||
|
lsData = getLSFromLsmes(text)
|
||||||
|
if lsData.shellNumber ~= 0 then
|
||||||
|
linkShells[lsIndex] = lsData
|
||||||
|
sendToBot(buildPacket("LINKSHELL_UPDATE", {
|
||||||
|
linkshellname = linkShells[lsIndex].name
|
||||||
|
}));
|
||||||
|
sendNotice("Linkshell #" .. lsIndex .. " changed to " .. linkShells[lsIndex].name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function dump(o)
|
||||||
|
if type(o) == 'table' then
|
||||||
|
local s = '{ '
|
||||||
|
for k, v in pairs(o) do
|
||||||
|
if type(k) ~= 'number' then
|
||||||
|
k = '\"' .. k .. '\"'
|
||||||
|
end
|
||||||
|
s = s .. '[' .. k .. '] = ' .. dump(v) .. ', '
|
||||||
|
end
|
||||||
|
return s .. '} '
|
||||||
|
else
|
||||||
|
return tostring(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function onLSMessageReceived (lsIndex, text)
|
||||||
|
|
||||||
|
if linkShells[lsIndex] and linkShells[lsIndex].name ~= nil then
|
||||||
|
payload = buildMessagePayload(text, linkShells[lsIndex].name);
|
||||||
|
|
||||||
|
if not has_value(echos, payload.message) then
|
||||||
|
|
||||||
|
table.insert(lsBuffers[lsIndex], buildPacket("LINKSHELL_MESSAGE", payload));
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function onShoutReceived (text)
|
||||||
|
if framework.settings.ProcessShouts then
|
||||||
|
table.insert(otherbuffer, buildPacket("SHOUT", buildShoutPayload(text)));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function cmdToggleAutoConnect ()
|
||||||
|
framework.settings.AutoConnect = not framework.settings.AutoConnect;
|
||||||
|
sendNotice("Auto Connect on Startup: " .. tostring(framework.settings.AutoConnect));
|
||||||
|
framework.saveAddonConfig();
|
||||||
|
end
|
||||||
|
|
||||||
|
function cmdToggleAutoReconnect ()
|
||||||
|
framework.settings.AutoReconnect = not framework.settings.AutoReconnect;
|
||||||
|
sendNotice("Auto Reconnect: " .. tostring(framework.settings.AutoReconnect));
|
||||||
|
framework.saveAddonConfig();
|
||||||
|
end
|
||||||
|
|
||||||
|
function cmdToggleShouts ()
|
||||||
|
framework.settings.ProcessShouts = not framework.settings.ProcessShouts;
|
||||||
|
sendNotice("Sending Shouts: " .. tostring(framework.settings.ProcessShouts));
|
||||||
|
framework.saveAddonConfig();
|
||||||
|
end
|
||||||
|
|
||||||
|
function cmdShowVersion (engineVer)
|
||||||
|
sendNotice('Engine Version: ' .. engineVer .. ' | LinkCloud Version: ' .. _version)
|
||||||
|
end
|
||||||
|
function sendNotice (msg)
|
||||||
|
if(framework.sendNoticeMessage ~= nil) then framework.sendNoticeMessage(msg) end
|
||||||
|
if(framework.consoleLog ~= nil) then framework.consoleLog(msg) end
|
||||||
|
end
|
||||||
|
function sendError (msg)
|
||||||
|
if(framework.sendErrorMessage ~= nil) then framework.sendErrorMessage(msg) end
|
||||||
|
if(framework.consoleLog ~= nil) then framework.consoleLog('ERROR: ' .. msg) end
|
||||||
|
end
|
||||||
|
function cmdShowHelpMenu()
|
||||||
|
sendNotice(" *** LinkCloud v" .. _version .. " - Author: Twisted ***");
|
||||||
|
sendNotice(" connect | c --> Attempts to connect to the LinkCloud service.");
|
||||||
|
sendNotice(" autoconnect --> Toggles Automatic connection when game starts. [Default On]");
|
||||||
|
sendNotice(" autoreconnect --> Toggles Automatic reconnection when connection is lost. [Default On]");
|
||||||
|
sendNotice(" shouts --> Toggles sending shouts to LinkCloud. [Default On]")
|
||||||
|
sendNotice(" version | ver | v --> Displays Version");
|
||||||
|
sendNotice(" status | s --> Shows connection status to the server");
|
||||||
|
sendNotice(" help | h --> Displays this message");
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
hook = hook,
|
||||||
|
onLSMesUpdate = onLSMesUpdate,
|
||||||
|
onLSMessageReceived = onLSMessageReceived,
|
||||||
|
onShoutReceived = onShoutReceived,
|
||||||
|
sendLinkshellAddRequest = sendLinkshellAddRequest
|
||||||
|
}
|
12
addon/LinkCloud/data/settings.xml
Normal file
12
addon/LinkCloud/data/settings.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.1" ?>
|
||||||
|
<settings>
|
||||||
|
<global>
|
||||||
|
<AuthKey></AuthKey>
|
||||||
|
<AutoConnect>true</AutoConnect>
|
||||||
|
<AutoReconnect>true</AutoReconnect>
|
||||||
|
<HostAddress>linkcloud.drunken.games</HostAddress>
|
||||||
|
<HostPort>5050</HostPort>
|
||||||
|
<ProcessShouts>true</ProcessShouts>
|
||||||
|
<TCPTimeout>2</TCPTimeout>
|
||||||
|
</global>
|
||||||
|
</settings>
|
20
addon/LinkCloud/lcmem.lua
Normal file
20
addon/LinkCloud/lcmem.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
local struct_lib = require('struct')
|
||||||
|
local string = struct_lib.string
|
||||||
|
local tag = struct_lib.tag
|
||||||
|
local uint32 = struct_lib.uint32
|
||||||
|
local uint16 = struct_lib.uint16
|
||||||
|
local int16 = struct_lib.int16
|
||||||
|
local pc_name = string(0x10)
|
||||||
|
local entity_id = tag(uint32, 'entity')
|
||||||
|
local ip = tag(uint32, 'ip')
|
||||||
|
|
||||||
|
function getAccountInfo()
|
||||||
|
return struct({signature = '538B5C240856578BFB83C9FF33C053F2AEA1'}, {
|
||||||
|
version = {0x248, string(0x10)},
|
||||||
|
ip = {0x260, ip},
|
||||||
|
port = {0x26C, uint16},
|
||||||
|
id = {0x314, entity_id},
|
||||||
|
name = {0x318, pc_name},
|
||||||
|
server_id = {0x390, int16},
|
||||||
|
})
|
||||||
|
end
|
11
addon/LinkCloud/lunajson.lua
Normal file
11
addon/LinkCloud/lunajson.lua
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
local newdecoder = require 'lunajson.decoder'
|
||||||
|
local newencoder = require 'lunajson.encoder'
|
||||||
|
local sax = require 'lunajson.sax'
|
||||||
|
-- If you need multiple contexts of decoder and/or encoder,
|
||||||
|
-- you can require lunajson.decoder and/or lunajson.encoder directly.
|
||||||
|
return {
|
||||||
|
decode = newdecoder(),
|
||||||
|
encode = newencoder(),
|
||||||
|
newparser = sax.newparser,
|
||||||
|
newfileparser = sax.newfileparser,
|
||||||
|
}
|
515
addon/LinkCloud/lunajson/decoder.lua
Normal file
515
addon/LinkCloud/lunajson/decoder.lua
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
local setmetatable, tonumber, tostring =
|
||||||
|
setmetatable, tonumber, tostring
|
||||||
|
local floor, inf =
|
||||||
|
math.floor, math.huge
|
||||||
|
local mininteger, tointeger =
|
||||||
|
math.mininteger or nil, math.tointeger or nil
|
||||||
|
local byte, char, find, gsub, match, sub =
|
||||||
|
string.byte, string.char, string.find, string.gsub, string.match, string.sub
|
||||||
|
|
||||||
|
local function _decode_error(pos, errmsg)
|
||||||
|
error("parse error at " .. pos .. ": " .. errmsg, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_str_ctrl_pat
|
||||||
|
if _VERSION == "Lua 5.1" then
|
||||||
|
-- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
|
||||||
|
f_str_ctrl_pat = '[^\32-\255]'
|
||||||
|
else
|
||||||
|
f_str_ctrl_pat = '[\0-\31]'
|
||||||
|
end
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
|
||||||
|
local function newdecoder()
|
||||||
|
local json, pos, nullv, arraylen, rec_depth
|
||||||
|
|
||||||
|
-- `f` is the temporary for dispatcher[c] and
|
||||||
|
-- the dummy for the first return value of `find`
|
||||||
|
local dispatcher, f
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Helper
|
||||||
|
--]]
|
||||||
|
local function decode_error(errmsg)
|
||||||
|
return _decode_error(pos, errmsg)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Invalid
|
||||||
|
--]]
|
||||||
|
local function f_err()
|
||||||
|
decode_error('invalid value')
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Constants
|
||||||
|
--]]
|
||||||
|
-- null
|
||||||
|
local function f_nul()
|
||||||
|
if sub(json, pos, pos+2) == 'ull' then
|
||||||
|
pos = pos+3
|
||||||
|
return nullv
|
||||||
|
end
|
||||||
|
decode_error('invalid value')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- false
|
||||||
|
local function f_fls()
|
||||||
|
if sub(json, pos, pos+3) == 'alse' then
|
||||||
|
pos = pos+4
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
decode_error('invalid value')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- true
|
||||||
|
local function f_tru()
|
||||||
|
if sub(json, pos, pos+2) == 'rue' then
|
||||||
|
pos = pos+3
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
decode_error('invalid value')
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Numbers
|
||||||
|
Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)
|
||||||
|
is captured as a number and its conformance to the JSON spec is checked.
|
||||||
|
--]]
|
||||||
|
-- deal with non-standard locales
|
||||||
|
local radixmark = match(tostring(0.5), '[^0-9]')
|
||||||
|
local fixedtonumber = tonumber
|
||||||
|
if radixmark ~= '.' then
|
||||||
|
if find(radixmark, '%W') then
|
||||||
|
radixmark = '%' .. radixmark
|
||||||
|
end
|
||||||
|
fixedtonumber = function(s)
|
||||||
|
return tonumber(gsub(s, '.', radixmark))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function number_error()
|
||||||
|
return decode_error('invalid number')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
||||||
|
local function f_zro(mns)
|
||||||
|
local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0
|
||||||
|
|
||||||
|
if num == '' then
|
||||||
|
if c == '' then
|
||||||
|
if mns then
|
||||||
|
return -0.0
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if c == 'e' or c == 'E' then
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
if c == '' then
|
||||||
|
pos = pos + #num
|
||||||
|
if mns then
|
||||||
|
return -0.0
|
||||||
|
end
|
||||||
|
return 0.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
|
||||||
|
if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
|
||||||
|
if c ~= '' then
|
||||||
|
if c == 'e' or c == 'E' then
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
end
|
||||||
|
if c ~= '' then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = pos + #num
|
||||||
|
c = fixedtonumber(num)
|
||||||
|
|
||||||
|
if mns then
|
||||||
|
c = -c
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
||||||
|
local function f_num(mns)
|
||||||
|
pos = pos-1
|
||||||
|
local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)
|
||||||
|
if byte(num, -1) == 0x2E then -- error if ended with period
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
|
||||||
|
if c ~= '' then
|
||||||
|
if c ~= 'e' and c ~= 'E' then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
if not num or c ~= '' then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = pos + #num
|
||||||
|
c = fixedtonumber(num)
|
||||||
|
|
||||||
|
if mns then
|
||||||
|
c = -c
|
||||||
|
if c == mininteger and not find(num, '[^0-9]') then
|
||||||
|
c = mininteger
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
-- skip minus sign
|
||||||
|
local function f_mns()
|
||||||
|
local c = byte(json, pos)
|
||||||
|
if c then
|
||||||
|
pos = pos+1
|
||||||
|
if c > 0x30 then
|
||||||
|
if c < 0x3A then
|
||||||
|
return f_num(true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if c > 0x2F then
|
||||||
|
return f_zro(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
decode_error('invalid number')
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Strings
|
||||||
|
--]]
|
||||||
|
local f_str_hextbl = {
|
||||||
|
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||||
|
0x8, 0x9, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
|
||||||
|
__index = function()
|
||||||
|
return inf
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(f_str_hextbl, f_str_hextbl)
|
||||||
|
|
||||||
|
local f_str_escapetbl = {
|
||||||
|
['"'] = '"',
|
||||||
|
['\\'] = '\\',
|
||||||
|
['/'] = '/',
|
||||||
|
['b'] = '\b',
|
||||||
|
['f'] = '\f',
|
||||||
|
['n'] = '\n',
|
||||||
|
['r'] = '\r',
|
||||||
|
['t'] = '\t',
|
||||||
|
__index = function()
|
||||||
|
decode_error("invalid escape sequence")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(f_str_escapetbl, f_str_escapetbl)
|
||||||
|
|
||||||
|
local function surrogate_first_error()
|
||||||
|
return decode_error("1st surrogate pair byte not continued by 2nd")
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_str_surrogate_prev = 0
|
||||||
|
local function f_str_subst(ch, ucode)
|
||||||
|
if ch == 'u' then
|
||||||
|
local c1, c2, c3, c4, rest = byte(ucode, 1, 5)
|
||||||
|
ucode = f_str_hextbl[c1-47] * 0x1000 +
|
||||||
|
f_str_hextbl[c2-47] * 0x100 +
|
||||||
|
f_str_hextbl[c3-47] * 0x10 +
|
||||||
|
f_str_hextbl[c4-47]
|
||||||
|
if ucode ~= inf then
|
||||||
|
if ucode < 0x80 then -- 1byte
|
||||||
|
if rest then
|
||||||
|
return char(ucode, rest)
|
||||||
|
end
|
||||||
|
return char(ucode)
|
||||||
|
elseif ucode < 0x800 then -- 2bytes
|
||||||
|
c1 = floor(ucode / 0x40)
|
||||||
|
c2 = ucode - c1 * 0x40
|
||||||
|
c1 = c1 + 0xC0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2)
|
||||||
|
elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes
|
||||||
|
c1 = floor(ucode / 0x1000)
|
||||||
|
ucode = ucode - c1 * 0x1000
|
||||||
|
c2 = floor(ucode / 0x40)
|
||||||
|
c3 = ucode - c2 * 0x40
|
||||||
|
c1 = c1 + 0xE0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
c3 = c3 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, c3, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2, c3)
|
||||||
|
elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st
|
||||||
|
if f_str_surrogate_prev == 0 then
|
||||||
|
f_str_surrogate_prev = ucode
|
||||||
|
if not rest then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
surrogate_first_error()
|
||||||
|
end
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
surrogate_first_error()
|
||||||
|
else -- surrogate pair 2nd
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
ucode = 0x10000 +
|
||||||
|
(f_str_surrogate_prev - 0xD800) * 0x400 +
|
||||||
|
(ucode - 0xDC00)
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
c1 = floor(ucode / 0x40000)
|
||||||
|
ucode = ucode - c1 * 0x40000
|
||||||
|
c2 = floor(ucode / 0x1000)
|
||||||
|
ucode = ucode - c2 * 0x1000
|
||||||
|
c3 = floor(ucode / 0x40)
|
||||||
|
c4 = ucode - c3 * 0x40
|
||||||
|
c1 = c1 + 0xF0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
c3 = c3 + 0x80
|
||||||
|
c4 = c4 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, c3, c4, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2, c3, c4)
|
||||||
|
end
|
||||||
|
decode_error("2nd surrogate pair byte appeared without 1st")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
decode_error("invalid unicode codepoint literal")
|
||||||
|
end
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
surrogate_first_error()
|
||||||
|
end
|
||||||
|
return f_str_escapetbl[ch] .. ucode
|
||||||
|
end
|
||||||
|
|
||||||
|
-- caching interpreted keys for speed
|
||||||
|
local f_str_keycache = setmetatable({}, {__mode="v"})
|
||||||
|
|
||||||
|
local function f_str(iskey)
|
||||||
|
local newpos = pos
|
||||||
|
local tmppos, c1, c2
|
||||||
|
repeat
|
||||||
|
newpos = find(json, '"', newpos, true) -- search '"'
|
||||||
|
if not newpos then
|
||||||
|
decode_error("unterminated string")
|
||||||
|
end
|
||||||
|
tmppos = newpos-1
|
||||||
|
newpos = newpos+1
|
||||||
|
c1, c2 = byte(json, tmppos-1, tmppos)
|
||||||
|
if c2 == 0x5C and c1 == 0x5C then -- skip preceding '\\'s
|
||||||
|
repeat
|
||||||
|
tmppos = tmppos-2
|
||||||
|
c1, c2 = byte(json, tmppos-1, tmppos)
|
||||||
|
until c2 ~= 0x5C or c1 ~= 0x5C
|
||||||
|
tmppos = newpos-2
|
||||||
|
end
|
||||||
|
until c2 ~= 0x5C -- leave if '"' is not preceded by '\'
|
||||||
|
|
||||||
|
local str = sub(json, pos, tmppos)
|
||||||
|
pos = newpos
|
||||||
|
|
||||||
|
if iskey then -- check key cache
|
||||||
|
tmppos = f_str_keycache[str] -- reuse tmppos for cache key/val
|
||||||
|
if tmppos then
|
||||||
|
return tmppos
|
||||||
|
end
|
||||||
|
tmppos = str
|
||||||
|
end
|
||||||
|
|
||||||
|
if find(str, f_str_ctrl_pat) then
|
||||||
|
decode_error("unescaped control string")
|
||||||
|
end
|
||||||
|
if find(str, '\\', 1, true) then -- check whether a backslash exists
|
||||||
|
-- We need to grab 4 characters after the escape char,
|
||||||
|
-- for encoding unicode codepoint to UTF-8.
|
||||||
|
-- As we need to ensure that every first surrogate pair byte is
|
||||||
|
-- immediately followed by second one, we grab upto 5 characters and
|
||||||
|
-- check the last for this purpose.
|
||||||
|
str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst)
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
decode_error("1st surrogate pair byte not continued by 2nd")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if iskey then -- commit key cache
|
||||||
|
f_str_keycache[tmppos] = str
|
||||||
|
end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Arrays, Objects
|
||||||
|
--]]
|
||||||
|
-- array
|
||||||
|
local function f_ary()
|
||||||
|
rec_depth = rec_depth + 1
|
||||||
|
if rec_depth > 1000 then
|
||||||
|
decode_error('too deeply nested json (> 1000)')
|
||||||
|
end
|
||||||
|
local ary = {}
|
||||||
|
|
||||||
|
pos = match(json, '^[ \n\r\t]*()', pos)
|
||||||
|
|
||||||
|
local i = 0
|
||||||
|
if byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty
|
||||||
|
pos = pos+1
|
||||||
|
else
|
||||||
|
local newpos = pos
|
||||||
|
repeat
|
||||||
|
i = i+1
|
||||||
|
f = dispatcher[byte(json,newpos)] -- parse value
|
||||||
|
pos = newpos+1
|
||||||
|
ary[i] = f()
|
||||||
|
newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
|
||||||
|
until not newpos
|
||||||
|
|
||||||
|
newpos = match(json, '^[ \n\r\t]*%]()', pos) -- check closing bracket
|
||||||
|
if not newpos then
|
||||||
|
decode_error("no closing bracket of an array")
|
||||||
|
end
|
||||||
|
pos = newpos
|
||||||
|
end
|
||||||
|
|
||||||
|
if arraylen then -- commit the length of the array if `arraylen` is set
|
||||||
|
ary[0] = i
|
||||||
|
end
|
||||||
|
rec_depth = rec_depth - 1
|
||||||
|
return ary
|
||||||
|
end
|
||||||
|
|
||||||
|
-- objects
|
||||||
|
local function f_obj()
|
||||||
|
rec_depth = rec_depth + 1
|
||||||
|
if rec_depth > 1000 then
|
||||||
|
decode_error('too deeply nested json (> 1000)')
|
||||||
|
end
|
||||||
|
local obj = {}
|
||||||
|
|
||||||
|
pos = match(json, '^[ \n\r\t]*()', pos)
|
||||||
|
if byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty
|
||||||
|
pos = pos+1
|
||||||
|
else
|
||||||
|
local newpos = pos
|
||||||
|
|
||||||
|
repeat
|
||||||
|
if byte(json, newpos) ~= 0x22 then -- check '"'
|
||||||
|
decode_error("not key")
|
||||||
|
end
|
||||||
|
pos = newpos+1
|
||||||
|
local key = f_str(true) -- parse key
|
||||||
|
|
||||||
|
-- optimized for compact json
|
||||||
|
-- c1, c2 == ':', <the first char of the value> or
|
||||||
|
-- c1, c2, c3 == ':', ' ', <the first char of the value>
|
||||||
|
f = f_err
|
||||||
|
local c1, c2, c3 = byte(json, pos, pos+3)
|
||||||
|
if c1 == 0x3A then
|
||||||
|
if c2 ~= 0x20 then
|
||||||
|
f = dispatcher[c2]
|
||||||
|
newpos = pos+2
|
||||||
|
else
|
||||||
|
f = dispatcher[c3]
|
||||||
|
newpos = pos+3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if f == f_err then -- read a colon and arbitrary number of spaces
|
||||||
|
newpos = match(json, '^[ \n\r\t]*:[ \n\r\t]*()', pos)
|
||||||
|
if not newpos then
|
||||||
|
decode_error("no colon after a key")
|
||||||
|
end
|
||||||
|
f = dispatcher[byte(json, newpos)]
|
||||||
|
newpos = newpos+1
|
||||||
|
end
|
||||||
|
pos = newpos
|
||||||
|
obj[key] = f() -- parse value
|
||||||
|
newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos)
|
||||||
|
until not newpos
|
||||||
|
|
||||||
|
newpos = match(json, '^[ \n\r\t]*}()', pos)
|
||||||
|
if not newpos then
|
||||||
|
decode_error("no closing bracket of an object")
|
||||||
|
end
|
||||||
|
pos = newpos
|
||||||
|
end
|
||||||
|
|
||||||
|
rec_depth = rec_depth - 1
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The jump table to dispatch a parser for a value,
|
||||||
|
indexed by the code of the value's first char.
|
||||||
|
Nil key means the end of json.
|
||||||
|
--]]
|
||||||
|
dispatcher = { [0] =
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,
|
||||||
|
f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,
|
||||||
|
f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,
|
||||||
|
__index = function()
|
||||||
|
decode_error("unexpected termination")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(dispatcher, dispatcher)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
run decoder
|
||||||
|
--]]
|
||||||
|
local function decode(json_, pos_, nullv_, arraylen_)
|
||||||
|
json, pos, nullv, arraylen = json_, pos_, nullv_, arraylen_
|
||||||
|
rec_depth = 0
|
||||||
|
|
||||||
|
pos = match(json, '^[ \n\r\t]*()', pos)
|
||||||
|
|
||||||
|
f = dispatcher[byte(json, pos)]
|
||||||
|
pos = pos+1
|
||||||
|
local v = f()
|
||||||
|
|
||||||
|
if pos_ then
|
||||||
|
return v, pos
|
||||||
|
else
|
||||||
|
f, pos = find(json, '^[ \n\r\t]*', pos)
|
||||||
|
if pos ~= #json then
|
||||||
|
decode_error('json ended')
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return decode
|
||||||
|
end
|
||||||
|
|
||||||
|
return newdecoder
|
185
addon/LinkCloud/lunajson/encoder.lua
Normal file
185
addon/LinkCloud/lunajson/encoder.lua
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
local error = error
|
||||||
|
local byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match
|
||||||
|
local concat = table.concat
|
||||||
|
local tostring = tostring
|
||||||
|
local pairs, type = pairs, type
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local huge, tiny = 1/0, -1/0
|
||||||
|
|
||||||
|
local f_string_esc_pat
|
||||||
|
if _VERSION == "Lua 5.1" then
|
||||||
|
-- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
|
||||||
|
f_string_esc_pat = '[^ -!#-[%]^-\255]'
|
||||||
|
else
|
||||||
|
f_string_esc_pat = '[\0-\31"\\]'
|
||||||
|
end
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
|
||||||
|
local function newencoder()
|
||||||
|
local v, nullv
|
||||||
|
local i, builder, visited
|
||||||
|
|
||||||
|
local function f_tostring(v)
|
||||||
|
builder[i] = tostring(v)
|
||||||
|
i = i+1
|
||||||
|
end
|
||||||
|
|
||||||
|
local radixmark = match(tostring(0.5), '[^0-9]')
|
||||||
|
local delimmark = match(tostring(12345.12345), '[^0-9' .. radixmark .. ']')
|
||||||
|
if radixmark == '.' then
|
||||||
|
radixmark = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local radixordelim
|
||||||
|
if radixmark or delimmark then
|
||||||
|
radixordelim = true
|
||||||
|
if radixmark and find(radixmark, '%W') then
|
||||||
|
radixmark = '%' .. radixmark
|
||||||
|
end
|
||||||
|
if delimmark and find(delimmark, '%W') then
|
||||||
|
delimmark = '%' .. delimmark
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_number = function(n)
|
||||||
|
if tiny < n and n < huge then
|
||||||
|
local s = format("%.17g", n)
|
||||||
|
if radixordelim then
|
||||||
|
if delimmark then
|
||||||
|
s = gsub(s, delimmark, '')
|
||||||
|
end
|
||||||
|
if radixmark then
|
||||||
|
s = gsub(s, radixmark, '.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
builder[i] = s
|
||||||
|
i = i+1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
error('invalid number')
|
||||||
|
end
|
||||||
|
|
||||||
|
local doencode
|
||||||
|
|
||||||
|
local f_string_subst = {
|
||||||
|
['"'] = '\\"',
|
||||||
|
['\\'] = '\\\\',
|
||||||
|
['\b'] = '\\b',
|
||||||
|
['\f'] = '\\f',
|
||||||
|
['\n'] = '\\n',
|
||||||
|
['\r'] = '\\r',
|
||||||
|
['\t'] = '\\t',
|
||||||
|
__index = function(_, c)
|
||||||
|
return format('\\u00%02X', byte(c))
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(f_string_subst, f_string_subst)
|
||||||
|
|
||||||
|
local function f_string(s)
|
||||||
|
builder[i] = '"'
|
||||||
|
if find(s, f_string_esc_pat) then
|
||||||
|
s = gsub(s, f_string_esc_pat, f_string_subst)
|
||||||
|
end
|
||||||
|
builder[i+1] = s
|
||||||
|
builder[i+2] = '"'
|
||||||
|
i = i+3
|
||||||
|
end
|
||||||
|
|
||||||
|
local function f_table(o)
|
||||||
|
if visited[o] then
|
||||||
|
error("loop detected")
|
||||||
|
end
|
||||||
|
visited[o] = true
|
||||||
|
|
||||||
|
local tmp = o[0]
|
||||||
|
if type(tmp) == 'number' then -- arraylen available
|
||||||
|
builder[i] = '['
|
||||||
|
i = i+1
|
||||||
|
for j = 1, tmp do
|
||||||
|
doencode(o[j])
|
||||||
|
builder[i] = ','
|
||||||
|
i = i+1
|
||||||
|
end
|
||||||
|
if tmp > 0 then
|
||||||
|
i = i-1
|
||||||
|
end
|
||||||
|
builder[i] = ']'
|
||||||
|
|
||||||
|
else
|
||||||
|
tmp = o[1]
|
||||||
|
if tmp ~= nil then -- detected as array
|
||||||
|
builder[i] = '['
|
||||||
|
i = i+1
|
||||||
|
local j = 2
|
||||||
|
repeat
|
||||||
|
doencode(tmp)
|
||||||
|
tmp = o[j]
|
||||||
|
if tmp == nil then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
j = j+1
|
||||||
|
builder[i] = ','
|
||||||
|
i = i+1
|
||||||
|
until false
|
||||||
|
builder[i] = ']'
|
||||||
|
|
||||||
|
else -- detected as object
|
||||||
|
builder[i] = '{'
|
||||||
|
i = i+1
|
||||||
|
local tmp = i
|
||||||
|
for k, v in pairs(o) do
|
||||||
|
if type(k) ~= 'string' then
|
||||||
|
error("non-string key")
|
||||||
|
end
|
||||||
|
f_string(k)
|
||||||
|
builder[i] = ':'
|
||||||
|
i = i+1
|
||||||
|
doencode(v)
|
||||||
|
builder[i] = ','
|
||||||
|
i = i+1
|
||||||
|
end
|
||||||
|
if i > tmp then
|
||||||
|
i = i-1
|
||||||
|
end
|
||||||
|
builder[i] = '}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i+1
|
||||||
|
visited[o] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local dispatcher = {
|
||||||
|
boolean = f_tostring,
|
||||||
|
number = f_number,
|
||||||
|
string = f_string,
|
||||||
|
table = f_table,
|
||||||
|
__index = function()
|
||||||
|
error("invalid type value")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(dispatcher, dispatcher)
|
||||||
|
|
||||||
|
function doencode(v)
|
||||||
|
if v == nullv then
|
||||||
|
builder[i] = 'null'
|
||||||
|
i = i+1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return dispatcher[type(v)](v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function encode(v_, nullv_)
|
||||||
|
v, nullv = v_, nullv_
|
||||||
|
i, builder, visited = 1, {}, {}
|
||||||
|
|
||||||
|
doencode(v)
|
||||||
|
return concat(builder)
|
||||||
|
end
|
||||||
|
|
||||||
|
return encode
|
||||||
|
end
|
||||||
|
|
||||||
|
return newencoder
|
719
addon/LinkCloud/lunajson/sax.lua
Normal file
719
addon/LinkCloud/lunajson/sax.lua
Normal file
@ -0,0 +1,719 @@
|
|||||||
|
local setmetatable, tonumber, tostring =
|
||||||
|
setmetatable, tonumber, tostring
|
||||||
|
local floor, inf =
|
||||||
|
math.floor, math.huge
|
||||||
|
local mininteger, tointeger =
|
||||||
|
math.mininteger or nil, math.tointeger or nil
|
||||||
|
local byte, char, find, gsub, match, sub =
|
||||||
|
string.byte, string.char, string.find, string.gsub, string.match, string.sub
|
||||||
|
|
||||||
|
local function _parse_error(pos, errmsg)
|
||||||
|
error("parse error at " .. pos .. ": " .. errmsg, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_str_ctrl_pat
|
||||||
|
if _VERSION == "Lua 5.1" then
|
||||||
|
-- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
|
||||||
|
f_str_ctrl_pat = '[^\32-\255]'
|
||||||
|
else
|
||||||
|
f_str_ctrl_pat = '[\0-\31]'
|
||||||
|
end
|
||||||
|
|
||||||
|
local type, unpack = type, table.unpack or unpack
|
||||||
|
local open = io.open
|
||||||
|
|
||||||
|
local _ENV = nil
|
||||||
|
|
||||||
|
|
||||||
|
local function nop() end
|
||||||
|
|
||||||
|
local function newparser(src, saxtbl)
|
||||||
|
local json, jsonnxt, rec_depth
|
||||||
|
local jsonlen, pos, acc = 0, 1, 0
|
||||||
|
|
||||||
|
-- `f` is the temporary for dispatcher[c] and
|
||||||
|
-- the dummy for the first return value of `find`
|
||||||
|
local dispatcher, f
|
||||||
|
|
||||||
|
-- initialize
|
||||||
|
if type(src) == 'string' then
|
||||||
|
json = src
|
||||||
|
jsonlen = #json
|
||||||
|
jsonnxt = function()
|
||||||
|
json = ''
|
||||||
|
jsonlen = 0
|
||||||
|
jsonnxt = nop
|
||||||
|
end
|
||||||
|
else
|
||||||
|
jsonnxt = function()
|
||||||
|
acc = acc + jsonlen
|
||||||
|
pos = 1
|
||||||
|
repeat
|
||||||
|
json = src()
|
||||||
|
if not json then
|
||||||
|
json = ''
|
||||||
|
jsonlen = 0
|
||||||
|
jsonnxt = nop
|
||||||
|
return
|
||||||
|
end
|
||||||
|
jsonlen = #json
|
||||||
|
until jsonlen > 0
|
||||||
|
end
|
||||||
|
jsonnxt()
|
||||||
|
end
|
||||||
|
|
||||||
|
local sax_startobject = saxtbl.startobject or nop
|
||||||
|
local sax_key = saxtbl.key or nop
|
||||||
|
local sax_endobject = saxtbl.endobject or nop
|
||||||
|
local sax_startarray = saxtbl.startarray or nop
|
||||||
|
local sax_endarray = saxtbl.endarray or nop
|
||||||
|
local sax_string = saxtbl.string or nop
|
||||||
|
local sax_number = saxtbl.number or nop
|
||||||
|
local sax_boolean = saxtbl.boolean or nop
|
||||||
|
local sax_null = saxtbl.null or nop
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Helper
|
||||||
|
--]]
|
||||||
|
local function tryc()
|
||||||
|
local c = byte(json, pos)
|
||||||
|
if not c then
|
||||||
|
jsonnxt()
|
||||||
|
c = byte(json, pos)
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_error(errmsg)
|
||||||
|
return _parse_error(acc + pos, errmsg)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tellc()
|
||||||
|
return tryc() or parse_error("unexpected termination")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function spaces() -- skip spaces and prepare the next char
|
||||||
|
while true do
|
||||||
|
pos = match(json, '^[ \n\r\t]*()', pos)
|
||||||
|
if pos <= jsonlen then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if jsonlen == 0 then
|
||||||
|
parse_error("unexpected termination")
|
||||||
|
end
|
||||||
|
jsonnxt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Invalid
|
||||||
|
--]]
|
||||||
|
local function f_err()
|
||||||
|
parse_error('invalid value')
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Constants
|
||||||
|
--]]
|
||||||
|
-- fallback slow constants parser
|
||||||
|
local function generic_constant(target, targetlen, ret, sax_f)
|
||||||
|
for i = 1, targetlen do
|
||||||
|
local c = tellc()
|
||||||
|
if byte(target, i) ~= c then
|
||||||
|
parse_error("invalid char")
|
||||||
|
end
|
||||||
|
pos = pos+1
|
||||||
|
end
|
||||||
|
return sax_f(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- null
|
||||||
|
local function f_nul()
|
||||||
|
if sub(json, pos, pos+2) == 'ull' then
|
||||||
|
pos = pos+3
|
||||||
|
return sax_null(nil)
|
||||||
|
end
|
||||||
|
return generic_constant('ull', 3, nil, sax_null)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- false
|
||||||
|
local function f_fls()
|
||||||
|
if sub(json, pos, pos+3) == 'alse' then
|
||||||
|
pos = pos+4
|
||||||
|
return sax_boolean(false)
|
||||||
|
end
|
||||||
|
return generic_constant('alse', 4, false, sax_boolean)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- true
|
||||||
|
local function f_tru()
|
||||||
|
if sub(json, pos, pos+2) == 'rue' then
|
||||||
|
pos = pos+3
|
||||||
|
return sax_boolean(true)
|
||||||
|
end
|
||||||
|
return generic_constant('rue', 3, true, sax_boolean)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Numbers
|
||||||
|
Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)
|
||||||
|
is captured as a number and its conformance to the JSON spec is checked.
|
||||||
|
--]]
|
||||||
|
-- deal with non-standard locales
|
||||||
|
local radixmark = match(tostring(0.5), '[^0-9]')
|
||||||
|
local fixedtonumber = tonumber
|
||||||
|
if radixmark ~= '.' then
|
||||||
|
if find(radixmark, '%W') then
|
||||||
|
radixmark = '%' .. radixmark
|
||||||
|
end
|
||||||
|
fixedtonumber = function(s)
|
||||||
|
return tonumber(gsub(s, '.', radixmark))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function number_error()
|
||||||
|
return parse_error('invalid number')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fallback slow parser
|
||||||
|
local function generic_number(mns)
|
||||||
|
local buf = {}
|
||||||
|
local i = 1
|
||||||
|
local is_int = true
|
||||||
|
|
||||||
|
local c = byte(json, pos)
|
||||||
|
pos = pos+1
|
||||||
|
|
||||||
|
local function nxt()
|
||||||
|
buf[i] = c
|
||||||
|
i = i+1
|
||||||
|
c = tryc()
|
||||||
|
pos = pos+1
|
||||||
|
end
|
||||||
|
|
||||||
|
if c == 0x30 then
|
||||||
|
nxt()
|
||||||
|
if c and 0x30 <= c and c < 0x3A then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
||||||
|
end
|
||||||
|
if c == 0x2E then
|
||||||
|
is_int = false
|
||||||
|
nxt()
|
||||||
|
if not (c and 0x30 <= c and c < 0x3A) then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
||||||
|
end
|
||||||
|
if c == 0x45 or c == 0x65 then
|
||||||
|
is_int = false
|
||||||
|
nxt()
|
||||||
|
if c == 0x2B or c == 0x2D then
|
||||||
|
nxt()
|
||||||
|
end
|
||||||
|
if not (c and 0x30 <= c and c < 0x3A) then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
||||||
|
end
|
||||||
|
if c and (0x41 <= c and c <= 0x5B or
|
||||||
|
0x61 <= c and c <= 0x7B or
|
||||||
|
c == 0x2B or c == 0x2D or c == 0x2E) then
|
||||||
|
number_error()
|
||||||
|
end
|
||||||
|
pos = pos-1
|
||||||
|
|
||||||
|
local num = char(unpack(buf))
|
||||||
|
num = fixedtonumber(num)
|
||||||
|
if mns then
|
||||||
|
num = -num
|
||||||
|
if num == mininteger and is_int then
|
||||||
|
num = mininteger
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sax_number(num)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
||||||
|
local function f_zro(mns)
|
||||||
|
local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0
|
||||||
|
|
||||||
|
if num == '' then
|
||||||
|
if pos > jsonlen then
|
||||||
|
pos = pos - 1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
if c == '' then
|
||||||
|
if mns then
|
||||||
|
return sax_number(-0.0)
|
||||||
|
end
|
||||||
|
return sax_number(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if c == 'e' or c == 'E' then
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
if c == '' then
|
||||||
|
pos = pos + #num
|
||||||
|
if pos > jsonlen then
|
||||||
|
pos = pos - #num - 1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
if mns then
|
||||||
|
return sax_number(-0.0)
|
||||||
|
end
|
||||||
|
return sax_number(0.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pos = pos-1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
|
||||||
|
if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then
|
||||||
|
pos = pos-1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
|
||||||
|
if c ~= '' then
|
||||||
|
if c == 'e' or c == 'E' then
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
end
|
||||||
|
if c ~= '' then
|
||||||
|
pos = pos-1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = pos + #num
|
||||||
|
if pos > jsonlen then
|
||||||
|
pos = pos - #num - 1
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
c = fixedtonumber(num)
|
||||||
|
|
||||||
|
if mns then
|
||||||
|
c = -c
|
||||||
|
end
|
||||||
|
return sax_number(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
||||||
|
local function f_num(mns)
|
||||||
|
pos = pos-1
|
||||||
|
local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)
|
||||||
|
if byte(num, -1) == 0x2E then -- error if ended with period
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
|
||||||
|
if c ~= '' then
|
||||||
|
if c ~= 'e' and c ~= 'E' then
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
|
||||||
|
if not num or c ~= '' then
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = pos + #num
|
||||||
|
if pos > jsonlen then
|
||||||
|
pos = pos - #num
|
||||||
|
return generic_number(mns)
|
||||||
|
end
|
||||||
|
c = fixedtonumber(num)
|
||||||
|
|
||||||
|
if mns then
|
||||||
|
c = -c
|
||||||
|
if c == mininteger and not find(num, '[^0-9]') then
|
||||||
|
c = mininteger
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sax_number(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- skip minus sign
|
||||||
|
local function f_mns()
|
||||||
|
local c = byte(json, pos) or tellc()
|
||||||
|
if c then
|
||||||
|
pos = pos+1
|
||||||
|
if c > 0x30 then
|
||||||
|
if c < 0x3A then
|
||||||
|
return f_num(true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if c > 0x2F then
|
||||||
|
return f_zro(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
parse_error("invalid number")
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Strings
|
||||||
|
--]]
|
||||||
|
local f_str_hextbl = {
|
||||||
|
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||||
|
0x8, 0x9, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, inf, inf, inf, inf, inf, inf, inf,
|
||||||
|
inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
|
||||||
|
__index = function()
|
||||||
|
return inf
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(f_str_hextbl, f_str_hextbl)
|
||||||
|
|
||||||
|
local f_str_escapetbl = {
|
||||||
|
['"'] = '"',
|
||||||
|
['\\'] = '\\',
|
||||||
|
['/'] = '/',
|
||||||
|
['b'] = '\b',
|
||||||
|
['f'] = '\f',
|
||||||
|
['n'] = '\n',
|
||||||
|
['r'] = '\r',
|
||||||
|
['t'] = '\t',
|
||||||
|
__index = function()
|
||||||
|
parse_error("invalid escape sequence")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
setmetatable(f_str_escapetbl, f_str_escapetbl)
|
||||||
|
|
||||||
|
local function surrogate_first_error()
|
||||||
|
return parse_error("1st surrogate pair byte not continued by 2nd")
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_str_surrogate_prev = 0
|
||||||
|
local function f_str_subst(ch, ucode)
|
||||||
|
if ch == 'u' then
|
||||||
|
local c1, c2, c3, c4, rest = byte(ucode, 1, 5)
|
||||||
|
ucode = f_str_hextbl[c1-47] * 0x1000 +
|
||||||
|
f_str_hextbl[c2-47] * 0x100 +
|
||||||
|
f_str_hextbl[c3-47] * 0x10 +
|
||||||
|
f_str_hextbl[c4-47]
|
||||||
|
if ucode ~= inf then
|
||||||
|
if ucode < 0x80 then -- 1byte
|
||||||
|
if rest then
|
||||||
|
return char(ucode, rest)
|
||||||
|
end
|
||||||
|
return char(ucode)
|
||||||
|
elseif ucode < 0x800 then -- 2bytes
|
||||||
|
c1 = floor(ucode / 0x40)
|
||||||
|
c2 = ucode - c1 * 0x40
|
||||||
|
c1 = c1 + 0xC0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2)
|
||||||
|
elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes
|
||||||
|
c1 = floor(ucode / 0x1000)
|
||||||
|
ucode = ucode - c1 * 0x1000
|
||||||
|
c2 = floor(ucode / 0x40)
|
||||||
|
c3 = ucode - c2 * 0x40
|
||||||
|
c1 = c1 + 0xE0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
c3 = c3 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, c3, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2, c3)
|
||||||
|
elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st
|
||||||
|
if f_str_surrogate_prev == 0 then
|
||||||
|
f_str_surrogate_prev = ucode
|
||||||
|
if not rest then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
surrogate_first_error()
|
||||||
|
end
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
surrogate_first_error()
|
||||||
|
else -- surrogate pair 2nd
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
ucode = 0x10000 +
|
||||||
|
(f_str_surrogate_prev - 0xD800) * 0x400 +
|
||||||
|
(ucode - 0xDC00)
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
c1 = floor(ucode / 0x40000)
|
||||||
|
ucode = ucode - c1 * 0x40000
|
||||||
|
c2 = floor(ucode / 0x1000)
|
||||||
|
ucode = ucode - c2 * 0x1000
|
||||||
|
c3 = floor(ucode / 0x40)
|
||||||
|
c4 = ucode - c3 * 0x40
|
||||||
|
c1 = c1 + 0xF0
|
||||||
|
c2 = c2 + 0x80
|
||||||
|
c3 = c3 + 0x80
|
||||||
|
c4 = c4 + 0x80
|
||||||
|
if rest then
|
||||||
|
return char(c1, c2, c3, c4, rest)
|
||||||
|
end
|
||||||
|
return char(c1, c2, c3, c4)
|
||||||
|
end
|
||||||
|
parse_error("2nd surrogate pair byte appeared without 1st")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
parse_error("invalid unicode codepoint literal")
|
||||||
|
end
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
surrogate_first_error()
|
||||||
|
end
|
||||||
|
return f_str_escapetbl[ch] .. ucode
|
||||||
|
end
|
||||||
|
|
||||||
|
local function f_str(iskey)
|
||||||
|
local pos2 = pos
|
||||||
|
local newpos
|
||||||
|
local str = ''
|
||||||
|
local bs
|
||||||
|
while true do
|
||||||
|
while true do -- search '\' or '"'
|
||||||
|
newpos = find(json, '[\\"]', pos2)
|
||||||
|
if newpos then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
str = str .. sub(json, pos, jsonlen)
|
||||||
|
if pos2 == jsonlen+2 then
|
||||||
|
pos2 = 2
|
||||||
|
else
|
||||||
|
pos2 = 1
|
||||||
|
end
|
||||||
|
jsonnxt()
|
||||||
|
if jsonlen == 0 then
|
||||||
|
parse_error("unterminated string")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if byte(json, newpos) == 0x22 then -- break if '"'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pos2 = newpos+2 -- skip '\<char>'
|
||||||
|
bs = true -- mark the existence of a backslash
|
||||||
|
end
|
||||||
|
str = str .. sub(json, pos, newpos-1)
|
||||||
|
pos = newpos+1
|
||||||
|
|
||||||
|
if find(str, f_str_ctrl_pat) then
|
||||||
|
parse_error("unescaped control string")
|
||||||
|
end
|
||||||
|
if bs then -- a backslash exists
|
||||||
|
-- We need to grab 4 characters after the escape char,
|
||||||
|
-- for encoding unicode codepoint to UTF-8.
|
||||||
|
-- As we need to ensure that every first surrogate pair byte is
|
||||||
|
-- immediately followed by second one, we grab upto 5 characters and
|
||||||
|
-- check the last for this purpose.
|
||||||
|
str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst)
|
||||||
|
if f_str_surrogate_prev ~= 0 then
|
||||||
|
f_str_surrogate_prev = 0
|
||||||
|
parse_error("1st surrogate pair byte not continued by 2nd")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if iskey then
|
||||||
|
return sax_key(str)
|
||||||
|
end
|
||||||
|
return sax_string(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Arrays, Objects
|
||||||
|
--]]
|
||||||
|
-- arrays
|
||||||
|
local function f_ary()
|
||||||
|
rec_depth = rec_depth + 1
|
||||||
|
if rec_depth > 1000 then
|
||||||
|
parse_error('too deeply nested json (> 1000)')
|
||||||
|
end
|
||||||
|
sax_startarray()
|
||||||
|
|
||||||
|
spaces()
|
||||||
|
if byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty
|
||||||
|
pos = pos+1
|
||||||
|
else
|
||||||
|
local newpos
|
||||||
|
while true do
|
||||||
|
f = dispatcher[byte(json, pos)] -- parse value
|
||||||
|
pos = pos+1
|
||||||
|
f()
|
||||||
|
newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
|
||||||
|
if newpos then
|
||||||
|
pos = newpos
|
||||||
|
else
|
||||||
|
newpos = match(json, '^[ \n\r\t]*%]()', pos) -- check closing bracket
|
||||||
|
if newpos then
|
||||||
|
pos = newpos
|
||||||
|
break
|
||||||
|
end
|
||||||
|
spaces() -- since the current chunk can be ended, skip spaces toward following chunks
|
||||||
|
local c = byte(json, pos)
|
||||||
|
pos = pos+1
|
||||||
|
if c == 0x2C then -- check comma again
|
||||||
|
spaces()
|
||||||
|
elseif c == 0x5D then -- check closing bracket again
|
||||||
|
break
|
||||||
|
else
|
||||||
|
parse_error("no closing bracket of an array")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if pos > jsonlen then
|
||||||
|
spaces()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rec_depth = rec_depth - 1
|
||||||
|
return sax_endarray()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- objects
|
||||||
|
local function f_obj()
|
||||||
|
rec_depth = rec_depth + 1
|
||||||
|
if rec_depth > 1000 then
|
||||||
|
parse_error('too deeply nested json (> 1000)')
|
||||||
|
end
|
||||||
|
sax_startobject()
|
||||||
|
|
||||||
|
spaces()
|
||||||
|
if byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty
|
||||||
|
pos = pos+1
|
||||||
|
else
|
||||||
|
local newpos
|
||||||
|
while true do
|
||||||
|
if byte(json, pos) ~= 0x22 then
|
||||||
|
parse_error("not key")
|
||||||
|
end
|
||||||
|
pos = pos+1
|
||||||
|
f_str(true) -- parse key
|
||||||
|
newpos = match(json, '^[ \n\r\t]*:[ \n\r\t]*()', pos) -- check colon
|
||||||
|
if newpos then
|
||||||
|
pos = newpos
|
||||||
|
else
|
||||||
|
spaces() -- read spaces through chunks
|
||||||
|
if byte(json, pos) ~= 0x3A then -- check colon again
|
||||||
|
parse_error("no colon after a key")
|
||||||
|
end
|
||||||
|
pos = pos+1
|
||||||
|
spaces()
|
||||||
|
end
|
||||||
|
if pos > jsonlen then
|
||||||
|
spaces()
|
||||||
|
end
|
||||||
|
f = dispatcher[byte(json, pos)]
|
||||||
|
pos = pos+1
|
||||||
|
f() -- parse value
|
||||||
|
newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
|
||||||
|
if newpos then
|
||||||
|
pos = newpos
|
||||||
|
else
|
||||||
|
newpos = match(json, '^[ \n\r\t]*}()', pos) -- check closing bracket
|
||||||
|
if newpos then
|
||||||
|
pos = newpos
|
||||||
|
break
|
||||||
|
end
|
||||||
|
spaces() -- read spaces through chunks
|
||||||
|
local c = byte(json, pos)
|
||||||
|
pos = pos+1
|
||||||
|
if c == 0x2C then -- check comma again
|
||||||
|
spaces()
|
||||||
|
elseif c == 0x7D then -- check closing bracket again
|
||||||
|
break
|
||||||
|
else
|
||||||
|
parse_error("no closing bracket of an object")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if pos > jsonlen then
|
||||||
|
spaces()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rec_depth = rec_depth - 1
|
||||||
|
return sax_endobject()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The jump table to dispatch a parser for a value,
|
||||||
|
indexed by the code of the value's first char.
|
||||||
|
Key should be non-nil.
|
||||||
|
--]]
|
||||||
|
dispatcher = { [0] =
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,
|
||||||
|
f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,
|
||||||
|
f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,
|
||||||
|
f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,
|
||||||
|
f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
public funcitons
|
||||||
|
--]]
|
||||||
|
local function run()
|
||||||
|
rec_depth = 0
|
||||||
|
spaces()
|
||||||
|
f = dispatcher[byte(json, pos)]
|
||||||
|
pos = pos+1
|
||||||
|
f()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read(n)
|
||||||
|
if n < 0 then
|
||||||
|
error("the argument must be non-negative")
|
||||||
|
end
|
||||||
|
local pos2 = (pos-1) + n
|
||||||
|
local str = sub(json, pos, pos2)
|
||||||
|
while pos2 > jsonlen and jsonlen ~= 0 do
|
||||||
|
jsonnxt()
|
||||||
|
pos2 = pos2 - (jsonlen - (pos-1))
|
||||||
|
str = str .. sub(json, pos, pos2)
|
||||||
|
end
|
||||||
|
if jsonlen ~= 0 then
|
||||||
|
pos = pos2+1
|
||||||
|
end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tellpos()
|
||||||
|
return acc + pos
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
run = run,
|
||||||
|
tryc = tryc,
|
||||||
|
read = read,
|
||||||
|
tellpos = tellpos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function newfileparser(fn, saxtbl)
|
||||||
|
local fp = open(fn)
|
||||||
|
local function gen()
|
||||||
|
local s
|
||||||
|
if fp then
|
||||||
|
s = fp:read(8192)
|
||||||
|
if not s then
|
||||||
|
fp:close()
|
||||||
|
fp = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
return newparser(gen, saxtbl)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
newparser = newparser,
|
||||||
|
newfileparser = newfileparser
|
||||||
|
}
|
1237
addon/LinkCloud/struct.lua
Normal file
1237
addon/LinkCloud/struct.lua
Normal file
File diff suppressed because it is too large
Load Diff
4
server/.env
Normal file
4
server/.env
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TCP_LISTEN_PORT=5050
|
||||||
|
BIND_ADDRESS=0.0.0.0
|
||||||
|
MAX_CLOCK_SYNC_MISMATCH_SECONDS=2
|
||||||
|
JWT_SECRET=
|
82
server/Utility/db.js
Normal file
82
server/Utility/db.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import * as mysql from "mysql2";
|
||||||
|
import { event } from "./eventHandler.js";
|
||||||
|
import { Debug, Log, Warn, Err, logger, profilerDone } from "./loggerUtility.js";
|
||||||
|
|
||||||
|
let pool;
|
||||||
|
|
||||||
|
export let queryCount = 0;
|
||||||
|
|
||||||
|
export const setup = ({ user, host, database, password, connectionLimit }) => {
|
||||||
|
/**
|
||||||
|
* Creates a MySQL connection pool with the provided configuration options.
|
||||||
|
* @param {Object} config - The configuration object for creating the pool.
|
||||||
|
* @param {string} config.host - The host of the MySQL server.
|
||||||
|
* @param {string} config.user - The user to authenticate as.
|
||||||
|
* @param {string} config.database - The name of the database to use for the connection.
|
||||||
|
* @param {string} config.password - The password of the user.
|
||||||
|
* @param {boolean} config.waitForConnections - Determines if the pool should wait for connections.
|
||||||
|
* @param {number} config.connectionLimit - The maximum number of connections to create at once.
|
||||||
|
* @param {number} config.maxIdle - The maximum number of idle connections
|
||||||
|
*/
|
||||||
|
pool = mysql.createPool({
|
||||||
|
host,
|
||||||
|
user,
|
||||||
|
database,
|
||||||
|
password,
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit,
|
||||||
|
maxIdle: 10, // max idle connections, the default value is the same as `connectionLimit`
|
||||||
|
idleTimeout: 60000, // idle connections timeout, in milliseconds, the default value 60000
|
||||||
|
queueLimit: 0,
|
||||||
|
enableKeepAlive: true,
|
||||||
|
keepAliveInitialDelay: 0,
|
||||||
|
});
|
||||||
|
try{
|
||||||
|
runPrepQuery("SELECT 1 FROM linkshells", [], (r,f,e) => {
|
||||||
|
if(!e) {
|
||||||
|
event.emit("DATABASE_CONNECTED");
|
||||||
|
} else {
|
||||||
|
Err('MYSQL_FAILED_NO_TABLES')
|
||||||
|
event.emit("MYSQL_FAILED_NO_TABLES");
|
||||||
|
event.emit("MYSQL_FAILED_TO_CONNECT");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
event.emit("MYSQL_FAILED_TO_CONNECT");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runPrepQuery = (sql, data, callback) => {
|
||||||
|
/**
|
||||||
|
* Establishes a connection to the MySQL database pool and executes a SQL query.
|
||||||
|
* @param {Error} err - The error object returned from the connection attempt.
|
||||||
|
* @param {Connection} conn - The connection object to the MySQL database pool.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
pool.getConnection((err, conn) => {
|
||||||
|
if (err) {
|
||||||
|
Err(`MYSQL ERROR (${err?.errno}): ${err?.code} ${err?.sqlMessage}`);
|
||||||
|
if (queryCount === 0) {
|
||||||
|
event.emit("MYSQL_FAILED_TO_CONNECT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
conn.execute(sql, data, (e, r, f) => {
|
||||||
|
if (typeof callback === "function") callback(r, f, e);
|
||||||
|
queryCount++;
|
||||||
|
pool.releaseConnection(conn);
|
||||||
|
});
|
||||||
|
} else if (typeof callback === "function") callback(false, false, err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const numRows = (rows) => {
|
||||||
|
/**
|
||||||
|
* Returns the number of rows if rows exist, otherwise returns false.
|
||||||
|
* @param {Array} rows - The array of rows to check.
|
||||||
|
* @returns {number|boolean} The number of rows if rows exist, otherwise false.
|
||||||
|
*/
|
||||||
|
if (rows) return rows.length;
|
||||||
|
return false;
|
||||||
|
};
|
86
server/Utility/discordClient.js
Normal file
86
server/Utility/discordClient.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Client, Collection, Events, GatewayIntentBits, Partials } from 'discord.js'
|
||||||
|
import infoCommand from '../commands/utility/info.js'
|
||||||
|
import connectLinkshell from '../commands/utility/connectLinkshell.js'
|
||||||
|
import joinlinkcloud from '../commands/utility/joinlinkcloud.js'
|
||||||
|
import { getLSFromGuildId, getLSModel } from "../Utility/lsModel.js"
|
||||||
|
import linkcloudstatus from '../commands/utility/linkcloudstatus.js'
|
||||||
|
import { event } from "./eventHandler.js";
|
||||||
|
import { Err, Log, Debug, Warn } from '../Utility/loggerUtility.js'
|
||||||
|
import createEcho from '../commands/utility/createEcho.js'
|
||||||
|
|
||||||
|
import fsConfig from '../config.json' assert { type: "json" };
|
||||||
|
const token = fsConfig.token
|
||||||
|
const servers = {
|
||||||
|
retail: fsConfig.retail_servers,
|
||||||
|
private: fsConfig.private_servers
|
||||||
|
}
|
||||||
|
export const client = new Client({ intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildMembers,
|
||||||
|
],
|
||||||
|
partials: [
|
||||||
|
Partials.Channel,
|
||||||
|
Partials.Message
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const outboundQueue = []
|
||||||
|
|
||||||
|
export const botSetup = () => {
|
||||||
|
client.commands = new Collection();
|
||||||
|
client.commands.set(infoCommand.data.name, infoCommand);
|
||||||
|
client.commands.set(connectLinkshell.data.name, connectLinkshell);
|
||||||
|
client.commands.set(joinlinkcloud.data.name, joinlinkcloud)
|
||||||
|
client.commands.set(linkcloudstatus.data.name, linkcloudstatus)
|
||||||
|
client.commands.set(createEcho.data.name, createEcho)
|
||||||
|
client.once(Events.ClientReady, readyClient => {
|
||||||
|
Log(`Discord Bot Ready! Logged in as ${readyClient.user.tag}`);
|
||||||
|
});
|
||||||
|
client.login(token);
|
||||||
|
}
|
||||||
|
client.on(Events.MessageCreate, async message => {
|
||||||
|
if(!message.guildId) return;
|
||||||
|
const linkshell = getLSFromGuildId(message.guildId)
|
||||||
|
const lsChannels = linkshell.channels
|
||||||
|
if (linkshell) {
|
||||||
|
for(const channel of lsChannels) {
|
||||||
|
if(channel.channelId == message.channelId && !message?.author?.bot) {
|
||||||
|
const guild = client.guilds.cache.get(message.guildId)
|
||||||
|
let nickname = false
|
||||||
|
guild.members.cache.forEach((member) => {
|
||||||
|
if (member.user.id == message.author.id) {
|
||||||
|
nickname = member.nickname
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const authorName = nickname ? nickname : message?.author?.globalName
|
||||||
|
//TODO Fix emojis
|
||||||
|
const messagePacket = {platform: linkshell.ffxiver, server: linkshell.server, from: authorName, message: message.content, lsName: linkshell.name}
|
||||||
|
event.emit('NEW_DISCORD_ECHO_RECEIVED', messagePacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Warn('Unable to find linkshell counterpart for this guild.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.on(Events.InteractionCreate, async interaction => {
|
||||||
|
if (interaction.isModalSubmit()) {
|
||||||
|
event.emit('on_' + interaction.customId, interaction)
|
||||||
|
}
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
const command = interaction.client.commands.get(interaction.commandName);
|
||||||
|
if (!command) {
|
||||||
|
Err(`No command matching ${interaction.commandName} was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await command.execute(interaction);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (interaction.replied || interaction.deferred) {
|
||||||
|
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
6
server/Utility/eventHandler.js
Normal file
6
server/Utility/eventHandler.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventEmitter instance that can be used to emit and listen for events.
|
||||||
|
*/
|
||||||
|
export const event = new EventEmitter();
|
146
server/Utility/loggerUtility.js
Normal file
146
server/Utility/loggerUtility.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import winston, { addColors } from "winston";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructures specific formats from the winston library for logging purposes.
|
||||||
|
* @returns An object containing the destructured formats for logging.
|
||||||
|
*/
|
||||||
|
const { combine, timestamp, json, errors, colorize } = winston.format;
|
||||||
|
|
||||||
|
const consoleFormat = winston.format.printf((info) => {
|
||||||
|
/**
|
||||||
|
* Formats log information into a string with timestamp, log level, metadata, message, and duration.
|
||||||
|
* @param {object} info - The log information object containing timestamp, level, meta, message, and durationMs.
|
||||||
|
* @returns {string} A formatted log message string.
|
||||||
|
*/
|
||||||
|
return `[${info.timestamp}][${info.level.toUpperCase()}](${info.meta}) ${info.message}${
|
||||||
|
info.durationMs ? ` Duration: ${info.durationMs}ms` : ""
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
const consoleTransport = new winston.transports.Console({
|
||||||
|
/**
|
||||||
|
* Configures the format of the logs by combining timestamp, consoleFormat, and colorize options.
|
||||||
|
* @param {Object} timestamp - The timestamp configuration object.
|
||||||
|
* @param {Object} consoleFormat - The console format configuration object.
|
||||||
|
* @param {Object} colorize - The colorize configuration object.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
format: combine(timestamp({ format: "YY-MM-DD HH:mm:ss" }), consoleFormat, colorize({ all: true })),
|
||||||
|
handleExceptions: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a Winston logger with specified settings and transports for logging.
|
||||||
|
* @param {string} logger - The name of the logger.
|
||||||
|
* @param {object} options - The configuration options for the logger.
|
||||||
|
* @param {string} options.level - The logging level for the logger.
|
||||||
|
* @param {Array} options.transports - An array of transports for logging.
|
||||||
|
* @param {Array} options.exceptionHandlers - An array of exception handlers for logging.
|
||||||
|
* @param {Array} options.rejectionHandlers - An array of rejection handlers for logging.
|
||||||
|
* @param {boolean} options.exitOnError - Determines if the process should exit on error.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
winston.loggers.add("logger", {
|
||||||
|
level: "debug",
|
||||||
|
transports: [
|
||||||
|
consoleTransport,
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: "./logs/runtimeLog.json",
|
||||||
|
format: combine(timestamp(), errors({ stack: true }), json()),
|
||||||
|
tailable: true,
|
||||||
|
maxsize: 1000000, //1mb
|
||||||
|
handleExceptions: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
exceptionHandlers: [
|
||||||
|
//consoleTransport,
|
||||||
|
new winston.transports.File({ filename: "./logs/exception.log" }),
|
||||||
|
],
|
||||||
|
rejectionHandlers: [
|
||||||
|
//consoleTransport,
|
||||||
|
new winston.transports.File({ filename: "./logs/rejections.log" }),
|
||||||
|
],
|
||||||
|
exitOnError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _getCallerFile = () => {
|
||||||
|
/**
|
||||||
|
* Generates a formatted string representing the file path and line number of the caller.
|
||||||
|
* @returns {string} A string in the format "directory/filename:lineNumber"
|
||||||
|
*/
|
||||||
|
const err = new Error();
|
||||||
|
Error.prepareStackTrace = (_, stack) => stack;
|
||||||
|
const stack = err.stack;
|
||||||
|
Error.prepareStackTrace = undefined;
|
||||||
|
const dir = path.dirname(stack[2].getFileName()).split("/").pop();
|
||||||
|
const filename = path.basename(stack[2].getFileName()).replace(".js", "");
|
||||||
|
return `${dir}/${filename}:${stack[2].getLineNumber()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the logger instance from Winston loggers.
|
||||||
|
* @returns The logger instance for logging purposes.
|
||||||
|
*/
|
||||||
|
export const logger = winston.loggers.get("logger");
|
||||||
|
/**
|
||||||
|
* Add color styles to different log levels for console output.
|
||||||
|
* @param {Object} colors - An object containing color styles for different log levels.
|
||||||
|
* Colors should be specified in the format "style color" (e.g. "bold red").
|
||||||
|
* Available styles: bold, italic
|
||||||
|
* Available colors: black, red, green, yellow, blue, magenta, cyan, white
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
addColors({
|
||||||
|
info: "bold green",
|
||||||
|
warn: "bold italic yellow",
|
||||||
|
error: "bold red",
|
||||||
|
debug: "magenta",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the exitOnError property of the logger to false.
|
||||||
|
* This property determines whether the logger should exit the process when an error occurs.
|
||||||
|
*/
|
||||||
|
logger.exitOnError = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a debug message along with metadata about the caller file.
|
||||||
|
* @param {string} message - The message to be logged.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
export const Debug = (message) => {
|
||||||
|
logger.debug({ message, meta: _getCallerFile() });
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Logs a message using the logger with the caller file information.
|
||||||
|
* @param {string} message - The message to be logged.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
export const Log = (message) => {
|
||||||
|
logger.info({ message, meta: _getCallerFile() });
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Logs a warning message along with metadata about the caller file.
|
||||||
|
* @param {string} message - The warning message to be logged.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
export const Warn = (message) => {
|
||||||
|
logger.warn({ message, meta: _getCallerFile() });
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Logs an error message along with metadata about the caller file.
|
||||||
|
* @param {string} message - The error message to be logged.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
export const Err = (message) => {
|
||||||
|
logger.error({ message, meta: _getCallerFile() });
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Marks the end of profiling and adds metadata about the caller file to the profiler message.
|
||||||
|
* @param {Profiler} profiler - The profiler object used for profiling.
|
||||||
|
* @param {object} message - The message object containing profiling data.
|
||||||
|
* @returns None
|
||||||
|
*/
|
||||||
|
export const profilerDone = (profiler, message) => {
|
||||||
|
profiler.done({ ...message, meta: _getCallerFile() });
|
||||||
|
};
|
226
server/Utility/lsModel.js
Normal file
226
server/Utility/lsModel.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { runPrepQuery, numRows } from "../Utility/db.js";
|
||||||
|
import { Warn, Log, Err, Debug } from "./loggerUtility.js";
|
||||||
|
import { Webhook } from 'discord-webhook-node';
|
||||||
|
import crypto from "crypto";
|
||||||
|
const linkshells = {};
|
||||||
|
const reloadModel = (lsName, serverId, ffxiver) => {
|
||||||
|
runPrepQuery(
|
||||||
|
"SELECT * FROM linkshells WHERE server = ? AND `name` = ? AND `ffxiver` = ? LIMIT 0,1",
|
||||||
|
[serverId, lsName, ffxiver],
|
||||||
|
async (r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
if (numRows(r)) {
|
||||||
|
updateMemory(r[0])
|
||||||
|
} else {
|
||||||
|
Warn(`Unable to locate linkshell model ${lsName}, ${serverId}, ${ffxiver}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const getLsFromChannelId = (channelId) => {
|
||||||
|
for(const ls in linkshells) {
|
||||||
|
for(const channel in linkshells[ls]?.channels) {
|
||||||
|
if(linkshells[ls]?.channels[channel]?.channelId == channelId) {
|
||||||
|
return linkshells[ls]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const reloadModelById = (id) => {
|
||||||
|
runPrepQuery("SELECT * FROM linkshells WHERE id = ? LIMIT 0,1", [id], async (r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
if (numRows(r)) {
|
||||||
|
updateMemory(r[0])
|
||||||
|
} else {
|
||||||
|
Warn(`Unable to locate linkshell model ${id},`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getLSChannels = (linkshellId) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const out = [];
|
||||||
|
runPrepQuery("SELECT * FROM linkshellchannels WHERE linkshellId = ?", [linkshellId], (r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
if (numRows(r)) {
|
||||||
|
for (const row of r) {
|
||||||
|
row.Webhook = new Webhook(row.webhookUrl)
|
||||||
|
row.hookHistory = []
|
||||||
|
out.push(row);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(out);
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const createNewLS = async (lsName, serverId, ffxiver, ownerId) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (!(await lsExists(lsName, serverId, ffxiver))) {
|
||||||
|
runPrepQuery(
|
||||||
|
"INSERT INTO linkshells (`name`, `server`, `discord_owner_id`, `ffxiver`) VALUES(?,?,?,?)",
|
||||||
|
[lsName, serverId, ownerId, ffxiver],
|
||||||
|
(r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
reloadModel(lsName, serverId, ffxiver);
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const addChannelForLinkshell = (linkshellId, channelId, guildId, webhookUrl) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
runPrepQuery("DELETE FROM linkshellchannels WHERE guildId = ? AND channelId = ? LIMIT 1", [guildId, channelId], (r,f,e) => {
|
||||||
|
if(!e) {
|
||||||
|
runPrepQuery(
|
||||||
|
"INSERT INTO linkshellchannels (channelId, linkshellId, guildId, webhookUrl) VALUES(?,?,?,?)",
|
||||||
|
[channelId, linkshellId, guildId, webhookUrl],
|
||||||
|
(r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
reloadModelById(linkshellId);
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const getLSFromGuildId = (gid) => {
|
||||||
|
|
||||||
|
for (const platform in linkshells) {
|
||||||
|
|
||||||
|
for (const server in linkshells[platform]) {
|
||||||
|
|
||||||
|
for (const linkshell in linkshells[platform][server]) {
|
||||||
|
|
||||||
|
for (const channel in linkshells[platform][server][linkshell].channels) {
|
||||||
|
if (linkshells[platform][server][linkshell].channels[channel].guildId == gid) {
|
||||||
|
return getLSModel(linkshell, platform, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
export const getLSModel = (ls, platform, server) => {
|
||||||
|
try {
|
||||||
|
return linkshells[platform][server][ls] ? linkshells[platform][server][ls] : false;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
export const updateLSModel = (ls, newModel) => {
|
||||||
|
if (!linkshells[ls]) return false;
|
||||||
|
linkshells[ls] = { ...linkshells[ls], ...newModel };
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const lsExists = (ls, server, version) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const lsExistsResult = runPrepQuery(
|
||||||
|
"SELECT * FROM linkshells WHERE server = ? AND `name` = ? AND `ffxiver` = ? LIMIT 0,1",
|
||||||
|
[server, ls, version],
|
||||||
|
(r, f, e) => {
|
||||||
|
if (e) return reject(e);
|
||||||
|
if (numRows(r)) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const loadModelFromDB = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
runPrepQuery("SELECT * FROM linkshells", [], async (r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
if (numRows(r)) {
|
||||||
|
for(const row of r) {
|
||||||
|
updateMemory(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMemory = async (row) => {
|
||||||
|
if(!linkshells[row.ffxiver]) linkshells[row.ffxiver] = {}
|
||||||
|
if(!linkshells[row.ffxiver][row.server]) linkshells[row.ffxiver][row.server] = {}
|
||||||
|
if(linkshells[row.ffxiver][row.server][row.name]?.webhookTimer) clearTimeout(linkshells[row.ffxiver][row.server][row.name].webhookTimer)
|
||||||
|
linkshells[row.ffxiver][row.server][row.name] = row
|
||||||
|
linkshells[row.ffxiver][row.server][row.name].channels = await getLSChannels(row.id);
|
||||||
|
linkshells[row.ffxiver][row.server][row.name].webhookQueue = []
|
||||||
|
linkshells[row.ffxiver][row.server][row.name].webhookTimer = processWebhookQueue(linkshells[row.ffxiver][row.server][row.name])
|
||||||
|
}
|
||||||
|
const processWebhookQueue = (linkshell) => {
|
||||||
|
const nextItem = linkshell.webhookQueue.shift()
|
||||||
|
if(!nextItem) {
|
||||||
|
return setTimeout(() => { processWebhookQueue(linkshell) }, 100)
|
||||||
|
}
|
||||||
|
const packet = nextItem.packet
|
||||||
|
const trimmedMsg = packet.payload.message.replace(/[\n\r]/g, '').trim()
|
||||||
|
console.log(`"${packet.payload.name + trimmedMsg}"`)
|
||||||
|
const msgHash = crypto.createHash("md5").update(packet.payload.name + trimmedMsg).digest("hex");
|
||||||
|
for (const channel of linkshell.channels) {
|
||||||
|
if (!findInRecent(msgHash, channel.hookHistory)) {
|
||||||
|
channel.hookHistory.push({msgHash: msgHash, timeStamp: Date.now()})
|
||||||
|
Debug(`${msgHash} added to message hashes.`)
|
||||||
|
channel.Webhook.setUsername(packet.payload.name);
|
||||||
|
channel.Webhook.setAvatar("https://ui-avatars.com/api/?background=random&name=" + packet.payload.name)
|
||||||
|
channel.Webhook.send(trimmedMsg);
|
||||||
|
if(channel.hookHistory.length > 100) {
|
||||||
|
const removed = channel.hookHistory.shift()
|
||||||
|
Debug(`${removed.msgHash} removed from hashes.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setTimeout(() => { processWebhookQueue(linkshell) }, 100)
|
||||||
|
}
|
||||||
|
const findInRecent = (msgHash, history) => {
|
||||||
|
for(const idx in history) {
|
||||||
|
const msgHashData = history[idx]
|
||||||
|
if(msgHashData.msgHash == msgHash) {
|
||||||
|
Debug(`Found Message Hash`)
|
||||||
|
if(msgHashData.timeStamp + 5000 > Date.now()) {
|
||||||
|
Debug(`Hash Timestamp Not Expired ${msgHashData.timeStamp + 5000} / ${Date.now()}`)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Debug(`Hash Timestamp Expired ${msgHashData.timeStamp + 5000} / ${Date.now()}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(`Message Hash Not Found. ${msgHash}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
73
server/Utility/userModel.js
Normal file
73
server/Utility/userModel.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { runPrepQuery, numRows } from "../Utility/db.js";
|
||||||
|
import { Warn } from "./loggerUtility.js";
|
||||||
|
const users = {}
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
const { sign, verify } = jwt;
|
||||||
|
const addUserToMemory = (userId, authToken) => {
|
||||||
|
users[userId] = {userId, authToken}
|
||||||
|
}
|
||||||
|
const removeUser = (userId) => {
|
||||||
|
if(users[userId]?.socket) {
|
||||||
|
socket.destroy()
|
||||||
|
delete users[userId]
|
||||||
|
runPrepQuery("DELETE FROM users WHERE userId = ? LIMIT 1", [userId], (r,f,e) => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getUser = (userId) => {
|
||||||
|
if(users[userId]) return users[userId]
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
export const getUserFromJwt = (jwt) => {
|
||||||
|
try {
|
||||||
|
const decoded = verify(jwt, process.env.JWT_SECRET)
|
||||||
|
return getUser(decoded.userId)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export const setUserSocket = (socket, userId) => {
|
||||||
|
socket.userId = userId
|
||||||
|
users[userId].socket = socket
|
||||||
|
}
|
||||||
|
export const addUser = (userId) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
runPrepQuery("SELECT * FROM users WHERE userId = ? LIMIT 0,1", [userId], async (r,f,e) => {
|
||||||
|
if(!e) {
|
||||||
|
let token
|
||||||
|
if(!numRows(r)) {
|
||||||
|
token = sign({userId: userId}, process.env.JWT_SECRET)
|
||||||
|
runPrepQuery("INSERT INTO users (authToken, userId) VALUES(?,?)", [token, userId], async (r,f,e) => {
|
||||||
|
if(!e) {
|
||||||
|
return resolve({userId, authToken: token})
|
||||||
|
}
|
||||||
|
return resolve(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return resolve({userId, authToken: token})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
export const loadUsersFromDB = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
runPrepQuery("SELECT * FROM users", [], async (r, f, e) => {
|
||||||
|
if (!e) {
|
||||||
|
if (numRows(r)) {
|
||||||
|
for(const row of r) {
|
||||||
|
users[row.userId] = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
190
server/commands/utility/connectLinkshell.js
Normal file
190
server/commands/utility/connectLinkshell.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
ActionRowBuilder,
|
||||||
|
Events,
|
||||||
|
ModalBuilder,
|
||||||
|
TextInputBuilder,
|
||||||
|
TextInputStyle,
|
||||||
|
PermissionsBitField,
|
||||||
|
StringSelectMenuBuilder,
|
||||||
|
StringSelectMenuOptionBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
ComponentType,
|
||||||
|
} from "discord.js";
|
||||||
|
import { getLSFromGuildId, getLSModel, updateLSModel, createNewLS } from "../../Utility/lsModel.js";
|
||||||
|
import { event } from "../../Utility/eventHandler.js";
|
||||||
|
import { client } from "../../Utility/discordClient.js";
|
||||||
|
import config from "../../config.json" assert { type: "json" };
|
||||||
|
import uuid4 from "uuid4";
|
||||||
|
import { runPrepQuery, numRows } from "../../Utility/db.js";
|
||||||
|
|
||||||
|
const servers = config.servers;
|
||||||
|
const options = [];
|
||||||
|
for (const idx in servers) {
|
||||||
|
options.push({ name: servers[idx], value: idx });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("lcaddlinkshell")
|
||||||
|
.setDescription("Add your linkshell to the Link Cloud service.")
|
||||||
|
/*.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("linkshellname")
|
||||||
|
.setDescription("The name of your linkshell, exactly as it appears in game.")
|
||||||
|
.setRequired(true)
|
||||||
|
)*/
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("provider")
|
||||||
|
.setDescription("FFXI Server Type")
|
||||||
|
.setRequired(true)
|
||||||
|
.addChoices(...options)
|
||||||
|
),
|
||||||
|
async execute(interaction) {
|
||||||
|
if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: 'You must have the "Administrator" flag to use this command.',
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const version = interaction.options.getString("provider");
|
||||||
|
const confirm = new ButtonBuilder().setCustomId("confirm").setLabel("Yes").setStyle(ButtonStyle.Success);
|
||||||
|
const cancel = new ButtonBuilder().setCustomId("cancel").setLabel("No").setStyle(ButtonStyle.Danger);
|
||||||
|
|
||||||
|
const response = version > 0 ? await interaction.reply({
|
||||||
|
content: `Have you set up your LinkCloud addon for FFXI using the \`/lcjoin\` command?`,
|
||||||
|
components: [new ActionRowBuilder().addComponents(cancel, confirm)],
|
||||||
|
ephemeral: true,
|
||||||
|
}) : await interaction.reply({
|
||||||
|
content: `LinkCloud is not currently supported by this server. Please visit our discord for more info, or to get this server added to the list.`,
|
||||||
|
ephemeral: true,
|
||||||
|
})
|
||||||
|
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||||
|
try {
|
||||||
|
const confirmation = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||||
|
if (confirmation.customId === "confirm") {
|
||||||
|
let sqlCreationId = false
|
||||||
|
const sqlResult = await runPrepQuery("SELECT * FROM `pendinglinks` WHERE `userId` = ? LIMIT 0,1", [interaction.user.id], async (r,f,e) => {
|
||||||
|
if(numRows(r)) {
|
||||||
|
sqlCreationId = r[0].linkId
|
||||||
|
}
|
||||||
|
const creationId = sqlCreationId ? sqlCreationId : uuid4();
|
||||||
|
if(!sqlCreationId) {
|
||||||
|
await runPrepQuery("INSERT INTO `pendinglinks` (`linkId`, `userId`, `ffxiver`) VALUES(?,?, ?)", [creationId, interaction.user.id, version], async(r,f,e) => {
|
||||||
|
if(e) {
|
||||||
|
return confirmation.update({
|
||||||
|
content: `Something Failed. Try again later.`,
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await confirmation.update({
|
||||||
|
content: `# LinkCloud Linkshell Setup\nPlease follow the steps below.\n\n- Equip the Linkshell you wish to equip in the Linkshell #1 slot.\n- UnEquip any Linkshell equipped in the Linkshell #2 slot.\n- Run the command below in game.\n\n\`\`\`//lc addlinkshell ${creationId}\`\`\`\n\nYou will receive a direct message in discord once the process has completed.`,
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
} else if (confirmation.customId === "cancel") {
|
||||||
|
await confirmation.update({
|
||||||
|
content: "Please run the `/lcjoin` command first, then run this command again.",
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
await interaction.editReply({
|
||||||
|
content: "Confirmation not received within 1 minute, cancelling",
|
||||||
|
components: [],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*const response = await interaction.reply({
|
||||||
|
content: `**Please run the command below in game.**\n\n\`\`\`//lc addlinkshell ${creationId}\`\`\``,
|
||||||
|
ephemeral: true,
|
||||||
|
});*/
|
||||||
|
|
||||||
|
/*const lsName = interaction.options.getString("linkshellname");
|
||||||
|
const version = interaction.options.getString("ffxiversion");
|
||||||
|
|
||||||
|
|
||||||
|
let serverName = "";
|
||||||
|
if (Number(serverId) < 1000) {
|
||||||
|
serverName = servers.retail[serverId];
|
||||||
|
} else {
|
||||||
|
serverName = servers.community[serverId];
|
||||||
|
}
|
||||||
|
console.log(serverId, lsName);
|
||||||
|
const confirm = new ButtonBuilder()
|
||||||
|
.setCustomId("confirm")
|
||||||
|
.setLabel("Connect")
|
||||||
|
.setStyle(ButtonStyle.Success);
|
||||||
|
const cancel = new ButtonBuilder().setCustomId("cancel").setLabel("Cancel").setStyle(ButtonStyle.Danger);
|
||||||
|
|
||||||
|
const response = await interaction.reply({
|
||||||
|
content: `**Confirm the information below to finish**\n\n**Linkshell Name** \`${lsName}\`\n**FFXI Server** \`${serverName}\``,
|
||||||
|
components: [new ActionRowBuilder().addComponents(cancel, confirm)],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||||
|
try {
|
||||||
|
const confirmation = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||||
|
if (confirmation.customId === "confirm") {
|
||||||
|
await confirmation.update({
|
||||||
|
content: `Linkshell ${lsName} on ${serverName} has been added to LinkCloud.`,
|
||||||
|
components: [],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
} else if (confirmation.customId === "cancel") {
|
||||||
|
await confirmation.update({ content: "Action cancelled", components: [] });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await interaction.editReply({
|
||||||
|
content: "Confirmation not received within 1 minute, cancelling",
|
||||||
|
components: [],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
event.on("on_serverId", async (interaction) => {});
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log(lsName)
|
||||||
|
try {
|
||||||
|
let lsModel = getLSModel(lsName.trim().replace(String.fromCharCode(10), '').replace(String.fromCharCode(13), ''))
|
||||||
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
|
console.log(lsModel)
|
||||||
|
if (lsModel) {
|
||||||
|
await interaction.reply({ content: 'Your linkshell already exists.', ephemeral: true });
|
||||||
|
} else {
|
||||||
|
let myHook = false
|
||||||
|
channel.fetchWebhooks().then(async hooks => {
|
||||||
|
hooks.each(hook => {
|
||||||
|
if (hook.owner.id == config.clientId) {
|
||||||
|
myHook = hook
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(!myHook) {
|
||||||
|
myHook = await channel.createWebhook({
|
||||||
|
name: 'LinkCloud',
|
||||||
|
avatar: 'https://i.imgur.com/AfFp7pu.png',
|
||||||
|
}).catch(console.error);
|
||||||
|
}
|
||||||
|
if(myHook) {
|
||||||
|
createNewLS(interaction.guildId, interaction.channelId, lsName, interaction.user.id, myHook.url)
|
||||||
|
await interaction.reply({ content: 'Your linkshell was successfully connected!', ephemeral: true });
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: 'Failed to create webhook.', ephemeral: true });
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||||
|
}*/
|
104
server/commands/utility/createEcho.js
Normal file
104
server/commands/utility/createEcho.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
ActionRowBuilder,
|
||||||
|
Events,
|
||||||
|
ModalBuilder,
|
||||||
|
TextInputBuilder,
|
||||||
|
TextInputStyle,
|
||||||
|
PermissionsBitField,
|
||||||
|
StringSelectMenuBuilder,
|
||||||
|
StringSelectMenuOptionBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
ComponentType,
|
||||||
|
} from "discord.js";
|
||||||
|
import { addChannelForLinkshell } from "../../Utility/lsModel.js";
|
||||||
|
import { event } from "../../Utility/eventHandler.js";
|
||||||
|
import { client } from "../../Utility/discordClient.js";
|
||||||
|
import config from "../../config.json" assert { type: "json" };
|
||||||
|
import uuid4 from "uuid4";
|
||||||
|
import { runPrepQuery, numRows } from "../../Utility/db.js";
|
||||||
|
import servers from "../../resources/servers.json" assert { type: 'json' };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("lccreateecho")
|
||||||
|
.setDescription("Connect a linkshell to a text-chat channel."),
|
||||||
|
async execute(interaction) {
|
||||||
|
if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: 'You must have the "Administrator" flag to use this command.',
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const options = [];
|
||||||
|
runPrepQuery('SELECT * FROM linkshells WHERE discord_owner_id = ?', [interaction.user.id], async (r,f,e) => {
|
||||||
|
if (numRows(r)) {
|
||||||
|
for(const row of r) {
|
||||||
|
let lsName = row.name
|
||||||
|
if (row.ffxiver == 1) {
|
||||||
|
lsName = `${lsName} on ${servers.se[row.server]}`
|
||||||
|
} else {
|
||||||
|
lsName = `${lsName} on ${servers.platforms[ffxiver]}`
|
||||||
|
}
|
||||||
|
options.push(new StringSelectMenuOptionBuilder()
|
||||||
|
.setLabel(row.name)
|
||||||
|
.setDescription(lsName)
|
||||||
|
.setValue(String(row.id)))
|
||||||
|
}
|
||||||
|
const select = new StringSelectMenuBuilder()
|
||||||
|
.setCustomId('selectedLinkshell')
|
||||||
|
.setPlaceholder('Select a Linkshell')
|
||||||
|
.addOptions(...options);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(select);
|
||||||
|
|
||||||
|
const response = await interaction.reply({
|
||||||
|
content: 'Please select the Linkshell you want to echo. Note: This will override any echo currently set up in this channel.',
|
||||||
|
components: [row],
|
||||||
|
ephemeral: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const collector = response.createMessageComponentCollector({ componentType: ComponentType.StringSelect, time: 60_000 });
|
||||||
|
|
||||||
|
collector.on('collect', async i => {
|
||||||
|
const linkshellId = i.values[0];
|
||||||
|
let myHook = false
|
||||||
|
const channel = await client.channels.fetch(i.channelId)
|
||||||
|
channel.fetchWebhooks().then(async hooks => {
|
||||||
|
hooks.each(hook => {
|
||||||
|
if (hook.owner.id == config.clientId) {
|
||||||
|
myHook = hook
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(!myHook) {
|
||||||
|
myHook = await channel.createWebhook({
|
||||||
|
name: 'LinkCloud',
|
||||||
|
avatar: 'https://i.imgur.com/AfFp7pu.png',
|
||||||
|
}).catch(console.error);
|
||||||
|
}
|
||||||
|
if(myHook) {
|
||||||
|
if(await addChannelForLinkshell(linkshellId, i.channelId, i.guildId, myHook.url)) {
|
||||||
|
await i.reply({ content: '# Echo Created!\nYou should see messages as soon as stream data for this linkshell has been received.', ephemeral: true });
|
||||||
|
} else {
|
||||||
|
await i.reply({ content: 'Failed to create echo.', ephemeral: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await i.reply({ content: 'Failed to create webhook.', ephemeral: true });
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await interaction.reply({
|
||||||
|
content: 'You have not added any linkshells. Please use the `/lcaddlinkshell` command to add one.',
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
12
server/commands/utility/info.js
Normal file
12
server/commands/utility/info.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js'
|
||||||
|
import { getLSFromGuildId, getLSModel } from "../../Utility/lsModel.js"
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('lchelp')
|
||||||
|
.setDescription('View Bot Info'),
|
||||||
|
async execute(interaction) {
|
||||||
|
console.log(interaction)
|
||||||
|
console.log(getLSFromGuildId(interaction.guildId))
|
||||||
|
await interaction.reply('Some whitty response!');
|
||||||
|
},
|
||||||
|
};
|
41
server/commands/utility/joinlinkcloud.js
Normal file
41
server/commands/utility/joinlinkcloud.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
ModalBuilder,
|
||||||
|
TextInputBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
} from "discord.js";
|
||||||
|
import { getLSFromGuildId, getLSModel } from "../../Utility/lsModel.js";
|
||||||
|
import { event } from "../../Utility/eventHandler.js";
|
||||||
|
import { numRows, runPrepQuery } from "../../Utility/db.js";
|
||||||
|
import uuid4 from "uuid4";
|
||||||
|
import { addUser } from "../../Utility/userModel.js";
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder().setName("lcjoin").setDescription("Configure and download the LinkCloud addon."),
|
||||||
|
async execute(interaction) {
|
||||||
|
const userString = interaction.user.id;
|
||||||
|
const user = await addUser(userString);
|
||||||
|
const button = new ButtonBuilder()
|
||||||
|
.setLabel("LinkCloud for FFXI Windower Retail")
|
||||||
|
.setURL(`http://linkcloud.drunken.games:3000/addons/windower/retail/${userString}`)
|
||||||
|
.setStyle(ButtonStyle.Link);
|
||||||
|
|
||||||
|
const button2 = new ButtonBuilder()
|
||||||
|
.setLabel("LinkCloud for FFXI Ashita Retail")
|
||||||
|
.setURL(`http://linkcloud.drunken.games:3000/addons/ashita/retail/${userString}`)
|
||||||
|
.setStyle(ButtonStyle.Link);
|
||||||
|
if (user) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: `# LinkCloud Setup\n### Installing LinkCloud for FFXI Retail (PlayOnline)\nIf you are using FFXI Windower 4, use the "[LinkCloud for FFXI Windower Retail](http://linkcloud.drunken.games:3000/addons/windower/retail/${userString})" button to download the addon\nIf you are using Ashita, use the "[LinkCloud for FFXI Ashita Retail](http://linkcloud.drunken.games:3000/addons/ashita/retail/${userString})" button to download the addon\n- Extract the addon to your \`C:\\Program Files (x86)\\Windower\\addons\` folder.\n- Load the addon with the \`//lua l linkcloud\` command\n- To load this addon on startup, add \`lua load linkcloud\` to your \`C:\\Program Files (x86)\\Windower\\scripts\\init.txt\` file.\n\n### Installing LinkCloud for Horizon XI\nWe are currently working with Horizon XI to have our addon added to the list.\n\n### Manual Installation\nIf you have installed the addon manually, you will need to set the auth token for your client. This is not currently supported in the Alpha testing phase.\n\n# Support the project\nPlease join the project discord for continued support and updates, but mostly just to let Smokey know you care ;)\nhttps://discord.gg/n5VYHSQbhA`,
|
||||||
|
ephemeral: true,
|
||||||
|
components: [
|
||||||
|
new ActionRowBuilder().addComponents(button),
|
||||||
|
new ActionRowBuilder().addComponents(button2),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: "Failed to create user.", ephemeral: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
13
server/commands/utility/linkcloudstatus.js
Normal file
13
server/commands/utility/linkcloudstatus.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { SlashCommandBuilder, ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'
|
||||||
|
import { getLSFromGuildId, getLSModel } from "../../Utility/lsModel.js"
|
||||||
|
import { event } from "../../Utility/eventHandler.js";
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('lcstatus')
|
||||||
|
.setDescription('Show the status of LinkCloud.'),
|
||||||
|
async execute(interaction) {
|
||||||
|
const userString = interaction.user.id
|
||||||
|
await interaction.reply({ content: `Link Cloud Status: Online\nYour LinkCloud client is connected to the LinkCloud service.`, ephemeral: true });
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
13
server/config.json
Normal file
13
server/config.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"token": "xxx",
|
||||||
|
"clientId": "xxx",
|
||||||
|
"guildId": "xxx",
|
||||||
|
"db": {
|
||||||
|
"user": "xxx",
|
||||||
|
"host": "xxx",
|
||||||
|
"database": "xxx",
|
||||||
|
"password": "xxx",
|
||||||
|
"connectionLimit": 10
|
||||||
|
},
|
||||||
|
"servers": ["Not Listed", "Square Enix", "Horizon XI"]
|
||||||
|
}
|
50
server/deployment-commands.cjs
Normal file
50
server/deployment-commands.cjs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const { REST, Routes } = require('discord.js');
|
||||||
|
const { clientId, guildId, token } = require('./config.json');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const commands = [];
|
||||||
|
// Grab all the command folders from the commands directory you created earlier
|
||||||
|
const foldersPath = path.join(__dirname, 'commands');
|
||||||
|
const commandFolders = fs.readdirSync(foldersPath);
|
||||||
|
const doStuff = async () => {
|
||||||
|
for (const folder of commandFolders) {
|
||||||
|
// Grab all the command files from the commands directory you created earlier
|
||||||
|
const commandsPath = path.join(foldersPath, folder);
|
||||||
|
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||||
|
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const filePath = path.join(commandsPath, file);
|
||||||
|
const c = await import('file://' + filePath);
|
||||||
|
const command = c.default
|
||||||
|
console.log(command)
|
||||||
|
if ('data' in command && 'execute' in command) {
|
||||||
|
commands.push(command.data.toJSON());
|
||||||
|
} else {
|
||||||
|
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rest = new REST().setToken(token);
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||||
|
|
||||||
|
// The put method is used to fully refresh all commands in the guild with the current set
|
||||||
|
const data = await rest.put(
|
||||||
|
Routes.applicationGuildCommands(clientId, guildId),
|
||||||
|
{ body: commands },
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||||
|
} catch (error) {
|
||||||
|
// And of course, make sure you catch and log any errors!
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
doStuff()
|
||||||
|
// Construct and prepare an instance of the REST module
|
||||||
|
|
||||||
|
|
||||||
|
// and deploy your commands!
|
147
server/ffxidiscord.js
Normal file
147
server/ffxidiscord.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
const Net = require('net');
|
||||||
|
const port = 6033;
|
||||||
|
const { Webhook } = require('discord-webhook-node');
|
||||||
|
const hook = new Webhook("https://discord.com/api/webhooks/1157030453807698040/K80q5yE4qvKeAi7W4arGyP5YH82dTiGDALw2ESU7uzQ3b_Nv5ylgKno80sLsOTVnVQV5");
|
||||||
|
const server = new Net.Server();
|
||||||
|
const botToken = 'MTE1NzAzNDUyMzAyMDc1OTE3MA.GQH1hm.H4vZIz1IpbX31xtTObuIUZa1sN9m5VrM_f0iU0'
|
||||||
|
server.listen(port, function() {
|
||||||
|
console.log(`Server listening for connection requests on socket localhost:${port}`);
|
||||||
|
});
|
||||||
|
messageQueue = []
|
||||||
|
const CryptoJS = require("crypto-js");
|
||||||
|
const Discord = require("discord.js");
|
||||||
|
const { Client, GatewayIntentBits, SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { MessageMentions: { USERS_PATTERN } } = require('discord.js');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
var hasleader = false
|
||||||
|
var ignoreBuffer = []
|
||||||
|
const client = new Discord.Client({
|
||||||
|
intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const commands = {}
|
||||||
|
client.on("messageCreate", function(message) {
|
||||||
|
nickname = false
|
||||||
|
if (message.author.bot) return;
|
||||||
|
if (message.channelId != "1128902878166269962") return;
|
||||||
|
guild = client.guilds.cache.get(message.guildId)
|
||||||
|
guild.members.cache.forEach((member) => {
|
||||||
|
if (member.user.id == message.author.id) {
|
||||||
|
nickname = member.nickname
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (nickname == false || nickname === null) {
|
||||||
|
nickname = message.author.globalName
|
||||||
|
}
|
||||||
|
ignoreBuffer.push("[" + nickname + "] " + message.content.replace(/[\n\r]/g, ''))
|
||||||
|
messageQueue.push("[" + nickname + "] " + message.content.replace(/[\n\r]/g, ''))
|
||||||
|
if(ignoreBuffer.length > 1000) {
|
||||||
|
ignoreBuffer.shift()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.login(botToken);
|
||||||
|
messageBuffer = []
|
||||||
|
sentMessages = []
|
||||||
|
|
||||||
|
var checkDuplicate = (message_hash, time) => {
|
||||||
|
for (let msg of sentMessages) {
|
||||||
|
if (msg.hash == message_hash) {
|
||||||
|
let differential = Math.abs(time - msg.time)
|
||||||
|
if (differential <= 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var processBuffer = () => {
|
||||||
|
if(messageBuffer.length) {
|
||||||
|
msg = messageBuffer.shift()
|
||||||
|
duplicate = true
|
||||||
|
rawInventoryString = String(msg.metaData.server) + String(msg.payload.name) + String(msg.payload.message.replace(/[\n\r]/g, ''));
|
||||||
|
msgTime = String(msg.metaData.time)
|
||||||
|
hashedIS = CryptoJS.MD5(rawInventoryString).toString()
|
||||||
|
|
||||||
|
if(!checkDuplicate(hashedIS, msgTime) && !ignoreBuffer.includes(msg.payload.message.replace(/[\n\r]/g, ''))) {
|
||||||
|
let msgStore = {
|
||||||
|
"hash": hashedIS,
|
||||||
|
"time": msgTime
|
||||||
|
}
|
||||||
|
sentMessages.push(msgStore)
|
||||||
|
if (sentMessages.length > 100) {
|
||||||
|
sentMessages.shift()
|
||||||
|
}
|
||||||
|
hook.setUsername(msg.payload.name);
|
||||||
|
hook.setAvatar("https://ui-avatars.com/api/?background=random&name=" + msg.payload.name)
|
||||||
|
hook.send(msg.payload.message.replace(/[\n\r]/g, ''));
|
||||||
|
if(msg.payload.message.toLowerCase().includes("discord info") || msg.payload.message.toLowerCase().includes("discord invite") || msg.payload.message.toLowerCase().includes("invite to discord")) {
|
||||||
|
msg.metaData.server = 255
|
||||||
|
msg.payload.message = 'https://discord.gg/9ydGN8AHUu'
|
||||||
|
messageQueue.push(msg.payload.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Duplicate Detected.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(processBuffer, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
var processSocketResponse = (socket) => {
|
||||||
|
if(messageQueue.length) {
|
||||||
|
socket.write(messageQueue.shift() + '\n')
|
||||||
|
} else {
|
||||||
|
socket.write('PONG\n');
|
||||||
|
}
|
||||||
|
socket.lastPingTime = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeSocketAsLeader = (socket) => {
|
||||||
|
if (socket.isLeader) { hasleader = false }
|
||||||
|
}
|
||||||
|
setTimeout(processBuffer, 5000)
|
||||||
|
ffxiClient = {
|
||||||
|
|
||||||
|
}
|
||||||
|
server.on('connection', function(socket) {
|
||||||
|
socket.uid = uuidv4();
|
||||||
|
console.log('A new connection has been established.');
|
||||||
|
socket.write('CONNECTION_ACCEPTED\n');
|
||||||
|
|
||||||
|
socket.on('data', function(chunk) {
|
||||||
|
payload = JSON.parse(chunk.toString())
|
||||||
|
if(payload['type'] == "MESSAGE") {
|
||||||
|
messageBuffer.push(payload)
|
||||||
|
socket.linkshell = payload.payload.linkshellname
|
||||||
|
socket.write('RECEIVEDOK\n');
|
||||||
|
console.log(payload);
|
||||||
|
} else if (payload['type'] == "PING") {
|
||||||
|
processSocketResponse(socket)
|
||||||
|
} else if (payload['type'] == "OTHER") {
|
||||||
|
socket.write('RECEIVEDOK\n');
|
||||||
|
} else if (payload['type'] == "HANDSHAKE") {
|
||||||
|
socket.write('WELCOME\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('end', function() {
|
||||||
|
removeSocketAsLeader(socket)
|
||||||
|
console.log('Closing connection with the client');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('error', function(err) {
|
||||||
|
removeSocketAsLeader(socket)
|
||||||
|
console.log(`Error: ${err}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
43
server/logs/combined.log
Normal file
43
server/logs/combined.log
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T05:37:30.066Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T05:37:30.076Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T05:37:30.174Z"}
|
||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T05:40:36.869Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T05:40:36.889Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T05:40:36.984Z"}
|
||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T05:46:42.073Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T05:46:42.092Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T05:46:42.185Z"}
|
||||||
|
{"level":"info","message":"CONNECTED: 24.72.146.204:49705","timestamp":"2024-08-02T05:57:12.452Z"}
|
||||||
|
{"level":"warn","message":"Clock out of sync: 15 second(s) out.","timestamp":"2024-08-02T05:57:12.530Z"}
|
||||||
|
{"level":"info","message":"CLOSED: 24.72.146.204 49705","timestamp":"2024-08-02T05:57:12.531Z"}
|
||||||
|
{"level":"info","message":"CONNECTED: 24.72.146.204:50653","timestamp":"2024-08-02T06:06:31.252Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] connection ACCEPTED.","timestamp":"2024-08-02T06:06:31.334Z"}
|
||||||
|
{"level":"error","message":"Uncaught exception: read ECONNRESET","timestamp":"2024-08-02T06:06:33.603Z"}
|
||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T06:06:35.301Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T06:06:35.308Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T06:06:35.392Z"}
|
||||||
|
{"level":"info","message":"CONNECTED: 24.72.146.204:50707","timestamp":"2024-08-02T06:07:03.663Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] connection ACCEPTED.","timestamp":"2024-08-02T06:07:03.744Z"}
|
||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T06:08:15.712Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T06:08:15.745Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T06:08:15.850Z"}
|
||||||
|
{"level":"info","message":"CONNECTED: 24.72.146.204:50847","timestamp":"2024-08-02T06:08:46.112Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] connection ACCEPTED.","timestamp":"2024-08-02T06:08:46.192Z"}
|
||||||
|
{"level":"info","message":"Connecting to MySQL DB...","timestamp":"2024-08-02T06:14:51.322Z"}
|
||||||
|
{"level":"info","message":"Server bound to 0.0.0.0 on port 5050.","timestamp":"2024-08-02T06:14:51.341Z"}
|
||||||
|
{"level":"info","message":"Database connected successfully.","timestamp":"2024-08-02T06:14:51.450Z"}
|
||||||
|
{"level":"info","message":"CONNECTED: 24.72.146.204:51416","timestamp":"2024-08-02T06:15:21.941Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] connection ACCEPTED.","timestamp":"2024-08-02T06:15:22.022Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:16:04.140Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:16:04.231Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:16:04.388Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:16:35.453Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:16:35.541Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:17:52.177Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:17:52.258Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:17:58.943Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:17:59.024Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:18:24.260Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:18:24.510Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:18:27.506Z"}
|
||||||
|
{"level":"info","message":"[24.72.146.204] LS MESSAGE RECEIVED.","timestamp":"2024-08-02T06:18:27.584Z"}
|
1
server/logs/error.log
Normal file
1
server/logs/error.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"level":"error","message":"Uncaught exception: read ECONNRESET","timestamp":"2024-08-02T06:06:33.603Z"}
|
56
server/logs/exception.log
Normal file
56
server/logs/exception.log
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{"date":"Thu Apr 04 2024 09:43:47 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.01,0.02,0],"uptime":14063589.16},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":1004,"memoryUsage":{"arrayBuffers":414379,"external":4718740,"heapTotal":28983296,"heapUsed":26803808,"rss":83767296},"pid":3324100,"uid":1004,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 12:50:20 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0,0],"uptime":14074781.55},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":616669,"external":4921030,"heapTotal":30031872,"heapUsed":28303960,"rss":87810048},"pid":3329402,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 15:16:08 GMT+0000 (Coordinated Universal Time)","error":{},"exception":true,"level":"error","message":"uncaughtException: crypto.createHash is not a function\nTypeError: crypto.createHash is not a function\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:197:32)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:186:3)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","os":{"loadavg":[0.36,0.12,0.04],"uptime":14083530.13},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":311589,"external":4681486,"heapTotal":28721152,"heapUsed":26739104,"rss":84271104},"pid":3335334,"uid":0,"version":"v20.5.1"},"stack":"TypeError: crypto.createHash is not a function\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:197:32)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:186:3)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","trace":[{"column":32,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"processWebhookQueue","line":197,"method":null,"native":false},{"column":3,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"Timeout._onTimeout","line":186,"method":"_onTimeout","native":false},{"column":17,"file":"node:internal/timers","function":"listOnTimeout","line":573,"method":null,"native":false},{"column":7,"file":"node:internal/timers","function":"process.processTimers","line":514,"method":"processTimers","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 15:49:54 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":5050,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::5050\nError: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:162:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","os":{"loadavg":[0,0,0],"uptime":14085555.69},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":266020,"external":4375222,"heapTotal":40259584,"heapUsed":29239920,"rss":89219072},"pid":3336102,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:162:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":8,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":162,"method":null,"native":false},{"column":25,"file":"node:internal/modules/esm/module_job","function":"ModuleJob.run","line":192,"method":"run","native":false},{"column":24,"file":"node:internal/modules/esm/loader","function":"async DefaultModuleLoader.import","line":228,"method":"import","native":false},{"column":7,"file":"node:internal/process/esm_loader","function":"async loadESM","line":40,"method":null,"native":false},{"column":12,"file":"node:internal/modules/run_main","function":"async handleMainPromise","line":66,"method":null,"native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 16:33:39 GMT+0000 (Coordinated Universal Time)","error":{},"exception":true,"level":"error","message":"uncaughtException: Log is not defined\nReferenceError: Log is not defined\n at findInRecent (file:///home/ffxi/discordbot/Utility/lsModel.js:215:13)\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:199:14)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:187:3)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","os":{"loadavg":[0.03,0.03,0],"uptime":14088180.68},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":373393,"external":4743290,"heapTotal":30818304,"heapUsed":28378384,"rss":91467776},"pid":3336126,"uid":0,"version":"v20.5.1"},"stack":"ReferenceError: Log is not defined\n at findInRecent (file:///home/ffxi/discordbot/Utility/lsModel.js:215:13)\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:199:14)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:187:3)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","trace":[{"column":13,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"findInRecent","line":215,"method":null,"native":false},{"column":14,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"processWebhookQueue","line":199,"method":null,"native":false},{"column":3,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"Timeout._onTimeout","line":187,"method":"_onTimeout","native":false},{"column":17,"file":"node:internal/timers","function":"listOnTimeout","line":573,"method":null,"native":false},{"column":7,"file":"node:internal/timers","function":"process.processTimers","line":514,"method":"processTimers","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 17:55:10 GMT+0000 (Coordinated Universal Time)","error":{},"exception":true,"level":"error","message":"uncaughtException: Debug is not defined\nReferenceError: Debug is not defined\n at findInRecent (file:///home/ffxi/discordbot/Utility/lsModel.js:211:13)\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:195:14)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:188:22)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","os":{"loadavg":[0.11,0.09,0.03],"uptime":14093072.19},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":348143,"external":4718040,"heapTotal":30556160,"heapUsed":27126560,"rss":84647936},"pid":3339231,"uid":0,"version":"v20.5.1"},"stack":"ReferenceError: Debug is not defined\n at findInRecent (file:///home/ffxi/discordbot/Utility/lsModel.js:211:13)\n at processWebhookQueue (file:///home/ffxi/discordbot/Utility/lsModel.js:195:14)\n at Timeout._onTimeout (file:///home/ffxi/discordbot/Utility/lsModel.js:188:22)\n at listOnTimeout (node:internal/timers:573:17)\n at process.processTimers (node:internal/timers:514:7)","trace":[{"column":13,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"findInRecent","line":211,"method":null,"native":false},{"column":14,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"processWebhookQueue","line":195,"method":null,"native":false},{"column":22,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"Timeout._onTimeout","line":188,"method":"_onTimeout","native":false},{"column":17,"file":"node:internal/timers","function":"listOnTimeout","line":573,"method":null,"native":false},{"column":7,"file":"node:internal/timers","function":"process.processTimers","line":514,"method":"processTimers","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 20:28:59 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.04,0.04,0.01],"uptime":14102301.17},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":430329,"external":4745282,"heapTotal":31866880,"heapUsed":29872752,"rss":91582464},"pid":3341647,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Apr 05 2024 07:42:11 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0.02,0],"uptime":14142692.63},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":1004,"memoryUsage":{"arrayBuffers":428736,"external":4733097,"heapTotal":29769728,"heapUsed":27437224,"rss":85319680},"pid":3349137,"uid":1004,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Apr 05 2024 07:59:46 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":5050,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::5050\nError: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","os":{"loadavg":[0,0,0],"uptime":14143748.22},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":266020,"external":4375222,"heapTotal":40521728,"heapUsed":29328008,"rss":88952832},"pid":3349433,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":8,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":166,"method":null,"native":false},{"column":25,"file":"node:internal/modules/esm/module_job","function":"ModuleJob.run","line":192,"method":"run","native":false},{"column":24,"file":"node:internal/modules/esm/loader","function":"async DefaultModuleLoader.import","line":228,"method":"import","native":false},{"column":7,"file":"node:internal/process/esm_loader","function":"async loadESM","line":40,"method":null,"native":false},{"column":12,"file":"node:internal/modules/run_main","function":"async handleMainPromise","line":66,"method":null,"native":false}]}
|
||||||
|
{"date":"Fri Apr 05 2024 08:00:06 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":5050,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::5050\nError: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","os":{"loadavg":[0,0,0],"uptime":14143767.3},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":266020,"external":4375222,"heapTotal":40783872,"heapUsed":29466704,"rss":89710592},"pid":3349455,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":8,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":166,"method":null,"native":false},{"column":25,"file":"node:internal/modules/esm/module_job","function":"ModuleJob.run","line":192,"method":"run","native":false},{"column":24,"file":"node:internal/modules/esm/loader","function":"async DefaultModuleLoader.import","line":228,"method":"import","native":false},{"column":7,"file":"node:internal/process/esm_loader","function":"async loadESM","line":40,"method":null,"native":false},{"column":12,"file":"node:internal/modules/run_main","function":"async handleMainPromise","line":66,"method":null,"native":false}]}
|
||||||
|
{"date":"Fri Apr 05 2024 08:01:59 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":5050,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::5050\nError: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","os":{"loadavg":[1.29,0.45,0.16],"uptime":56.96},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":210035,"external":4319237,"heapTotal":39206912,"heapUsed":29308944,"rss":90214400},"pid":1643,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::5050\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at file:///home/ffxi/discordbot/v2.js:166:8\n at ModuleJob.run (node:internal/modules/esm/module_job:192:25)\n at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)\n at async loadESM (node:internal/process/esm_loader:40:7)\n at async handleMainPromise (node:internal/modules/run_main:66:12)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":8,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":166,"method":null,"native":false},{"column":25,"file":"node:internal/modules/esm/module_job","function":"ModuleJob.run","line":192,"method":"run","native":false},{"column":24,"file":"node:internal/modules/esm/loader","function":"async DefaultModuleLoader.import","line":228,"method":"import","native":false},{"column":7,"file":"node:internal/process/esm_loader","function":"async loadESM","line":40,"method":null,"native":false},{"column":12,"file":"node:internal/modules/run_main","function":"async handleMainPromise","line":66,"method":null,"native":false}]}
|
||||||
|
{"date":"Sun Apr 07 2024 07:52:20 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":3000,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::3000\nError: listen EADDRINUSE: address already in use :::3000\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at Function.listen (/home/ffxi/discordbot/node_modules/express/lib/application.js:635:24)\n at listen (file:///home/ffxi/discordbot/webserver.js:39:9)\n at EventEmitter.<anonymous> (file:///home/ffxi/discordbot/v2.js:27:2)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)","os":{"loadavg":[0.02,0.04,0],"uptime":13258.14},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":307504,"external":4677401,"heapTotal":43401216,"heapUsed":25764720,"rss":92110848},"pid":5271,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::3000\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at Function.listen (/home/ffxi/discordbot/node_modules/express/lib/application.js:635:24)\n at listen (file:///home/ffxi/discordbot/webserver.js:39:9)\n at EventEmitter.<anonymous> (file:///home/ffxi/discordbot/v2.js:27:2)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":24,"file":"/home/ffxi/discordbot/node_modules/express/lib/application.js","function":"Function.listen","line":635,"method":"listen","native":false},{"column":9,"file":"file:///home/ffxi/discordbot/webserver.js","function":"listen","line":39,"method":null,"native":false},{"column":2,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":27,"method":null,"native":false},{"column":5,"file":"node:internal/process/task_queues","function":"process.processTicksAndRejections","line":95,"method":"processTicksAndRejections","native":false}]}
|
||||||
|
{"date":"Sun Apr 07 2024 07:52:38 GMT+0000 (Coordinated Universal Time)","error":{"address":"::","code":"EADDRINUSE","errno":-98,"port":3000,"syscall":"listen"},"exception":true,"level":"error","message":"uncaughtException: listen EADDRINUSE: address already in use :::3000\nError: listen EADDRINUSE: address already in use :::3000\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at Function.listen (/home/ffxi/discordbot/node_modules/express/lib/application.js:635:24)\n at listen (file:///home/ffxi/discordbot/webserver.js:39:9)\n at EventEmitter.<anonymous> (file:///home/ffxi/discordbot/v2.js:27:2)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)","os":{"loadavg":[0.33,0.1,0.02],"uptime":13275.99},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":306792,"external":4676689,"heapTotal":43139072,"heapUsed":25819912,"rss":91869184},"pid":5312,"uid":0,"version":"v20.5.1"},"stack":"Error: listen EADDRINUSE: address already in use :::3000\n at Server.setupListenHandle [as _listen2] (node:net:1872:16)\n at listenInCluster (node:net:1920:12)\n at Server.listen (node:net:2008:7)\n at Function.listen (/home/ffxi/discordbot/node_modules/express/lib/application.js:635:24)\n at listen (file:///home/ffxi/discordbot/webserver.js:39:9)\n at EventEmitter.<anonymous> (file:///home/ffxi/discordbot/v2.js:27:2)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)","trace":[{"column":16,"file":"node:net","function":"Server.setupListenHandle [as _listen2]","line":1872,"method":"setupListenHandle [as _listen2]","native":false},{"column":12,"file":"node:net","function":"listenInCluster","line":1920,"method":null,"native":false},{"column":7,"file":"node:net","function":"Server.listen","line":2008,"method":"listen","native":false},{"column":24,"file":"/home/ffxi/discordbot/node_modules/express/lib/application.js","function":"Function.listen","line":635,"method":"listen","native":false},{"column":9,"file":"file:///home/ffxi/discordbot/webserver.js","function":"listen","line":39,"method":null,"native":false},{"column":2,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":27,"method":null,"native":false},{"column":5,"file":"node:internal/process/task_queues","function":"process.processTicksAndRejections","line":95,"method":"processTicksAndRejections","native":false}]}
|
||||||
|
{"date":"Fri Apr 12 2024 20:19:02 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0,0],"uptime":476737.36},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":508413,"external":4812774,"heapTotal":30556160,"heapUsed":28088384,"rss":84824064},"pid":185768,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Apr 12 2024 20:39:26 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0,0],"uptime":477961.33},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":494042,"external":4798403,"heapTotal":30556160,"heapUsed":27596552,"rss":86097920},"pid":186487,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Apr 12 2024 20:58:19 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.01,0.01,0],"uptime":479093.93},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":535782,"external":4840143,"heapTotal":30031872,"heapUsed":28035832,"rss":85827584},"pid":187277,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Apr 12 2024 21:03:07 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0,0],"uptime":479382.33},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":496838,"external":4801199,"heapTotal":30031872,"heapUsed":27744192,"rss":84545536},"pid":187337,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sat Apr 27 2024 18:44:29 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.06,0.1,0.03],"uptime":195741.7},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":467942,"external":4772303,"heapTotal":29507584,"heapUsed":26748240,"rss":83783680},"pid":833943,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sat Apr 27 2024 18:45:20 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.46,0.19,0.07],"uptime":195793.12},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":722517,"external":5026878,"heapTotal":44449792,"heapUsed":29149912,"rss":96395264},"pid":835640,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri May 24 2024 03:50:06 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.05,0.06,0.01],"uptime":2474878.9},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":469771,"external":4774132,"heapTotal":29507584,"heapUsed":26633056,"rss":83304448},"pid":2104782,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Wed Jul 24 2024 11:48:01 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.07,0.08,0.02],"uptime":7773953.82},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":476589,"external":4780950,"heapTotal":29507584,"heapUsed":27664792,"rss":71475200},"pid":3534568,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sat Jul 27 2024 07:43:41 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.02,0.13,0.11],"uptime":8018494.26},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468158,"external":4815485,"heapTotal":31604736,"heapUsed":29202808,"rss":73105408},"pid":427529,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sat Jul 27 2024 15:25:28 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.02,0.02,0],"uptime":8046200.54},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":551330,"external":4898657,"heapTotal":34226176,"heapUsed":32309808,"rss":90439680},"pid":499842,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sat Jul 27 2024 18:28:48 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.19,0.09,0.03],"uptime":8057200.98},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":470333,"external":4817660,"heapTotal":32653312,"heapUsed":30300760,"rss":87105536},"pid":581771,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 03:40:11 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.02,0.05,0.01],"uptime":8090283.99},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":502060,"external":4849387,"heapTotal":33177600,"heapUsed":31766792,"rss":85803008},"pid":710546,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 03:51:14 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.06,0.06,0],"uptime":8090946.51},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468267,"external":4815594,"heapTotal":31604736,"heapUsed":28886704,"rss":82853888},"pid":747102,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 03:58:34 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.09,0.05,0.01],"uptime":8091386.73},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":469177,"external":4816504,"heapTotal":31080448,"heapUsed":29017064,"rss":86171648},"pid":748538,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 04:14:32 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.29,0.11,0.03],"uptime":8092344.52},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":473825,"external":4821152,"heapTotal":31604736,"heapUsed":29482400,"rss":71237632},"pid":751640,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 04:22:58 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.09,0.08,0.02],"uptime":8092850.89},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":476614,"external":4823941,"heapTotal":31604736,"heapUsed":29060296,"rss":86290432},"pid":754991,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 04:29:17 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.21,0.12,0.04],"uptime":8093229.75},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468821,"external":4816148,"heapTotal":31866880,"heapUsed":29329184,"rss":85712896},"pid":757723,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 04:33:12 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.16,0.14,0.05],"uptime":8093464.82},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":469694,"external":4817021,"heapTotal":32129024,"heapUsed":28982440,"rss":84611072},"pid":758706,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 05:02:20 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.04,0.08,0.06],"uptime":8095212.77},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":484325,"external":4831652,"heapTotal":32653312,"heapUsed":29939888,"rss":88678400},"pid":763918,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 09:04:49 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.01,0.06,0.02],"uptime":8109762.02},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":633225,"external":4980552,"heapTotal":35536896,"heapUsed":33593640,"rss":85016576},"pid":774312,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 14:16:28 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.25,0.13,0.05],"uptime":8128460.62},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":712588,"external":5059915,"heapTotal":46546944,"heapUsed":31689656,"rss":98271232},"pid":906740,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 17:07:43 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.02,0.04,0],"uptime":8138735.99},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":501258,"external":4848585,"heapTotal":33964032,"heapUsed":32347584,"rss":89595904},"pid":906847,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 18:24:33 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.06,0.03,0],"uptime":8143346.01},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":479983,"external":4827310,"heapTotal":32915456,"heapUsed":29925464,"rss":86872064},"pid":962513,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 18:52:29 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.08,0.03,0],"uptime":8145022.29},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":469823,"external":4817150,"heapTotal":31866880,"heapUsed":29714104,"rss":87707648},"pid":972172,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Sun Jul 28 2024 19:16:27 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.1,0.09,0.03],"uptime":8146460.09},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":518532,"external":4865859,"heapTotal":32915456,"heapUsed":30618584,"rss":89399296},"pid":981469,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Tue Jul 30 2024 15:52:58 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.26,0.12,0.04],"uptime":8307051.46},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":471067,"external":4818394,"heapTotal":32653312,"heapUsed":29960048,"rss":86130688},"pid":1657313,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Wed Jul 31 2024 02:53:02 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.05,0.06,0.08],"uptime":8346655.03},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":567686,"external":4915013,"heapTotal":38682624,"heapUsed":33023920,"rss":68386816},"pid":1682856,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Wed Jul 31 2024 04:16:52 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"write"},"exception":true,"level":"error","message":"uncaughtException: write ECONNRESET\nError: write ECONNRESET\n at afterWriteDispatched (node:internal/stream_base_commons:160:15)\n at writeGeneric (node:internal/stream_base_commons:151:3)\n at Socket._writeGeneric (node:net:952:11)\n at Socket._write (node:net:964:8)\n at writeOrBuffer (node:internal/streams/writable:399:12)\n at _write (node:internal/streams/writable:340:10)\n at Writable.write (node:internal/streams/writable:344:10)\n at writeToClientSocket (file:///home/ffxi/discordbot/v2.js:175:16)\n at Server.<anonymous> (file:///home/ffxi/discordbot/v2.js:161:5)\n at Server.emit (node:events:514:28)","os":{"loadavg":[0.06,0.06,0.04],"uptime":8351685.29},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":406071,"external":4818934,"heapTotal":32653312,"heapUsed":30328648,"rss":86663168},"pid":1881787,"uid":0,"version":"v20.5.1"},"stack":"Error: write ECONNRESET\n at afterWriteDispatched (node:internal/stream_base_commons:160:15)\n at writeGeneric (node:internal/stream_base_commons:151:3)\n at Socket._writeGeneric (node:net:952:11)\n at Socket._write (node:net:964:8)\n at writeOrBuffer (node:internal/streams/writable:399:12)\n at _write (node:internal/streams/writable:340:10)\n at Writable.write (node:internal/streams/writable:344:10)\n at writeToClientSocket (file:///home/ffxi/discordbot/v2.js:175:16)\n at Server.<anonymous> (file:///home/ffxi/discordbot/v2.js:161:5)\n at Server.emit (node:events:514:28)","trace":[{"column":15,"file":"node:internal/stream_base_commons","function":"afterWriteDispatched","line":160,"method":null,"native":false},{"column":3,"file":"node:internal/stream_base_commons","function":"writeGeneric","line":151,"method":null,"native":false},{"column":11,"file":"node:net","function":"Socket._writeGeneric","line":952,"method":"_writeGeneric","native":false},{"column":8,"file":"node:net","function":"Socket._write","line":964,"method":"_write","native":false},{"column":12,"file":"node:internal/streams/writable","function":"writeOrBuffer","line":399,"method":null,"native":false},{"column":10,"file":"node:internal/streams/writable","function":"_write","line":340,"method":null,"native":false},{"column":10,"file":"node:internal/streams/writable","function":"Writable.write","line":344,"method":"write","native":false},{"column":16,"file":"file:///home/ffxi/discordbot/v2.js","function":"writeToClientSocket","line":175,"method":null,"native":false},{"column":5,"file":"file:///home/ffxi/discordbot/v2.js","function":null,"line":161,"method":null,"native":false},{"column":28,"file":"node:events","function":"Server.emit","line":514,"method":"emit","native":false}]}
|
||||||
|
{"date":"Wed Jul 31 2024 12:56:33 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.03,0.05,0],"uptime":8382865.83},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":516526,"external":4863853,"heapTotal":38420480,"heapUsed":36158136,"rss":70664192},"pid":1902079,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Wed Jul 31 2024 23:00:03 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.05,0.07,0.01],"uptime":8419076.07},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":505161,"external":4852488,"heapTotal":33964032,"heapUsed":32438624,"rss":79953920},"pid":2128002,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 04:18:01 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.03,0.08,0.04],"uptime":8438153.65},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":480469,"external":4827796,"heapTotal":32129024,"heapUsed":29820296,"rss":85983232},"pid":2253837,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 04:36:39 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.08,0.05,0.03],"uptime":8439271.5},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468772,"external":4816099,"heapTotal":32129024,"heapUsed":29504512,"rss":87814144},"pid":2257396,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 04:38:57 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.07,0.07,0.04],"uptime":8439410.16},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":470239,"external":4817566,"heapTotal":32653312,"heapUsed":29312016,"rss":85970944},"pid":2259537,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 04:53:18 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.06,0.05,0.02],"uptime":8440270.62},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":476888,"external":4824215,"heapTotal":31604736,"heapUsed":29649520,"rss":85463040},"pid":2261908,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 05:14:04 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.01,0.02,0],"uptime":8441516.58},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468854,"external":4816181,"heapTotal":32653312,"heapUsed":29687416,"rss":86962176},"pid":2263719,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 05:32:24 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0,0.03,0.01],"uptime":8442617.4},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":468506,"external":4815833,"heapTotal":31866880,"heapUsed":29104840,"rss":86831104},"pid":2272510,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 07:04:24 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.04,0.03,0.01],"uptime":8448137.39},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":478088,"external":4825415,"heapTotal":32915456,"heapUsed":30692160,"rss":88076288},"pid":2286975,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 09:52:23 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.07,0.07,0.07],"uptime":8458216.26},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":492710,"external":4840037,"heapTotal":33964032,"heapUsed":31764256,"rss":83718144},"pid":2302189,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 11:04:37 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.03,0.05,0.03],"uptime":8462550.46},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":478642,"external":4825969,"heapTotal":32915456,"heapUsed":30369536,"rss":88555520},"pid":2349695,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 11:15:08 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.1,0.07,0.03],"uptime":8463181.08},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":476687,"external":4824014,"heapTotal":32129024,"heapUsed":29306640,"rss":84676608},"pid":2364232,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Thu Aug 01 2024 11:22:23 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.04,0.1,0.06],"uptime":8463616.32},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":467979,"external":4815306,"heapTotal":32129024,"heapUsed":29089648,"rss":84922368},"pid":2366861,"uid":0,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Aug 02 2024 06:06:33 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"read"},"exception":true,"level":"error","message":"uncaughtException: read ECONNRESET\nError: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","os":{"loadavg":[0.07,0.03,0.04],"uptime":1306.82},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":1004,"memoryUsage":{"arrayBuffers":486532,"external":4833859,"heapTotal":32653312,"heapUsed":30044368,"rss":89743360},"pid":2290,"uid":1004,"version":"v20.5.1"},"stack":"Error: read ECONNRESET\n at TCP.onStreamRead (node:internal/stream_base_commons:217:20)","trace":[{"column":20,"file":"node:internal/stream_base_commons","function":"TCP.onStreamRead","line":217,"method":"onStreamRead","native":false}]}
|
||||||
|
{"date":"Fri Aug 02 2024 08:06:00 GMT+0000 (Coordinated Universal Time)","error":{"code":"ECONNRESET","errno":-104,"syscall":"write"},"exception":true,"level":"error","message":"uncaughtException: write ECONNRESET\nError: write ECONNRESET\n at afterWriteDispatched (node:internal/stream_base_commons:160:15)\n at writeGeneric (node:internal/stream_base_commons:151:3)\n at Socket._writeGeneric (node:net:952:11)\n at Socket._write (node:net:964:8)\n at writeOrBuffer (node:internal/streams/writable:399:12)\n at _write (node:internal/streams/writable:340:10)\n at Writable.write (node:internal/streams/writable:344:10)\n at writeToClientSocket (file:///home/ffxi/discordbot/v3.js:237:12)\n at Server.<anonymous> (file:///home/ffxi/discordbot/v3.js:216:3)\n at Server.emit (node:events:514:28)","os":{"loadavg":[0,0,0],"uptime":8473.68},"process":{"argv":["/usr/bin/node","/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":1004,"memoryUsage":{"arrayBuffers":424428,"external":4837291,"heapTotal":33177600,"heapUsed":31130920,"rss":90456064},"pid":18677,"uid":1004,"version":"v20.5.1"},"stack":"Error: write ECONNRESET\n at afterWriteDispatched (node:internal/stream_base_commons:160:15)\n at writeGeneric (node:internal/stream_base_commons:151:3)\n at Socket._writeGeneric (node:net:952:11)\n at Socket._write (node:net:964:8)\n at writeOrBuffer (node:internal/streams/writable:399:12)\n at _write (node:internal/streams/writable:340:10)\n at Writable.write (node:internal/streams/writable:344:10)\n at writeToClientSocket (file:///home/ffxi/discordbot/v3.js:237:12)\n at Server.<anonymous> (file:///home/ffxi/discordbot/v3.js:216:3)\n at Server.emit (node:events:514:28)","trace":[{"column":15,"file":"node:internal/stream_base_commons","function":"afterWriteDispatched","line":160,"method":null,"native":false},{"column":3,"file":"node:internal/stream_base_commons","function":"writeGeneric","line":151,"method":null,"native":false},{"column":11,"file":"node:net","function":"Socket._writeGeneric","line":952,"method":"_writeGeneric","native":false},{"column":8,"file":"node:net","function":"Socket._write","line":964,"method":"_write","native":false},{"column":12,"file":"node:internal/streams/writable","function":"writeOrBuffer","line":399,"method":null,"native":false},{"column":10,"file":"node:internal/streams/writable","function":"_write","line":340,"method":null,"native":false},{"column":10,"file":"node:internal/streams/writable","function":"Writable.write","line":344,"method":"write","native":false},{"column":12,"file":"file:///home/ffxi/discordbot/v3.js","function":"writeToClientSocket","line":237,"method":null,"native":false},{"column":3,"file":"file:///home/ffxi/discordbot/v3.js","function":null,"line":216,"method":null,"native":false},{"column":28,"file":"node:events","function":"Server.emit","line":514,"method":"emit","native":false}]}
|
2
server/logs/rejections.log
Normal file
2
server/logs/rejections.log
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{"date":"Thu Apr 04 2024 09:24:58 GMT+0000 (Coordinated Universal Time)","error":{},"level":"error","message":"unhandledRejection: resolve is not defined\nReferenceError: resolve is not defined\n at file:///home/ffxi/discordbot/Utility/userModel.js:43:25\n at Execute.onResult (file:///home/ffxi/discordbot/Utility/db.js:66:41)\n at /home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js:90:16\n at process.processTicksAndRejections (node:internal/process/task_queues:77:11)","os":{"loadavg":[0.01,0.07,0.03],"uptime":14062459.77},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":1004,"memoryUsage":{"arrayBuffers":320983,"external":4690880,"heapTotal":28721152,"heapUsed":26358312,"rss":84070400},"pid":3323452,"uid":1004,"version":"v20.5.1"},"rejection":true,"stack":"ReferenceError: resolve is not defined\n at file:///home/ffxi/discordbot/Utility/userModel.js:43:25\n at Execute.onResult (file:///home/ffxi/discordbot/Utility/db.js:66:41)\n at /home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js:90:16\n at process.processTicksAndRejections (node:internal/process/task_queues:77:11)","trace":[{"column":25,"file":"file:///home/ffxi/discordbot/Utility/userModel.js","function":null,"line":43,"method":null,"native":false},{"column":41,"file":"file:///home/ffxi/discordbot/Utility/db.js","function":"Execute.onResult","line":66,"method":"onResult","native":false},{"column":16,"file":"/home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js","function":null,"line":90,"method":null,"native":false},{"column":11,"file":"node:internal/process/task_queues","function":"process.processTicksAndRejections","line":77,"method":"processTicksAndRejections","native":false}]}
|
||||||
|
{"date":"Thu Apr 04 2024 18:01:41 GMT+0000 (Coordinated Universal Time)","error":{},"level":"error","message":"unhandledRejection: Cannot read properties of undefined (reading 'webhookTimer')\nTypeError: Cannot read properties of undefined (reading 'webhookTimer')\n at updateMemory (file:///home/ffxi/discordbot/Utility/lsModel.js:180:51)\n at file:///home/ffxi/discordbot/Utility/lsModel.js:165:7\n at Execute.onResult (file:///home/ffxi/discordbot/Utility/db.js:66:41)\n at /home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js:86:16\n at process.processTicksAndRejections (node:internal/process/task_queues:77:11)","os":{"loadavg":[0,0.05,0.02],"uptime":14093462.31},"process":{"argv":["/usr/bin/node","/home/ffxi/discordbot/v2.js"],"cwd":"/home/ffxi/discordbot","execPath":"/usr/bin/node","gid":0,"memoryUsage":{"arrayBuffers":306613,"external":4676510,"heapTotal":43925504,"heapUsed":25552160,"rss":92241920},"pid":3339975,"uid":0,"version":"v20.5.1"},"rejection":true,"stack":"TypeError: Cannot read properties of undefined (reading 'webhookTimer')\n at updateMemory (file:///home/ffxi/discordbot/Utility/lsModel.js:180:51)\n at file:///home/ffxi/discordbot/Utility/lsModel.js:165:7\n at Execute.onResult (file:///home/ffxi/discordbot/Utility/db.js:66:41)\n at /home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js:86:16\n at process.processTicksAndRejections (node:internal/process/task_queues:77:11)","trace":[{"column":51,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":"updateMemory","line":180,"method":null,"native":false},{"column":7,"file":"file:///home/ffxi/discordbot/Utility/lsModel.js","function":null,"line":165,"method":null,"native":false},{"column":41,"file":"file:///home/ffxi/discordbot/Utility/db.js","function":"Execute.onResult","line":66,"method":"onResult","native":false},{"column":16,"file":"/home/ffxi/discordbot/node_modules/mysql2/lib/commands/query.js","function":null,"line":86,"method":null,"native":false},{"column":11,"file":"node:internal/process/task_queues","function":"process.processTicksAndRejections","line":77,"method":"processTicksAndRejections","native":false}]}
|
6651
server/logs/runtimeLog.json
Normal file
6651
server/logs/runtimeLog.json
Normal file
File diff suppressed because it is too large
Load Diff
26
server/package.json
Normal file
26
server/package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "ffxidiscord",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@zip.js/zip.js": "^2.7.41",
|
||||||
|
"adm-zip": "^0.5.12",
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"discord-webhook-node": "^1.1.8",
|
||||||
|
"discord.js": "^14.13.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mysql2": "^3.9.3",
|
||||||
|
"uuid4": "^2.0.3",
|
||||||
|
"winston": "^3.13.0",
|
||||||
|
"xml-js": "^1.6.11"
|
||||||
|
}
|
||||||
|
}
|
BIN
server/resources/addon/LinkCloud.zip
Normal file
BIN
server/resources/addon/LinkCloud.zip
Normal file
Binary file not shown.
25
server/resources/servers.json
Normal file
25
server/resources/servers.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"platforms": {
|
||||||
|
"1": "se",
|
||||||
|
"2": "Horizon XI"
|
||||||
|
},
|
||||||
|
"se": {
|
||||||
|
"2":"Undine",
|
||||||
|
"4":"Bahamut",
|
||||||
|
"5":"Shiva",
|
||||||
|
"8":"Phoenix",
|
||||||
|
"9":"Carbuncle",
|
||||||
|
"10":"Fenrir",
|
||||||
|
"11":"Sylph",
|
||||||
|
"12":"Valefor",
|
||||||
|
"14":"Leviathan",
|
||||||
|
"15":"Odin",
|
||||||
|
"19":"Quetzalcoatl",
|
||||||
|
"20":"Siren",
|
||||||
|
"23":"Ragnarok",
|
||||||
|
"26":"Cerberus",
|
||||||
|
"28":"Bismarck",
|
||||||
|
"30":"Lakshmi",
|
||||||
|
"31":"Asura"
|
||||||
|
}
|
||||||
|
}
|
299
server/v2.js
Normal file
299
server/v2.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import Net from "net";
|
||||||
|
import "dotenv/config";
|
||||||
|
import { setup as dbSetup, runPrepQuery, numRows } from "./Utility/db.js";
|
||||||
|
import { Err, Log, Debug, Warn } from "./Utility/loggerUtility.js";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { botSetup, client } from "./Utility/discordClient.js";
|
||||||
|
import { event } from "./Utility/eventHandler.js";
|
||||||
|
import fsConfig from "./config.json" assert { type: "json" };
|
||||||
|
import { loadModelFromDB, createNewLS } from "./Utility/lsModel.js";
|
||||||
|
import { listen } from "./webserver.js";
|
||||||
|
import { getUserFromJwt, setUserSocket, loadUsersFromDB } from "./Utility/userModel.js";
|
||||||
|
import { getLSModel } from "./Utility/lsModel.js";
|
||||||
|
import { link, write } from "fs";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const token = fsConfig.token;
|
||||||
|
Log("Connecting to MySQL DB...");
|
||||||
|
dbSetup(fsConfig.db);
|
||||||
|
|
||||||
|
event.on("DATABASE_CONNECTED", async () => {
|
||||||
|
Log("Ready!");
|
||||||
|
await loadUsersFromDB();
|
||||||
|
await loadModelFromDB();
|
||||||
|
botSetup();
|
||||||
|
listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.on("MYSQL_FAILED_TO_CONNECT", () => {
|
||||||
|
Err(
|
||||||
|
"Failed to launch LinkCloud. Unable to connect to MySQL database. Please check database connection parameters and try again."
|
||||||
|
);
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = Net.createServer();
|
||||||
|
const discordPacketBuffer = [];
|
||||||
|
const lsPacketBuffer = []
|
||||||
|
const sockets = [];
|
||||||
|
const discordWebhookQueue = []
|
||||||
|
const checkTime = (theirTime) => {
|
||||||
|
const myTime = Math.floor(Date.now() / 1000);
|
||||||
|
return Number(theirTime - myTime);
|
||||||
|
};
|
||||||
|
const hashPacketData = (packet) => {
|
||||||
|
let base = packet.type;
|
||||||
|
base += packet.metaData.gameTime;
|
||||||
|
base += packet.metaData.server;
|
||||||
|
base += packet.payload.name.replace(/[\n\r]/g, '').trim();
|
||||||
|
base += packet.payload.message.replace(/[\n\r]/g, '').trim();
|
||||||
|
base += packet.metaData.platform;
|
||||||
|
if (packet.payload?.linkshellname) {
|
||||||
|
base += packet.payload?.linkshellname;
|
||||||
|
} else if (packet.payload?.area) {
|
||||||
|
base += packet.payload?.area;
|
||||||
|
}
|
||||||
|
return crypto.createHash("md5").update(base).digest("hex");
|
||||||
|
};
|
||||||
|
const addToDiscordBuffer = (packet) => {
|
||||||
|
for (const idx in discordPacketBuffer) {
|
||||||
|
if (discordPacketBuffer[idx].hash == packet.hash) {
|
||||||
|
//Debug(`${packet.hash} exists, skipping...`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Debug(`New hash created ${packet.hash}`)
|
||||||
|
discordPacketBuffer.push(packet);
|
||||||
|
if(discordPacketBuffer.length > 1000) {
|
||||||
|
discordPacketBuffer.shift()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
server.on("connection", (sock) => {
|
||||||
|
//Debug("CONNECTED: " + sock.remoteAddress + ":" + sock.remotePort);
|
||||||
|
sock.on("data", async (data) => {
|
||||||
|
const response = {
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const packets = data.toString().split("\n");
|
||||||
|
for (const p in packets) {
|
||||||
|
if (packets[p]) {
|
||||||
|
const packet = JSON.parse(packets[p]);
|
||||||
|
|
||||||
|
const differential = checkTime(packet?.metaData?.clientTime);
|
||||||
|
let isLsMessage = false;
|
||||||
|
if (differential <= process.env.MAX_CLOCK_SYNC_MISMATCH_SECONDS) {
|
||||||
|
response.type = packet?.type?.toUpperCase();
|
||||||
|
response.packetId = packet?.packetId;
|
||||||
|
switch (response.type) {
|
||||||
|
case "HANDSHAKE":
|
||||||
|
const authed = AuthenticateSocket(packet, sock);
|
||||||
|
if(!authed) {
|
||||||
|
response.error = true;
|
||||||
|
response.errorMsg = "AUTH_FAIL";
|
||||||
|
response.errorDetails = `You shall not pass!`;
|
||||||
|
}
|
||||||
|
response.payload = authed ? "ACCEPTED" : "REJECTED";
|
||||||
|
response.disconnect = true
|
||||||
|
Log(`[${sock.remoteAddress}] connection ${response.payload}.`);
|
||||||
|
break;
|
||||||
|
case "HEARTBEAT":
|
||||||
|
response.payload = "PONG";
|
||||||
|
break;
|
||||||
|
case "LINKSHELL_MESSAGE":
|
||||||
|
console.log(packet)
|
||||||
|
packet.hash = hashPacketData(packet);
|
||||||
|
ProcessLSMessage(packet, sock)
|
||||||
|
break;
|
||||||
|
case "LINKSHELL_UPDATE":
|
||||||
|
console.log(packet)
|
||||||
|
ProcessLSUpdate(packet, sock)
|
||||||
|
break;
|
||||||
|
case "SHOUT":
|
||||||
|
packet.hash = hashPacketData(packet);
|
||||||
|
addToDiscordBuffer(packet);
|
||||||
|
break;
|
||||||
|
case "OTHER":
|
||||||
|
break;
|
||||||
|
case "ADD_LINKSHELL":
|
||||||
|
ProcessAddLinkshell(packet, sock);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response.error = true;
|
||||||
|
response.errorMsg = "UNKNOWN_PACKET_TYPE";
|
||||||
|
response.errorDetails = `UNKNOWN PACKET TYPE`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.error = true;
|
||||||
|
response.errorMsg = "CLOCK_OUT_OF_SYNC";
|
||||||
|
response.errorDetails = `This system and the servers clocks are out of sync by ${String(
|
||||||
|
differential
|
||||||
|
)} second(s).`;
|
||||||
|
}
|
||||||
|
if (sock) writeToClientSocket(sock,JSON.stringify(response) + "\n\r");
|
||||||
|
if (response.error) {
|
||||||
|
console.log(data)
|
||||||
|
console.log(packet)
|
||||||
|
sock.resetAndDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Debug(`TO ${sock.remoteAddress} ${JSON.stringify(response)}`)
|
||||||
|
} catch (ex) {
|
||||||
|
Err("Unexpected packet format, unable to parse.");
|
||||||
|
console.log(ex);
|
||||||
|
console.log(data.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
sock.on("timeout", (data) => {
|
||||||
|
removeSocketFromPool(sock)
|
||||||
|
Debug("TIMEOUT: " + sock.remoteAddress + " " + sock.remotePort);
|
||||||
|
})
|
||||||
|
sock.on("close", (data) => {
|
||||||
|
removeSocketFromPool(sock)
|
||||||
|
Debug("CLOSED: " + sock.remoteAddress + " " + sock.remotePort);
|
||||||
|
});
|
||||||
|
writeToClientSocket(sock,"CHALLENGE\n");
|
||||||
|
sockets.push(sock);
|
||||||
|
});
|
||||||
|
const removeSocketFromPool = (sock) => {
|
||||||
|
const index = sockets.findIndex(function (o) {
|
||||||
|
return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
|
||||||
|
});
|
||||||
|
if (index !== -1) sockets.splice(index, 1);
|
||||||
|
}
|
||||||
|
server.listen(process.env.TCP_LISTEN_PORT, () => {
|
||||||
|
Log(`Server Bound to ${process.env.BIND_ADDRESS} on port ${process.env.TCP_LISTEN_PORT}.`);
|
||||||
|
});
|
||||||
|
const writeToClientSocket = (socket, data, retry = 0) => {
|
||||||
|
if(!socket?.destroyed && socket?.readyState === 'open') {
|
||||||
|
socket.write(data, (err) => {
|
||||||
|
if(err) {
|
||||||
|
Debug(`Failed to write to socket`)
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if(socket?.destroyed) {
|
||||||
|
Debug(`The socket is dead and cant be written to`)
|
||||||
|
} else {
|
||||||
|
if(retry < 5) {
|
||||||
|
Debug(`Retrying to send packet ${data}`)
|
||||||
|
setTimeout(() => {writeToClientSocket(socket, data, retry + 1)}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ProcessLSMessage = (packet, socket) => {
|
||||||
|
for (const idx in lsPacketBuffer) {
|
||||||
|
if (lsPacketBuffer[idx].hash == packet.hash) {
|
||||||
|
//Debug(`${packet.hash} exists, skipping...`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Debug(`New hash created ${packet.hash}`)
|
||||||
|
lsPacketBuffer.push(packet);
|
||||||
|
if(lsPacketBuffer.length > 1000) {
|
||||||
|
lsPacketBuffer.shift()
|
||||||
|
}
|
||||||
|
const linkshell = ProcessLSUpdate(packet, socket)
|
||||||
|
if(!linkshell.channels) return false;
|
||||||
|
const re = /^(\[.+\] .+)/
|
||||||
|
if(!packet.payload.message.match(re)){
|
||||||
|
linkshell.webhookQueue.push({
|
||||||
|
linkshell,
|
||||||
|
packet
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProcessLSUpdate = (packet, socket) => {
|
||||||
|
const linkshell = getLSModel(packet.payload?.linkshellname, packet.metaData.platform, packet.metaData.server);
|
||||||
|
if(linkshell) {
|
||||||
|
linkshell.socket = socket
|
||||||
|
Log(`Linkshell "${packet.payload.linkshellname}" Registered By ${packet.metaData.character}`)
|
||||||
|
}
|
||||||
|
return linkshell
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const sendMessageToClient = (socket, message, error = false) => {
|
||||||
|
const packet = {};
|
||||||
|
packet.type = "SYSTEM_MESSAGE";
|
||||||
|
packet.payload = {};
|
||||||
|
packet.payload.isError = error;
|
||||||
|
packet.payload.message = message;
|
||||||
|
if (socket) writeToClientSocket(socket,JSON.stringify(packet) + "\n\r");
|
||||||
|
};
|
||||||
|
const sendLSMessage = (socket, message, from, ls) => {
|
||||||
|
const packet = {};
|
||||||
|
packet.type = "LS_ECHO";
|
||||||
|
packet.payload = {};
|
||||||
|
packet.payload.from = from.trim();
|
||||||
|
packet.payload.message = message.trim();
|
||||||
|
packet.payload.linkshell = ls
|
||||||
|
if (socket) writeToClientSocket(socket,JSON.stringify(packet) + "\n\r");
|
||||||
|
}
|
||||||
|
const AuthenticateSocket = (packet, socket) => {
|
||||||
|
const authId = packet.payload.authId;
|
||||||
|
const user = getUserFromJwt(authId);
|
||||||
|
if (user) {
|
||||||
|
setUserSocket(socket, user.userId)
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProcessAddLinkshell = (packet, socket) => {
|
||||||
|
const linkId = packet.payload.linkId.replace("\r", "").replace("\n", "");
|
||||||
|
const serverId = packet.metaData.server;
|
||||||
|
const lsName = packet.payload.lsName;
|
||||||
|
const result = runPrepQuery("SELECT * FROM pendinglinks WHERE linkId = ? LIMIT 0,1", [linkId], async (r, f, e) => {
|
||||||
|
if (numRows(r)) {
|
||||||
|
const ffxiver = r[0].ffxiver;
|
||||||
|
const userId = r[0].userId;
|
||||||
|
const discordUser = client.users.cache.get(userId);
|
||||||
|
console.log({ linkId, serverId, lsName, ffxiver, userId });
|
||||||
|
const newLs = await createNewLS(lsName, serverId, ffxiver, userId).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
sendMessageToClient(socket, "An error occured. Contact Support. [0x10]", true);
|
||||||
|
});
|
||||||
|
if (newLs) {
|
||||||
|
discordUser.send(
|
||||||
|
`# Success!\nYour Linkshell ${lsName} has been added to LinkCloud!\n\n## What's Next?\n- Set up the chat echo channel in your discord server using the \`/lccreateecho\` command in the channel you want to use for the chat echo.\n- Encourage LS members to use the \`/lcjoin\` command to get started streaming data to LinkCloud. The more streamers you have, the more reliable the echo will be.`
|
||||||
|
);
|
||||||
|
Log(`New Linkshell "${lsName}" has been created!`);
|
||||||
|
sendMessageToClient(
|
||||||
|
socket,
|
||||||
|
`Linkshell ${lsName} added successfully to LinkCloud. Check discord for further instruction.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Err(`Failed to create Linkshell "${lsName}".`);
|
||||||
|
sendMessageToClient(
|
||||||
|
socket,
|
||||||
|
"An error occured. This is most likely because this Linkshell already exists.",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
console.log(e);
|
||||||
|
discordUser.send(
|
||||||
|
`# Uh-oh!\nYour Linkshell ${lsName} seems to already exist in our database.\n\n## Need Help?\nYou can reach out to support via discord.\nhttps://discord.gg/n5VYHSQbhA`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendMessageToClient(socket, "An error occured. The supplied token is not valid.", true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
event.on('NEW_DISCORD_ECHO_RECEIVED', message => {
|
||||||
|
const linkshell = getLSModel(message.lsName, message.platform, message.server)
|
||||||
|
if(linkshell.socket) {
|
||||||
|
sendLSMessage(linkshell.socket, message.message, message.from, message.lsName)
|
||||||
|
}
|
||||||
|
})
|
421
server/v3.js
Normal file
421
server/v3.js
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
import Net from "net";
|
||||||
|
import "dotenv/config";
|
||||||
|
import { setup as dbSetup, runPrepQuery, numRows } from "./Utility/db.js";
|
||||||
|
import { Err, Log, Debug, Warn } from "./Utility/loggerUtility.js";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { botSetup, client } from "./Utility/discordClient.js";
|
||||||
|
import { event } from "./Utility/eventHandler.js";
|
||||||
|
import fsConfig from "./config.json" assert { type: "json" };
|
||||||
|
import { loadModelFromDB, createNewLS } from "./Utility/lsModel.js";
|
||||||
|
import { listen } from "./webserver.js";
|
||||||
|
import { getUserFromJwt, setUserSocket, loadUsersFromDB } from "./Utility/userModel.js";
|
||||||
|
import { getLSModel } from "./Utility/lsModel.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration.
|
||||||
|
*/
|
||||||
|
const token = fsConfig.token;
|
||||||
|
|
||||||
|
// Log initial message
|
||||||
|
Log("Connecting to MySQL DB...");
|
||||||
|
dbSetup(fsConfig.db);
|
||||||
|
|
||||||
|
// Event listener for successful database connection
|
||||||
|
event.on("DATABASE_CONNECTED", async () => {
|
||||||
|
Log("Ready!");
|
||||||
|
try {
|
||||||
|
await loadUsersFromDB();
|
||||||
|
await loadModelFromDB();
|
||||||
|
botSetup();
|
||||||
|
listen();
|
||||||
|
} catch (error) {
|
||||||
|
Err("Error during setup after database connection.", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for failed database connection
|
||||||
|
event.on("MYSQL_FAILED_TO_CONNECT", () => {
|
||||||
|
Err("Failed to launch LinkCloud. Unable to connect to MySQL database. Please check database connection parameters and try again.");
|
||||||
|
process.exit(1); // Exit with non-zero status for error indication
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global error handlers
|
||||||
|
process.on("uncaughtException", (error) => {
|
||||||
|
Err("Uncaught Exception:", error);
|
||||||
|
//process.exit(1); // Optionally exit with non-zero status
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("unhandledRejection", (reason, promise) => {
|
||||||
|
Err("Unhandled Rejection at Promise:", promise, "Reason:", reason);
|
||||||
|
// Optionally exit process if desired:
|
||||||
|
// process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
const server = Net.createServer();
|
||||||
|
const discordPacketBuffer = [];
|
||||||
|
const lsPacketBuffer = [];
|
||||||
|
const sockets = [];
|
||||||
|
const MAX_PACKET_BUFFER_SIZE = 1000; // Define constant for buffer size
|
||||||
|
|
||||||
|
// Check time difference
|
||||||
|
const checkTime = (theirTime) => {
|
||||||
|
const myTime = Math.floor(Date.now() / 1000);
|
||||||
|
return Number(theirTime - myTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a hash value for the given packet data using MD5 algorithm.
|
||||||
|
* @param {object} packet - The packet data object containing type, metaData, payload, etc.
|
||||||
|
* @returns {string} - The MD5 hash value of the concatenated packet data.
|
||||||
|
*/
|
||||||
|
const hashPacketData = (packet) => {
|
||||||
|
let base = packet.type;
|
||||||
|
base += packet.metaData.gameTime;
|
||||||
|
base += packet.metaData.server;
|
||||||
|
base += packet.payload.name.replace(/[\n\r]/g, '').trim();
|
||||||
|
base += packet.payload.message.replace(/[\n\r]/g, '').trim();
|
||||||
|
base += packet.metaData.platform;
|
||||||
|
|
||||||
|
if (packet.payload?.linkshellname) {
|
||||||
|
base += packet.payload.linkshellname;
|
||||||
|
} else if (packet.payload?.area) {
|
||||||
|
base += packet.payload.area;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.createHash("md5").update(base).digest("hex");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a packet to the Discord buffer if it does not already exist in the buffer.
|
||||||
|
* If the buffer exceeds 1000 packets, the oldest packet is removed.
|
||||||
|
* @param {Object} packet - The packet to add to the buffer.
|
||||||
|
* @returns {boolean} - Returns true if packet is added, false if it already exists.
|
||||||
|
*/
|
||||||
|
const addToDiscordBuffer = (packet) => {
|
||||||
|
const exists = discordPacketBuffer.some(p => p.hash === packet.hash);
|
||||||
|
if (exists) {
|
||||||
|
Debug(`${packet.hash} exists, skipping...`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug(`New hash created ${packet.hash}`);
|
||||||
|
discordPacketBuffer.push(packet);
|
||||||
|
|
||||||
|
if (discordPacketBuffer.length > MAX_PACKET_BUFFER_SIZE) {
|
||||||
|
discordPacketBuffer.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for handling incoming data on a socket connection.
|
||||||
|
* Parses the incoming data, processes the packets, and sends responses accordingly.
|
||||||
|
* @param {Socket} sock - The socket object representing the connection.
|
||||||
|
*/
|
||||||
|
server.on("connection", (sock) => {
|
||||||
|
Debug("CONNECTED: " + sock.remoteAddress + ":" + sock.remotePort);
|
||||||
|
|
||||||
|
sock.on("data", async (data) => {
|
||||||
|
const response = { error: false };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packets = data.toString().split("\n");
|
||||||
|
for (const packetStr of packets) {
|
||||||
|
if (packetStr) {
|
||||||
|
const packet = JSON.parse(packetStr);
|
||||||
|
const differential = checkTime(packet?.metaData?.clientTime);
|
||||||
|
|
||||||
|
if (differential <= process.env.MAX_CLOCK_SYNC_MISMATCH_SECONDS) {
|
||||||
|
response.type = packet?.type?.toUpperCase();
|
||||||
|
response.packetId = packet?.packetId;
|
||||||
|
|
||||||
|
switch (response.type) {
|
||||||
|
case "HANDSHAKE":
|
||||||
|
const authed = AuthenticateSocket(packet, sock);
|
||||||
|
response.payload = authed ? "ACCEPTED" : "REJECTED";
|
||||||
|
response.error = !authed;
|
||||||
|
response.errorMsg = "AUTH_FAIL";
|
||||||
|
response.errorDetails = "You shall not pass!";
|
||||||
|
response.disconnect = true;
|
||||||
|
Log(`[${sock.remoteAddress}] connection ${response.payload}.`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "HEARTBEAT":
|
||||||
|
response.payload = "PONG";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "LINKSHELL_MESSAGE":
|
||||||
|
packet.hash = hashPacketData(packet);
|
||||||
|
Log(`[${sock.remoteAddress}] LS_MESSAGE`)
|
||||||
|
ProcessLSMessage(packet, sock);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "LINKSHELL_UPDATE":
|
||||||
|
ProcessLSUpdate(packet, sock);
|
||||||
|
Log(`[${sock.remoteAddress}] LS_UPDATE`)
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SHOUT":
|
||||||
|
packet.hash = hashPacketData(packet);
|
||||||
|
addToDiscordBuffer(packet);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "OTHER":
|
||||||
|
// Handle other types
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ADD_LINKSHELL":
|
||||||
|
Log(`[${sock.remoteAddress}] ADD_LS`)
|
||||||
|
await ProcessAddLinkshell(packet, sock);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
response.error = true;
|
||||||
|
response.errorMsg = "UNKNOWN_PACKET_TYPE";
|
||||||
|
response.errorDetails = "UNKNOWN PACKET TYPE";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.error = true;
|
||||||
|
response.errorMsg = "CLOCK_OUT_OF_SYNC";
|
||||||
|
response.errorDetails = `This system and the server clocks are out of sync by ${differential} second(s).`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock) {
|
||||||
|
writeToClientSocket(sock, JSON.stringify(response) + "\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
Debug("Error processing packet", packet);
|
||||||
|
sock.destroy(); // Use destroy instead of resetAndDestroy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace (`trace` level logging is not natively supported)
|
||||||
|
//Debug(`TRACE: TO ${sock.remoteAddress} ${JSON.stringify(response)}`);
|
||||||
|
} catch (ex) {
|
||||||
|
Err("Unexpected packet format, unable to parse.");
|
||||||
|
Debug(ex);
|
||||||
|
Debug("Data:", data.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.on("timeout", () => {
|
||||||
|
removeSocketFromPool(sock);
|
||||||
|
Debug("TIMEOUT: " + sock.remoteAddress + " " + sock.remotePort);
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.on("close", () => {
|
||||||
|
removeSocketFromPool(sock);
|
||||||
|
Debug("CLOSED: " + sock.remoteAddress + " " + sock.remotePort);
|
||||||
|
});
|
||||||
|
|
||||||
|
writeToClientSocket(sock, "CHALLENGE\n");
|
||||||
|
sockets.push(sock);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a socket from the pool.
|
||||||
|
* @param {Socket} sock - The socket to remove.
|
||||||
|
*/
|
||||||
|
const removeSocketFromPool = (sock) => {
|
||||||
|
const index = sockets.findIndex((o) => o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort);
|
||||||
|
if (index !== -1) sockets.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to a client socket with retries.
|
||||||
|
* @param {Socket} socket - The socket to write to.
|
||||||
|
* @param {string} data - The data to write.
|
||||||
|
* @param {number} [retry=0] - The retry count.
|
||||||
|
*/
|
||||||
|
const writeToClientSocket = (socket, data, retry = 0) => {
|
||||||
|
if (!socket?.destroyed && socket?.readyState === 'open') {
|
||||||
|
socket.write(data, (err) => {
|
||||||
|
if (err) {
|
||||||
|
Debug("Failed to write to socket", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (socket?.destroyed) {
|
||||||
|
Debug("The socket is dead and can't be written to");
|
||||||
|
} else if (retry < 5) {
|
||||||
|
Debug(`Retrying to send packet ${data}`);
|
||||||
|
setTimeout(() => writeToClientSocket(socket, data, retry + 1), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a linkshell message packet.
|
||||||
|
* @param {object} packet - The packet data.
|
||||||
|
* @param {Socket} socket - The socket connection.
|
||||||
|
* @returns {boolean} - Returns false if packet exists, true otherwise.
|
||||||
|
*/
|
||||||
|
const ProcessLSMessage = (packet, socket) => {
|
||||||
|
const exists = lsPacketBuffer.some(p => p.hash === packet.hash);
|
||||||
|
if (exists) {
|
||||||
|
Debug(`${packet.hash} exists, skipping...`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug(`New hash created ${packet.hash}`);
|
||||||
|
lsPacketBuffer.push(packet);
|
||||||
|
|
||||||
|
if (lsPacketBuffer.length > MAX_PACKET_BUFFER_SIZE) {
|
||||||
|
lsPacketBuffer.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkshell = ProcessLSUpdate(packet, socket);
|
||||||
|
if (!linkshell?.channels) return false;
|
||||||
|
|
||||||
|
const re = /^(\[.+\] .+)/;
|
||||||
|
if (!packet.payload.message.match(re)) {
|
||||||
|
linkshell.webhookQueue.push({
|
||||||
|
linkshell,
|
||||||
|
packet,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a linkshell update packet.
|
||||||
|
* @param {object} packet - The packet data.
|
||||||
|
* @param {Socket} socket - The socket connection.
|
||||||
|
* @returns {object} - Returns the linkshell object.
|
||||||
|
*/
|
||||||
|
const ProcessLSUpdate = (packet, socket) => {
|
||||||
|
const linkshell = getLSModel(packet.payload?.linkshellname, packet.metaData.platform, packet.metaData.server);
|
||||||
|
|
||||||
|
if (linkshell) {
|
||||||
|
linkshell.socket = socket;
|
||||||
|
Log(`Linkshell "${packet.payload.linkshellname}" Registered By ${packet.metaData.character}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkshell;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a system message to the client.
|
||||||
|
* @param {Socket} socket - The client socket.
|
||||||
|
* @param {string} message - The message to send.
|
||||||
|
* @param {boolean} [error=false] - Whether the message is an error.
|
||||||
|
*/
|
||||||
|
const sendMessageToClient = (socket, message, error = false) => {
|
||||||
|
const packet = {
|
||||||
|
type: "SYSTEM_MESSAGE",
|
||||||
|
payload: {
|
||||||
|
isError: error,
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
writeToClientSocket(socket, JSON.stringify(packet) + "\n\r");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a linkshell echo message to the client.
|
||||||
|
* @param {Socket} socket - The client socket.
|
||||||
|
* @param {string} message - The message to send.
|
||||||
|
* @param {string} from - The sender of the message.
|
||||||
|
* @param {string} ls - The linkshell name.
|
||||||
|
*/
|
||||||
|
const sendLSMessage = (socket, message, from, ls) => {
|
||||||
|
const packet = {
|
||||||
|
type: "LS_ECHO",
|
||||||
|
payload: {
|
||||||
|
from: from.trim(),
|
||||||
|
message: message.trim(),
|
||||||
|
linkshell: ls,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
writeToClientSocket(socket, JSON.stringify(packet) + "\n\r");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates a socket connection.
|
||||||
|
* @param {object} packet - The packet data containing authentication info.
|
||||||
|
* @param {Socket} socket - The socket connection.
|
||||||
|
* @returns {boolean} - Returns true if authentication is successful, false otherwise.
|
||||||
|
*/
|
||||||
|
const AuthenticateSocket = (packet, socket) => {
|
||||||
|
const authId = packet.payload.authId;
|
||||||
|
const user = getUserFromJwt(authId);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
setUserSocket(socket, user.userId);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a request to add a linkshell.
|
||||||
|
* @param {object} packet - The packet data.
|
||||||
|
* @param {Socket} socket - The socket connection.
|
||||||
|
*/
|
||||||
|
const ProcessAddLinkshell = async (packet, socket) => {
|
||||||
|
const linkId = packet.payload.linkId.replace("\r", "").replace("\n", "");
|
||||||
|
const serverId = packet.metaData.server;
|
||||||
|
const lsName = packet.payload.lsName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runPrepQuery("SELECT * FROM pendinglinks WHERE linkId = ? LIMIT 0,1", [linkId]);
|
||||||
|
if (numRows(result)) {
|
||||||
|
const { ffxiver, userId } = result[0];
|
||||||
|
const discordUser = client.users.cache.get(userId);
|
||||||
|
|
||||||
|
Debug({ linkId, serverId, lsName, ffxiver, userId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newLs = await createNewLS(lsName, serverId, ffxiver, userId);
|
||||||
|
|
||||||
|
discordUser.send(
|
||||||
|
`# Success!\nYour Linkshell ${lsName} has been added to LinkCloud!\n\n## What's Next?\n- Set up the chat echo channel in your discord server using the \`/lccreateecho\` command in the channel you want to use for the chat echo.\n- Encourage LS members to use the \`/lcjoin\` command to get started streaming data to LinkCloud. The more streamers you have, the more reliable the echo will be.`
|
||||||
|
);
|
||||||
|
|
||||||
|
Log(`New Linkshell "${lsName}" has been created!`);
|
||||||
|
sendMessageToClient(socket, `Linkshell ${lsName} added successfully to LinkCloud. Check discord for further instruction.`);
|
||||||
|
} catch (error) {
|
||||||
|
Err(`Failed to create Linkshell "${lsName}".`, error);
|
||||||
|
sendMessageToClient(socket, "An error occurred. This is most likely because this Linkshell already exists.", true);
|
||||||
|
|
||||||
|
discordUser.send(
|
||||||
|
`# Uh-oh!\nYour Linkshell ${lsName} seems to already exist in our database.\n\n## Need Help?\nYou can reach out to support via discord.\nhttps://discord.gg/n5VYHSQbhA`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendMessageToClient(socket, "An error occurred. The supplied token is not valid.", true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Err("Database query failed during ProcessAddLinkshell.", error);
|
||||||
|
sendMessageToClient(socket, "An error occurred during linkshell processing. Please try again later.", true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for new Discord echo received.
|
||||||
|
*/
|
||||||
|
event.on('NEW_DISCORD_ECHO_RECEIVED', (message) => {
|
||||||
|
const linkshell = getLSModel(message.lsName, message.platform, message.server);
|
||||||
|
Log(`[${message.lsName} : ${message.server}] NEW_DISCORD_MESSAGE`)
|
||||||
|
if (linkshell?.socket) {
|
||||||
|
sendLSMessage(linkshell.socket, message.message, message.from, message.lsName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server listening
|
||||||
|
server.listen(process.env.TCP_LISTEN_PORT, () => {
|
||||||
|
Log(`Server Bound to ${process.env.BIND_ADDRESS} on port ${process.env.TCP_LISTEN_PORT}.`);
|
||||||
|
});
|
57
server/webserver.js
Normal file
57
server/webserver.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import AdmZip from 'adm-zip'
|
||||||
|
import { runPrepQuery, numRows } from './Utility/db.js'
|
||||||
|
const app = express()
|
||||||
|
const port = 3000
|
||||||
|
import { json2xml } from "xml-js";
|
||||||
|
import { BlobReader, BlobWriter, TextReader, TextWriter, ZipReader, ZipWriter } from "@zip.js/zip.js";
|
||||||
|
const saveFile = {
|
||||||
|
settings: {
|
||||||
|
global: {
|
||||||
|
AuthKey: "NONE",
|
||||||
|
AutoConnect: true,
|
||||||
|
AutoReconnect: true,
|
||||||
|
HostAddress: "linkcloud.drunken.games",
|
||||||
|
HostPort: 5050,
|
||||||
|
ProcessShouts: true,
|
||||||
|
TCPTimeout: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
compact: true,
|
||||||
|
ignoreComment: true,
|
||||||
|
spaces: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.get('/addons/windower/retail/:id', async (req, res) => {
|
||||||
|
saveFile.settings.global.AuthKey = await getTokenFromId(req.params.id);
|
||||||
|
const xmlString = `<?xml version="1.1" ?>\n${json2xml(saveFile, options)}`;
|
||||||
|
const existingZip = new AdmZip('./resources/addon/LinkCloud.zip');
|
||||||
|
existingZip.addFile("LinkCloud/data/settings.xml", Buffer.from(xmlString, "utf8"), "Auto Generated");
|
||||||
|
const data = existingZip.toBuffer()
|
||||||
|
res.set('Content-Type', 'application/zip');
|
||||||
|
res.set('Content-Disposition', 'attachment; filename=LinkCloud_Latest.zip');
|
||||||
|
res.set('Content-Length', data.length);
|
||||||
|
res.send(data);
|
||||||
|
})
|
||||||
|
export const listen = () => {
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Example app listening on port ${port}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getTokenFromId = (id) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
runPrepQuery("SELECT * FROM users WHERE userId = ? LIMIT 0,1", [id], (r,f,e) => {
|
||||||
|
if(!e) {
|
||||||
|
if(numRows(r)) {
|
||||||
|
resolve(r[0].authToken)
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user