diff --git a/lua/autorun/gmod_integration.lua b/lua/autorun/gmod_integration.lua index 558500a..e5ea6ee 100644 --- a/lua/autorun/gmod_integration.lua +++ b/lua/autorun/gmod_integration.lua @@ -1,66 +1,67 @@ -if game.SinglePlayer() then return end +if (game.SinglePlayer()) then return print("Gmod Integration is not supported in Singleplayer!") end // // Variables // -gmInte = gmInte or {} -gmInte.version = "0.2.3" -gmInte.config = { - ["redownloadMaterials"] = false, -} +gmInte = gmInte || {} + +gmInte.version = "0.3.0" +gmInte.config = {} gmInte.materials = {} // // Functions // -local function loadConfig() - if (SERVER) then - print(" | Loading File | gmod_integration/sv_config.lua") - RunConsoleCommand("sv_hibernate_think", "1") - if (!file.Exists("gm_integration", "DATA") || !file.Exists("gm_integration/config.json", "DATA")) then - file.CreateDir("gm_integration") +local function loadServerConfig() + RunConsoleCommand("sv_hibernate_think", "1") + + if (!file.Exists("gm_integration", "DATA") || !file.Exists("gm_integration/config.json", "DATA")) then + file.CreateDir("gm_integration") + file.Write("gm_integration/config.json", util.TableToJSON(gmInte.config, true)) + else + if (gmInte.config.id && gmInte.config.id != "") then return end + + local oldConfig = util.JSONToTable(file.Read("gm_integration/config.json", "DATA")) + if (!oldConfig.version || (oldConfig.version < gmInte.version)) then + print(" | Merging Config | gmod_integration/sv_config.lua") + table.Merge(gmInte.config, oldConfig) + gmInte.config.version = gmInte.version file.Write("gm_integration/config.json", util.TableToJSON(gmInte.config, true)) else - if (gmInte.config.id && gmInte.config.id != "") then return end - - local oldConfig = util.JSONToTable(file.Read("gm_integration/config.json", "DATA")) - if (!oldConfig.version || (oldConfig.version < gmInte.version)) then - print(" | Merging Config | gmod_integration/sv_config.lua") - table.Merge(gmInte.config, oldConfig) - gmInte.config.version = gmInte.version - file.Write("gm_integration/config.json", util.TableToJSON(gmInte.config, true)) - else - gmInte.config = oldConfig - end + gmInte.config = oldConfig end end end local function loadAllFiles(folder) local files, folders = file.Find(folder .. "/*", "LUA") - for k, v in SortedPairs(files) do - local path = folder .. "/" .. v + + for k, fileName in SortedPairs(files) do + local path = folder .. "/" .. fileName print(" | Loading File | " .. path) - if string.StartWith(v, "cl_") then + + if (string.StartWith(fileName, "cl_")) then if SERVER then AddCSLuaFile(path) else include(path) end - elseif string.StartWith(v, "sv_") then + elseif (string.StartWith(fileName, "sv_")) then if SERVER then include(path) end - elseif string.StartWith(v, "sh_") then + elseif (string.StartWith(fileName, "sh_")) then if SERVER then AddCSLuaFile(path) end include(path) end - if (path == "gmod_integration/sv_config.lua") then loadConfig() continue end + + if (fileName == "sv_config.lua") then loadServerConfig() continue end end + for k, v in SortedPairs(folders, true) do loadAllFiles(folder .. "/" .. v, name) end diff --git a/lua/gmod_integration/client/cl__color.lua b/lua/gmod_integration/client/cl__color.lua new file mode 100644 index 0000000..d29eaf2 --- /dev/null +++ b/lua/gmod_integration/client/cl__color.lua @@ -0,0 +1,23 @@ +local colorTbl = { + ["background"] = Color(41, 44, 54), + ["primary"] = Color(58, 62, 73), + ["primary-active"] = Color(58, 62, 73, 163), + ["secondary"] = Color(44, 47, 59), + ["secondary-active"] = Color(31, 33, 40), + ["green"] = Color(78, 151, 53), + ["green-active"] = Color(58, 122, 38), + ["orange"] = Color(204, 145, 62), + ["orange-active"] = Color(168, 122, 43), + ["red"] = Color(201, 59, 59), + ["red-active"] = Color(168, 43, 43), + ["blue"] = Color(67, 197, 214), + ["blue-active"] = Color(41, 152, 167), + ["purple"] = Color(73, 90, 252), + ["purple-active"] = Color(47, 63, 159), + ["font"] = Color(255, 255, 255), + ["font-secondary"] = Color(179, 179, 179) +} + +function gmInte.getColor(name) + return colorTbl[name] +end \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_context_menu.lua b/lua/gmod_integration/client/cl_context_menu.lua new file mode 100644 index 0000000..17c337e --- /dev/null +++ b/lua/gmod_integration/client/cl_context_menu.lua @@ -0,0 +1,12 @@ +list.Set("DesktopWindows", "GmodIntegration:DesktopWindows", { + icon = "gmod_integration/logo_context.png", + title = "GM Integration", + width = 960, + height = 700, + onewindow = true, + init = function(icon, window) + window:Close() + gmInte.openAdminConfig() + end + } +) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_font.lua b/lua/gmod_integration/client/cl_font.lua index 7655a1c..eba3bea 100644 --- a/lua/gmod_integration/client/cl_font.lua +++ b/lua/gmod_integration/client/cl_font.lua @@ -1,7 +1,7 @@ surface.CreateFont("GmodIntegration_Roboto_16", { font = "Roboto", size = 16, - weight = 500, + weight = 100, antialias = true, shadow = false }) diff --git a/lua/gmod_integration/client/cl_gui.lua b/lua/gmod_integration/client/cl_gui.lua deleted file mode 100644 index 638e24d..0000000 --- a/lua/gmod_integration/client/cl_gui.lua +++ /dev/null @@ -1,436 +0,0 @@ -local function saveConfig(setting, value) - gmInte.SendNet(3, { - [setting] = value - }) -end - -local configCat = { - "Authentication", - "Main", - "Trust & Safety", - "Punishment", - "Other" -} - -local possibleConfig = { - ["id"] = { - ["label"] = "ID", - ["description"] = "Server ID found on the webpanel.", - ["type"] = "textEntry", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value) - end, - ["onEditDelay"] = 0.5, - ["category"] = "Authentication" - }, - ["token"] = { - ["label"] = "Token", - ["description"] = "Server Token found on the webpanel.", - ["type"] = "textEntry", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value) - end, - ["onEditDelay"] = 0.5, - ["category"] = "Authentication" - }, - ["sendLog"] = { - ["label"] = "Logs", - ["description"] = "Activate or deactivate logs.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Main" - }, - ["logBotActions"] = { - ["label"] = "Log Bot Actions", - ["description"] = "Activate or deactivate logs for bot actions.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Main" - }, - ["filterOnBan"] = { - ["label"] = "Block Discord Ban Player", - ["description"] = "Block players banned on the discord server.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Trust & Safety" - }, - ["filterOnTrust"] = { - ["label"] = "Block UnTrust Player", - ["description"] = "Block players with a trust level lower than the minimal trust level set in the config.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Trust & Safety" - }, - ["minimalTrust"] = { - ["label"] = "Minimal Trust Level", - ["description"] = "The minimal trust level to be able to join the server.", - ["type"] = "textEntry", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value) - end, - ["onEditDelay"] = 0.5, - ["category"] = "Trust & Safety" - }, - ["syncChat"] = { - ["label"] = "Sync Chat", - ["description"] = "Sync chat between the server and the discord server.", - ["websocket"] = true, - ["restart"] = true, - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Main" - }, - ["syncBan"] = { - ["label"] = "Sync Ban", - ["description"] = "Sync chat between the server and the discord server.", - ["type"] = "checkbox", - ["disable"] = true, - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Punishment" - }, - ["syncTimeout"] = { - ["label"] = "Sync Timeout", - ["description"] = "Sync chat between the server and the discord server.", - ["type"] = "checkbox", - ["disable"] = true, - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Punishment" - }, - ["syncKick"] = { - ["label"] = "Sync Kick", - ["description"] = "Sync chat between the server and the discord server.", - ["type"] = "checkbox", - ["disable"] = true, - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Punishment" - }, - ["forcePlayerLink"] = { - ["label"] = "Force Player Verif", - ["description"] = "Sync chat between the server and the discord server.", - ["type"] = "checkbox", - ["disable"] = true, - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Main" - }, - ["supportLink"] = { - ["label"] = "Support Link", - ["description"] = "Server ID found on the webpanel.", - ["type"] = "textEntry", - ["disable"] = true, - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value) - end, - ["onEditDelay"] = 0.5, - ["category"] = "Trust & Safety" - }, - ["debug"] = { - ["label"] = "Debug", - ["description"] = "Activate or deactivate debug mode.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Other" - }, - ['devInstance'] = { - ["label"] = "Dev Instance", - ["description"] = "Activate or deactivate the dev instance of the API and Websocket.", - ["type"] = "checkbox", - ["value"] = function(setting, value) - return value - end, - ["onEdit"] = function(setting, value) - saveConfig(setting, value == "Enabled" && true || false) - end, - ["category"] = "Other" - } -} - -local buttonsInfo = { - { - ["label"] = "Open Webpanel", - ["func"] = function() - gui.OpenURL("https://gmod-integration.com/config/server") - end, - }, - { - ["label"] = "Test Connection", - ["func"] = function() - gmInte.SendNet(1) - end, - }, - { - ["label"] = "Buy Premium", - ["func"] = function() - gui.OpenURL("https://gmod-integration.com/premium") - end, - }, - { - ["label"] = "Install Websocket", - ["condition"] = function(data) - return !data.websocket - end, - ["func"] = function() - gui.OpenURL("https://github.com/FredyH/GWSockets/releases") - end, - }, - { - ["label"] = "Load Server Config", - ["condition"] = function(data) - return data.debug - end, - ["func"] = function(data) - gmInte.config = data - end, - } -} - -function gmInte.needRestart() - local frame = vgui.Create("DFrame") - frame:SetSize(400, 120) - frame:Center() - frame:SetTitle("Gmod Integration - Restart Required") - frame:SetDraggable(true) - frame:ShowCloseButton(true) - frame:MakePopup() - - local messagePanel = vgui.Create("DPanel", frame) - messagePanel:Dock(TOP) - messagePanel:SetSize(300, 40) - messagePanel:DockMargin(10, 0, 10, 10) - messagePanel:SetBackgroundColor(Color(0, 0, 0, 0)) - - local messageLabel = vgui.Create("DLabel", messagePanel) - messageLabel:Dock(FILL) - messageLabel:SetText("Some changes require a restart to be applied.\nRestart now ?") - messageLabel:SetContentAlignment(5) - messageLabel:SetWrap(true) - - local buttonGrid = vgui.Create("DGrid", frame) - buttonGrid:Dock(BOTTOM) - buttonGrid:DockMargin(5, 10, 5, 5) - buttonGrid:SetCols(2) - buttonGrid:SetColWide(frame:GetWide() / 2 - 10) - buttonGrid:SetRowHeight(35) - - local button = vgui.Create("DButton") - button:SetText("Restart") - button.DoClick = function() - frame:Close() - gmInte.SendNet(5) - end - button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) - buttonGrid:AddItem(button) - - local button = vgui.Create("DButton") - button:SetText("Maybe Later") - button.DoClick = function() - frame:Close() - end - button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) - buttonGrid:AddItem(button) -end - -function gmInte.openConfigMenu(data) - local needRestart = false - - local frame = vgui.Create("DFrame") - frame:SetSize(400, (600 / 1080) * ScrH()) - frame:Center() - frame:SetTitle("Gmod Integration - Server Config") - frame:SetDraggable(true) - frame:ShowCloseButton(true) - frame:MakePopup() - - local scrollPanel = vgui.Create("DScrollPanel", frame) - scrollPanel:Dock(FILL) - - local messagePanel = vgui.Create("DPanel", scrollPanel) - messagePanel:Dock(TOP) - messagePanel:SetSize(300, 60) - messagePanel:DockMargin(10, 0, 10, 10) - messagePanel:SetBackgroundColor(Color(0, 0, 0, 0)) - - local messageLabel = vgui.Create("DLabel", messagePanel) - messageLabel:Dock(FILL) - messageLabel:SetText("This config is superior to the webpanel config.\nIf you change something here you can override the webpanel config.\nSome features require a websocket connection to work properly.") - messageLabel:SetWrap(true) - - for k, catName in pairs(configCat) do - local collapsibleCategory = vgui.Create("DCollapsibleCategory", scrollPanel) - collapsibleCategory:Dock(TOP) - collapsibleCategory:DockMargin(10, 0, 10, 10) - collapsibleCategory:SetLabel(catName) - collapsibleCategory:SetExpanded(true) - - local configList = vgui.Create("DPanelList", collapsibleCategory) - configList:Dock(FILL) - configList:SetSpacing(5) - configList:EnableHorizontal(false) - configList:EnableVerticalScrollbar(false) - collapsibleCategory:SetContents(configList) - - for k, v in pairs(possibleConfig) do - if v.category == catName then - local panel = vgui.Create("DPanel", configList) - panel:Dock(TOP) - panel:SetSize(300, 25) - panel:SetBackgroundColor(Color(0, 0, 0, 0)) - - local label = vgui.Create("DLabel", panel) - label:Dock(LEFT) - label:SetSize(140, 25) - label:SetText(v.label) - label:SetContentAlignment(4) - - local input - - if v.type == "textEntry" then - input = vgui.Create("DTextEntry", panel) - input:SetText(v.value(k, data[k])) - local isLastID = 0 - input.OnChange = function(self) - isLastID = isLastID + 1 - local isLocalLastID = isLastID - timer.Simple(v.onEditDelay || 0.5, function() - if isLocalLastID == isLastID then - v.onEdit(k, self:GetValue()) - end - end) - end - elseif (v.type == "checkbox") then - input = vgui.Create("DComboBox", panel) - if (v.disable) then - input:SetEnabled(false) - end - input:AddChoice("Enabled") - input:AddChoice("Disabled") - input:SetText(v.value(k, data[k]) && "Enabled" || "Disabled") - input.OnSelect = function(self, index, value) - if (v.restart) then - needRestart = true - end - v.onEdit(k, value) - end - end - - input:Dock(FILL) - input:SetSize(150, 25) - - if (v.description) then - if (v.websocket && !data.websocket) then - v.description = v.description .. "\n\nThis feature require a websocket connection to work properly." - end - if (v.disable) then - v.description = v.description .. "\n\nThis feature will be available soon." - end - input:SetTooltip(v.description) - end - - configList:AddItem(panel) - end - end - end - - local buttonGrid = vgui.Create("DGrid", frame) - buttonGrid:Dock(BOTTOM) - buttonGrid:DockMargin(5, 10, 5, 5) - buttonGrid:SetCols(2) - buttonGrid:SetColWide(frame:GetWide() / 2 - 10) - buttonGrid:SetRowHeight(35) - - local buttonsCount = 0 - for k, v in pairs(buttonsInfo) do - if (v.condition && !v.condition(data)) then continue end - local button = vgui.Create("DButton") - button:SetText(v.label) - button.DoClick = function() - v.func(data) - end - button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) - buttonGrid:AddItem(button) - buttonsCount = buttonsCount + 1 - end - - if (buttonsCount % 2 == 1) then - local lastButton = buttonGrid:GetItems()[buttonsCount] - lastButton:SetWide(frame:GetWide() - 20) - end - - frame.OnClose = function() - if (needRestart) then gmInte.needRestart() end - end -end - -list.Set("DesktopWindows", "GmodIntegration:DesktopWindows", { - icon = "gmod_integration/logo_context.png", - title = "GM Integration", - width = 960, - height = 700, - onewindow = true, - init = function(icon, window) - window:Close() - gmInte.openAdminConfig() - end - } -) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_gui_admin.lua b/lua/gmod_integration/client/cl_gui_admin.lua new file mode 100644 index 0000000..132107e --- /dev/null +++ b/lua/gmod_integration/client/cl_gui_admin.lua @@ -0,0 +1,490 @@ +local function saveConfig(setting, value) + gmInte.SendNet("saveConfig", { + [setting] = value + }) +end + +local configCat = { + "Authentication", + "Main", + "Trust & Safety", + -- "Punishment", + "Advanced", +} + +local possibleConfig = { + { + ["id"] = "id", + ["label"] = "Server ID", + ["description"] = "Server ID found on the webpanel.", + ["type"] = "textEntry", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value) + end, + ["onEditDelay"] = 0.5, + ["category"] = "Authentication" + }, + { + ["id"]= "token", + ["label"] = "Server Token", + ["description"] = "Server Token found on the webpanel.", + ["type"] = "textEntry", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value) + end, + ["onEditDelay"] = 0.5, + ["category"] = "Authentication" + }, + -- { + -- ["id"]= "sendLog", + -- ["label"] = "Logs", + -- ["description"] = "Activate or deactivate logs.", + -- ["type"] = "checkbox", + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Main" + -- }, + -- { + -- ["id"]= "logBotActions", + -- ["label"] = "Log Bot Actions", + -- ["description"] = "Activate or deactivate logs for bot actions.", + -- ["type"] = "checkbox", + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Main" + -- }, + { + ["id"]= "maintenance", + ["label"] = "Maintenance", + ["description"] = "Activate or deactivate maintenance mode.", + ["type"] = "checkbox", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value == "Enabled" && true || false) + end, + ["category"] = "Main" + }, + { + ["id"]= "filterOnBan", + ["label"] = "Block Discord Ban Player", + ["description"] = "Block players banned on the discord server.", + ["type"] = "checkbox", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value == "Enabled" && true || false) + end, + ["category"] = "Trust & Safety" + }, + -- { + -- ["id"]= "filterOnTrust", + -- ["label"] = "Block UnTrust Player", + -- ["description"] = "Block players with a trust level lower than the minimal trust level set in the config.", + -- ["type"] = "checkbox", + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Trust & Safety" + -- }, + -- { + -- ["id"]= "minimalTrust", + -- ["label"] = "Minimal Trust Level", + -- ["description"] = "The minimal trust level to be able to join the server.", + -- ["type"] = "textEntry", + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value) + -- end, + -- ["onEditDelay"] = 0.5, + -- ["category"] = "Trust & Safety" + -- }, + -- { + -- ["id"]= "syncChat", + -- ["label"] = "Sync Chat", + -- ["description"] = "Sync chat between the server and the discord server.", + -- ["websocket"] = true, + -- ["restart"] = true, + -- ["type"] = "checkbox", + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Main" + -- }, + -- { + -- ["id"]= "syncBan", + -- ["label"] = "Sync Ban", + -- ["description"] = "Sync chat between the server and the discord server.", + -- ["type"] = "checkbox", + -- ["condition"] = function(data) + -- return false // Disabled for now + -- end, + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Punishment" + -- }, + -- { + -- ["id"]= "syncTimeout", + -- ["label"] = "Sync Timeout", + -- ["description"] = "Sync chat between the server and the discord server.", + -- ["type"] = "checkbox", + -- ["condition"] = function(data) + -- return false // Disabled for now + -- end, + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Punishment" + -- }, + -- { + -- ["id"]= "syncKick", + -- ["label"] = "Sync Kick", + -- ["description"] = "Sync chat between the server and the discord server.", + -- ["type"] = "checkbox", + -- ["condition"] = function(data) + -- return false // Disabled for now + -- end, + -- ["value"] = function(setting, value) + -- return value + -- end, + -- ["onEdit"] = function(setting, value) + -- saveConfig(setting, value == "Enabled" && true || false) + -- end, + -- ["category"] = "Punishment" + -- }, + { + ["id"]= "forcePlayerLink", + ["label"] = "Force Player Verif", + ["description"] = "Sync chat between the server and the discord server.", + ["type"] = "checkbox", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value == "Enabled" && true || false) + end, + ["category"] = "Main" + }, + { + ["id"]= "supportLink", + ["label"] = "Support Link", + ["description"] = "Server ID found on the webpanel.", + ["type"] = "textEntry", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value) + end, + ["onEditDelay"] = 0.5, + ["category"] = "Trust & Safety" + }, + { + ["id"]= "debug", + ["label"] = "Debug", + ["description"] = "Activate or deactivate debug mode.", + ["type"] = "checkbox", + ["value"] = function(setting, value) + return value + end, + ["position"] = 1, + ["onEdit"] = function(setting, value) + saveConfig(setting, value == "Enabled" && true || false) + end, + ["category"] = "Advanced" + }, + { + ["id"]= "websocketFQDN", + ["label"] = "Websocket FQDN", + ["description"] = "Websocket FQDN that will be used for the Websocket connection.", + ["type"] = "textEntry", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value) + end, + ["onEditDelay"] = 0.5, + ["category"] = "Advanced" + }, + { + ["id"]= "apiFQDN", + ["label"] = "API FQDN", + ["description"] = "API FQDN that will be used for the API connection.", + ["type"] = "textEntry", + ["value"] = function(setting, value) + return value + end, + ["onEdit"] = function(setting, value) + saveConfig(setting, value) + end, + ["onEditDelay"] = 0.5, + ["category"] = "Advanced" + }, +} + +local buttonsInfo = { + { + ["label"] = "Open Webpanel", + ["func"] = function() + gui.OpenURL("https://gmod-integration.com/config/server") + end, + }, + { + ["label"] = "Test Connection", + ["func"] = function() + gmInte.SendNet("testConnection") + end, + }, + { + ["label"] = "Buy Premium", + ["func"] = function() + gui.OpenURL("https://gmod-integration.com/premium") + end, + }, + { + ["label"] = "Install Websocket", + ["condition"] = function(data) + return !data.websocket + end, + ["func"] = function() + gui.OpenURL("https://github.com/FredyH/GWSockets/releases") + end, + }, + { + ["label"] = "Load Server Config", + ["condition"] = function(data) + return data.debug + end, + ["func"] = function(data) + gmInte.config = data + end, + } +} + +local colorTable = { + ["text"] = Color(255, 255, 255, 255), + ["background"] = Color(0, 0, 0, 200), + ["button"] = Color(0, 0, 0, 200), + ["buttonHover"] = Color(0, 0, 0, 255), + ["buttonText"] = Color(255, 255, 255, 255), + ["buttonTextHover"] = Color(255, 255, 255, 255), +} + +function gmInte.needRestart() + local frame = vgui.Create("DFrame") + frame:SetSize(400, 120) + frame:Center() + frame:SetTitle("Gmod Integration - Restart Required") + frame:SetDraggable(true) + frame:ShowCloseButton(true) + frame:MakePopup() + + local messagePanel = vgui.Create("DPanel", frame) + messagePanel:Dock(TOP) + messagePanel:SetSize(300, 40) + messagePanel:DockMargin(10, 0, 10, 10) + messagePanel:SetBackgroundColor(Color(0, 0, 0, 0)) + + local messageLabel = vgui.Create("DLabel", messagePanel) + messageLabel:Dock(FILL) + messageLabel:SetText("Some changes require a restart to be applied.\nRestart now ?") + messageLabel:SetContentAlignment(5) + messageLabel:SetWrap(true) + + local buttonGrid = vgui.Create("DGrid", frame) + buttonGrid:Dock(BOTTOM) + buttonGrid:DockMargin(5, 10, 5, 5) + buttonGrid:SetCols(2) + buttonGrid:SetColWide(frame:GetWide() / 2 - 10) + buttonGrid:SetRowHeight(35) + + local button = vgui.Create("DButton") + button:SetText("Restart") + button.DoClick = function() + frame:Close() + gmInte.SendNet("restartMap") + end + button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) + buttonGrid:AddItem(button) + + local button = vgui.Create("DButton") + button:SetText("Maybe Later") + button.DoClick = function() + frame:Close() + end + button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) + buttonGrid:AddItem(button) +end + +function gmInte.openConfigMenu(data) + local needRestart = false + + local frame = vgui.Create("DFrame") + frame:SetSize(400, (600 / 1080) * ScrH()) + frame:Center() + frame:SetTitle("Gmod Integration - Server Config") + frame:SetDraggable(true) + frame:ShowCloseButton(true) + frame:MakePopup() + + local scrollPanel = vgui.Create("DScrollPanel", frame) + scrollPanel:Dock(FILL) + + local messagePanel = vgui.Create("DPanel", scrollPanel) + messagePanel:Dock(TOP) + messagePanel:SetSize(300, 60) + messagePanel:DockMargin(10, 0, 10, 10) + messagePanel:SetBackgroundColor(Color(0, 0, 0, 0)) + + local messageLabel = vgui.Create("DLabel", messagePanel) + messageLabel:Dock(FILL) + messageLabel:SetText("This config is superior to the webpanel config.\nIf you change something here you can override the webpanel config.\nSome features require a websocket connection to work properly.") + messageLabel:SetWrap(true) + + for k, catName in pairs(configCat) do + local collapsibleCategory = vgui.Create("DCollapsibleCategory", scrollPanel) + collapsibleCategory:Dock(TOP) + collapsibleCategory:DockMargin(10, 0, 10, 10) + collapsibleCategory:SetLabel(catName) + collapsibleCategory:SetExpanded(true) + + local configList = vgui.Create("DPanelList", collapsibleCategory) + configList:Dock(FILL) + configList:SetSpacing(5) + configList:EnableHorizontal(false) + configList:EnableVerticalScrollbar(false) + collapsibleCategory:SetContents(configList) + + local categoryConfig = {} + for k, v in pairs(possibleConfig) do + if v.category == catName then + table.insert(categoryConfig, v) + end + end + + // Sort by position + table.sort(categoryConfig, function(a, b) + return (a.position || 0) < (b.position || 0) + end) + + for k, actualConfig in pairs(categoryConfig) do + local panel = vgui.Create("DPanel", configList) + panel:Dock(TOP) + panel:SetSize(300, 25) + panel:SetBackgroundColor(Color(0, 0, 0, 0)) + + local label = vgui.Create("DLabel", panel) + label:Dock(LEFT) + label:SetSize(140, 25) + label:SetText(actualConfig.label) + label:SetContentAlignment(4) + + local input + + if actualConfig.type == "textEntry" then + input = vgui.Create("DTextEntry", panel) + input:SetText(actualConfig.value(actualConfig.id, data[actualConfig.id] || "")) + local isLastID = 0 + input.OnChange = function(self) + isLastID = isLastID + 1 + local isLocalLastID = isLastID + timer.Simple(actualConfig.onEditDelay || 0.5, function() + if isLocalLastID == isLastID then + actualConfig.onEdit(actualConfig.id, self:GetValue()) + end + end) + end + elseif (actualConfig.type == "checkbox") then + input = vgui.Create("DComboBox", panel) + if (actualConfig.condition && !actualConfig.condition(data)) then + input:SetEnabled(false) + end + input:AddChoice("Enabled") + input:AddChoice("Disabled") + input:SetText(actualConfig.value(actualConfig.id, data[actualConfig.id]) && "Enabled" || "Disabled") + input.OnSelect = function(self, index, value) + if (actualConfig.restart) then + needRestart = true + end + actualConfig.onEdit(actualConfig.id, value) + end + end + + input:Dock(FILL) + input:SetSize(150, 25) + + if (actualConfig.description) then + if (actualConfig.websocket && !data.websocket) then + actualConfig.description = actualConfig.description .. "\n\nThis feature require a websocket connection to work properly." + end + if (actualConfig.disable) then + actualConfig.description = actualConfig.description .. "\n\nThis feature will be available soon." + end + input:SetTooltip(actualConfig.description) + end + + configList:AddItem(panel) + end + end + + local buttonGrid = vgui.Create("DGrid", frame) + buttonGrid:Dock(BOTTOM) + buttonGrid:DockMargin(5, 10, 5, 5) + buttonGrid:SetCols(2) + buttonGrid:SetColWide(frame:GetWide() / 2 - 10) + buttonGrid:SetRowHeight(35) + + local buttonsCount = 0 + for k, v in pairs(buttonsInfo) do + if (v.condition && !v.condition(data)) then continue end + local button = vgui.Create("DButton") + button:SetText(v.label) + button.DoClick = function() + v.func(data) + end + button:SetSize(buttonGrid:GetColWide(), buttonGrid:GetRowHeight()) + buttonGrid:AddItem(button) + buttonsCount = buttonsCount + 1 + end + + if (buttonsCount % 2 == 1) then + local lastButton = buttonGrid:GetItems()[buttonsCount] + lastButton:SetWide(frame:GetWide() - 20) + end + + frame.OnClose = function() + if (needRestart) then gmInte.needRestart() end + end +end \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_gui_link.lua b/lua/gmod_integration/client/cl_gui_link.lua new file mode 100644 index 0000000..968e140 --- /dev/null +++ b/lua/gmod_integration/client/cl_gui_link.lua @@ -0,0 +1,65 @@ +function gmInte.openVerifPopup() + local frame = vgui.Create("DFrame") + frame:SetSize(400, 200) + frame:Center() + frame:SetTitle("Gmod Integration - Verification Required") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(self, w, h) + draw.RoundedBox(8, 0, 0, w, h, gmInte.getColor("background")) + end + + local messageLabel = vgui.Create("DLabel", frame) + messageLabel:Dock(FILL) + messageLabel:DockMargin(10, 0, 10, 0) + messageLabel:SetText("Hey,\nIt looks like you haven't linked your Steam account to Discord yet. This is required to play on this server. Please click the button below to link your account.\n\nAfter you've done that, click the refresh button.") + messageLabel:SetContentAlignment(5) + messageLabel:SetFont("GmodIntegration_Roboto_16") + messageLabel:SetWrap(true) + + local buttonGrid = vgui.Create("DGrid", frame) + buttonGrid:Dock(BOTTOM) + buttonGrid:DockMargin(10, 0, 10, 10) + buttonGrid:SetCols(2) + buttonGrid:SetColWide(frame:GetWide() / 2 - 10) + buttonGrid:SetRowHeight(35) + + local button = vgui.Create("DButton") + button:SetText("Open Verification Page") + button.DoClick = function() + gui.OpenURL("https://verif.gmod-integration.com") + end + button:SetSize(buttonGrid:GetColWide() - 10, buttonGrid:GetRowHeight()) + buttonGrid:AddItem(button) + button:SetTextColor(Color(255, 255, 255)) + button.Paint = function(self, w, h) + local color = gmInte.getColor("primary") + if self:IsHovered() then + color = gmInte.getColor("primary-active") + end + draw.RoundedBox(8, 0, 0, w, h, color) + end + + local button = vgui.Create("DButton") + button:SetText("Refresh Verification") + button.DoClick = function() + gmInte.http.get("/users/" .. LocalPlayer():SteamID64(), function(code, body) + gmInte.SendNet("verifyMe") + frame:Close() + end, + function(err) + LocalPlayer():ChatPrint("Failed to refresh verification: " .. err) + end) + end + button:SetSize(buttonGrid:GetColWide() - 10, buttonGrid:GetRowHeight()) + buttonGrid:AddItem(button) + button:SetTextColor(Color(255, 255, 255)) + button.Paint = function(self, w, h) + local color = gmInte.getColor("primary") + if self:IsHovered() then + color = gmInte.getColor("primary-active") + end + draw.RoundedBox(8, 0, 0, w, h, color) + end +end \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_hook.lua b/lua/gmod_integration/client/cl_hook.lua index 86d960b..abb5fb1 100644 --- a/lua/gmod_integration/client/cl_hook.lua +++ b/lua/gmod_integration/client/cl_hook.lua @@ -4,7 +4,7 @@ // Player Finish Init hook.Add("InitPostEntity", "gmInte:Ply:Ready", function() - gmInte.SendNet(0) + gmInte.SendNet("ready") end) hook.Add("OnPlayerChat", "gmInte:OnPlayerChat:AdminCmd", function(ply, strText, bTeamOnly, bPlayerIsDead) @@ -12,7 +12,7 @@ hook.Add("OnPlayerChat", "gmInte:OnPlayerChat:AdminCmd", function(ply, strText, strText = string.lower(strText) - if (strText == "/gminte") then + if (strText == "/gmi") then gmInte.openAdminConfig() return true end diff --git a/lua/gmod_integration/client/cl_main.lua b/lua/gmod_integration/client/cl_main.lua index aea0a93..8d9fab2 100644 --- a/lua/gmod_integration/client/cl_main.lua +++ b/lua/gmod_integration/client/cl_main.lua @@ -2,18 +2,13 @@ // Main // -local function formatName(name) - // all un down case - name = string.lower(name) - // first leter in upper case - name = string.upper(string.sub(name, 1, 1)) .. string.sub(name, 2) - // every letter after a space in upper case - name = string.gsub(name, "(%a)([%w_']*)", function(a,b) return string.upper(a) .. string.lower(b) end) - return name -end - -function gmInte.discordSyncChatPly(data) - chat.AddText(Color(92, 105, 255), "(DISCORD) ", Color(12, 151, 12), formatName(data.name) .. ": ", Color(255, 255, 255), data.content) +function gmInte.chatAddText(data) + local args = {} + for _, v in ipairs(data) do + table.insert(args, v.color) + table.insert(args, v.text) + end + chat.AddText(unpack(args)) end function gmInte.showTestConnection(data) @@ -30,70 +25,11 @@ function gmInte.openAdminConfig() return end - gmInte.SendNet(2) -end - -local ScreenshotRequested = false -hook.Add("PostRender", "gmInteScreenshot", function() - if (!ScreenshotRequested) then return end - ScreenshotRequested = false - - local captureData = { - format = "png", - x = 0, - y = 0, - w = ScrW(), - h = ScrH() - } - - local screenCapture = render.Capture(captureData) - screenCapture = util.Base64Encode(screenCapture) - gmInte.log("Screenshot Taken - " .. string.len(#screenCapture / 1024) .. "KB", true) - - gmInte.post("/player/screenshots", - { - ["steamID64"] = LocalPlayer():SteamID64(), - ["screenshot"] = screenCapture, - ["options"] = captureData, - ["name"] = LocalPlayer():Nick() - }, - function(code, body) - gmInte.log("Screenshot sent to Discord", true) - end, - function(code, body) - gmInte.log("Screenshot failed to send to Discord, error code: " .. code, true) - end - ) -end) - -function gmInte.takeScreenShot(serverID, authToken) - gmInte.config.id = serverID - gmInte.config.token = authToken - - timer.Simple(0.2, function() - ScreenshotRequested = true - end) + gmInte.SendNet("getConfig") end // // Concommands // -concommand.Add("gmod_integration_admin", gmInte.openAdminConfig) -concommand.Add("gmod_integration_screenshot", function() - gmInte.SendNet(4) -end) - -// -// Chat Commands -// - -hook.Add("OnPlayerChat", "gmInteChatCommands", function(ply, text, teamChat, isDead) - if (ply != LocalPlayer()) then return end - text = string.lower(text) - text = string.sub(text, 2) - - if (text == "screen") then - gmInte.SendNet(4) - end -end) \ No newline at end of file +concommand.Add("gmod_integration_admin", gmInte.openAdminConfig) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_net.lua b/lua/gmod_integration/client/cl_net.lua index 5938cfb..ddf4fe1 100644 --- a/lua/gmod_integration/client/cl_net.lua +++ b/lua/gmod_integration/client/cl_net.lua @@ -1,33 +1,30 @@ // -// Network +// Send Net // -/* -Upload - 0 - Say I'm ready - 1 - Test Connection - 2 - Get Config - 3 - Save Config - 4 - Take ScreenShot - 5 - Restart Map -Receive - 1 - Sync Chat - 2 - Get Config - 3 - Test Connection - 4 - Take ScreenShot -*/ +local netSend = { + ["ready"] = 0, + ["testConnection"] = 1, + ["getConfig"] = 2, + ["saveConfig"] = 3, + ["takeScreenShot"] = 4, + ["restartMap"] = 5, + ["verifyMe"] = 6, +} -// Send function gmInte.SendNet(id, args, func) net.Start("gmIntegration") - net.WriteUInt(id, 8) + net.WriteUInt(netSend[id], 8) net.WriteString(util.TableToJSON(args || {})) if (func) then func() end net.SendToServer() end -// Receive -local netFunc = { +// +// Receive Net +// + +local netReceive = { [1] = function(data) gmInte.discordSyncChatPly(data) end, @@ -37,13 +34,22 @@ local netFunc = { [3] = function(data) gmInte.showTestConnection(data) end, - [4] = function(data) - gmInte.takeScreenShot(data.serverID, data.authToken) + [5] = function(data) + gmInte.config = table.Merge(gmInte.config, data) + end, + [6] = function(data) + gmInte.chatAddText(data) + end, + [7] = function() + gmInte.openVerifPopup() + end, + [8] = function(data) + gmInte.config.token = data.token end } net.Receive("gmIntegration", function() local id = net.ReadUInt(8) local args = util.JSONToTable(net.ReadString()) - if (netFunc[id]) then netFunc[id](args) end + if (netReceive[id]) then netReceive[id](args) end end) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_screenshots.lua b/lua/gmod_integration/client/cl_screenshots.lua new file mode 100644 index 0000000..505cd71 --- /dev/null +++ b/lua/gmod_integration/client/cl_screenshots.lua @@ -0,0 +1,89 @@ +// +// Hooks +// + +local ScreenshotRequested = false +local FailAttempts = 0 +hook.Add("PostRender", "gmInteScreenshot", function() + if (!ScreenshotRequested) then return end + ScreenshotRequested = false + + local captureData = { + format = "jpeg", + x = 0, + y = 0, + w = ScrW(), + h = ScrH(), + quality = 95, + } + + local screenCapture = render.Capture(captureData) + if (!screenCapture) then + if (FailAttempts < 3) then + timer.Simple(0.5, function() + ScreenshotRequested = true + FailAttempts = FailAttempts + 1 + gmInte.log("Failed to take screenshot, retrying... (" .. FailAttempts .. "/3)", true) + end) + return + else + FailAttempts = 0 + chat.AddText(Color(255, 130, 92), "[Gmod Integration] ", Color(102, 63, 63), "Failed to take screenshot, your system may not support this feature.") + return + end + end + + local base64Capture = util.Base64Encode(screenCapture) + + local size = math.Round(string.len(base64Capture) / 1024) + gmInte.log("Screenshot Taken - " .. size .. "KB", true) + + gmInte.http.post("/screenshots", + { + ["player"] = gmInte.getPlayerFormat(LocalPlayer()), + ["screenshot"] = base64Capture, + ["captureData"] = captureData, + ["size"] = size .. "KB" + }, + function(code, body) + gmInte.log("Screenshot sent to Discord", true) + chat.AddText(Color(255, 130, 92), "[Gmod Integration] ", Color(255, 255, 255), "Screenshot sent to Discord.") + end, + function(code, body) + gmInte.log("Screenshot failed to send to Discord, error code: " .. code, true) + end + ) +end) + +// +// Methods +// + +function gmInte.takeScreenShot() + timer.Simple(0.5, function() + ScreenshotRequested = true + end) +end + +// +// Console Commands +// + +concommand.Add("gmod_integration_screenshot", function() + gmInte.takeScreenShot() +end) + +// +// Chat Commands +// + +hook.Add("OnPlayerChat", "gmInteChatCommands", function(ply, text, teamChat, isDead) + if (ply != LocalPlayer()) then return end + text = string.lower(text) + text = string.sub(text, 2) + + if (text == "screen") then + gmInte.takeScreenShot() + return true + end +end) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_steaming.lua b/lua/gmod_integration/client/cl_steaming.lua new file mode 100644 index 0000000..c68a02d --- /dev/null +++ b/lua/gmod_integration/client/cl_steaming.lua @@ -0,0 +1,57 @@ +// +// Hooks +// + +local StreamsRequeted = false +hook.Add("PostRender", "gmInte:PostRender:Stream:Frame", function() + if (!StreamsRequeted) then return end + StreamsRequeted = false + + // Capture frame + local captureConfig = { + format = "jpeg", + x = 0, + y = 0, + w = ScrW(), + h = ScrH(), + quality = 30, + } + + local screenCapture = render.Capture(captureConfig) + if (!screenCapture) then return end + screenCapture = util.Base64Encode(screenCapture) + + local size = math.Round(string.len(screenCapture) / 1024) + gmInte.log("Frame captured, size: " .. size .. "KB", true) + + gmInte.http.post("/streams/frames", + { + ["player"] = gmInte.getPlayerFormat(LocalPlayer()), + ["base64Capture"] = screenCapture, + ["captureConfig"] = captureConfig, + ["size"] = size .. "KB" + }, + function(code, body) + gmInte.log("Frame sent to WebPanel, size: " .. size .. "KB", true) + end, + function(code, body) + gmInte.log("Failed to send frame to WebPanel", true) + end + ) +end) + +local Steam = false +timer.Create("gmInte:Stream:Frame", 0.5, 0, function() + if (Steam) then + StreamsRequeted = true + end +end) + +// +// Console Commands +// + +concommand.Add("gmod_integration_stream", function() + Steam = !Steam + gmInte.log("Streaming frames to WebPanel: " .. tostring(Steam)) +end) \ No newline at end of file diff --git a/lua/gmod_integration/client/cl_sync_chat.lua b/lua/gmod_integration/client/cl_sync_chat.lua new file mode 100644 index 0000000..3a3f891 --- /dev/null +++ b/lua/gmod_integration/client/cl_sync_chat.lua @@ -0,0 +1,13 @@ +local function formatName(name) + // all un down case + name = string.lower(name) + // first leter in upper case + name = string.upper(string.sub(name, 1, 1)) .. string.sub(name, 2) + // every letter after a space in upper case + name = string.gsub(name, "(%a)([%w_']*)", function(a,b) return string.upper(a) .. string.lower(b) end) + return name +end + +function gmInte.discordSyncChatPly(data) + chat.AddText(Color(92, 105, 255), "(DISCORD) ", Color(12, 151, 12), formatName(data.name) .. ": ", Color(255, 255, 255), data.content) +end \ No newline at end of file diff --git a/lua/gmod_integration/server/sv__websocket.lua b/lua/gmod_integration/server/sv__websocket.lua index c6faa6f..a460907 100644 --- a/lua/gmod_integration/server/sv__websocket.lua +++ b/lua/gmod_integration/server/sv__websocket.lua @@ -30,16 +30,7 @@ if (!GWSockets) then end local function getWebSocketURL() - local url = "wss://ws.gmod-integration.com" - local devURL = "wss://dev-ws.gmod-integration.com" - - if (!gmInte.config.debug) then return url end - if (gmInte.config.devInstance) then - gmInte.log("Using dev Instance", true) - return devURL - end - - return url + return "wss://" .. gmInte.config.websocketFQDN end local socket = GWSockets.createWebSocket(getWebSocketURL()) @@ -55,14 +46,12 @@ end // log on message function socket:onMessage(txt) gmInte.log("WebSocket Message: " .. txt, true) + local data = util.JSONToTable(txt) - if (gmInte.config.debug) then - gmInte.log("WebSocket Message: " .. txt, true) - end if (gmInte[data.method]) then gmInte[data.method](data) else - gmInte.logError("WebSocket Message: " .. txt .. " is not a valid method !") + gmInte.logError("WebSocket Message: " .. txt .. " is not a valid method !", true) end end diff --git a/lua/gmod_integration/server/sv_con.lua b/lua/gmod_integration/server/sv_con.lua index b9648b7..4bae5d6 100644 --- a/lua/gmod_integration/server/sv_con.lua +++ b/lua/gmod_integration/server/sv_con.lua @@ -1,7 +1,3 @@ -// -// Console Commands -// - local conFuncs = { ["version"] = function() gmInte.log("Version: " .. gmInte.version) diff --git a/lua/gmod_integration/server/sv_filtrers.lua b/lua/gmod_integration/server/sv_filtrers.lua new file mode 100644 index 0000000..4eefce9 --- /dev/null +++ b/lua/gmod_integration/server/sv_filtrers.lua @@ -0,0 +1,86 @@ +// +// Methods +// + +local function filterMessage(reason) + local Message = { + "\n----------------------------------------\n", + "You cannot join this server", + "", + "Reason: " .. (reason && reason || "none"), + "Help URL: " .. (gmInte.config.supportLink && gmInte.config.supportLink || "none"), + "", + "Have a nice day", + "\n----------------------------------------\n", + "Service provided by Gmod Integration", + } + + for k, v in pairs(Message) do + Message[k] = "\n" .. v + end + + return table.concat(Message) +end + +local function checkTrustFactor(trustLevel) + if (gmInte.config.filterOnTrust && (trustLevel < gmInte.config.minimalTrust)) then + return false + end + + return true +end + +local function checkBanStatus(banStatus) + if (gmInte.config.filterOnBan && banStatus) then + return false + end + + return true +end + +local function checkDiscordBanStatus(banStatus) + if (gmInte.config.syncBan && banStatus) then + return false + end + + return true +end + +local function playerFilter(data) + if (data.bot == 1) then return end + data.steamID64 = util.SteamIDTo64(data.networkid) + + gmInte.http.get("/players/" .. data.steamID64, + function(code, body) + if (gmInte.config.maintenance && !body.bypassMaintenance) then + game.KickID(data.networkid, filterMessage("The server is currently under maintenance and you are not whitelisted.")) + end + + if (!checkBanStatus(body.ban)) then + game.KickID(data.networkid, filterMessage("You are banned from this server.")) + end + + if (!checkDiscordBanStatus(body.discord_ban)) then + game.KickID(data.networkid, filterMessage("You are banned from our discord server.")) + end + + -- if (!checkTrustFactor(body.trust)) then + -- game.KickID(data.networkid, filterMessage("Your trust factor is too low.")) + -- end + end, + function (code, body) + if (gmInte.config.maintenance) then + game.KickID(data.networkid, filterMessage("The server is currently under maintenance and we cannot verify your account.\nVerification URL: https://verif.gmod-integration.com")) + end + end + ) +end + +// +// Hooks +// + +gameevent.Listen("player_connect") +hook.Add("player_connect", "gmInte:Player:Connect:Filter", function(data) + playerFilter(data) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_hook.lua b/lua/gmod_integration/server/sv_hook.lua deleted file mode 100644 index d28a4ba..0000000 --- a/lua/gmod_integration/server/sv_hook.lua +++ /dev/null @@ -1,40 +0,0 @@ -// -// Server Hooks -// - -hook.Add("ShutDown", "gmInte:Server:ShutDown", function() - gmInte.serverShutDown() -end) - -hook.Add("Initialize", "gmInte.sendStatus", function() - timer.Simple(1, function() - gmInte.serverStart() - end) -end) - -// -// Player Hooks -// - -gameevent.Listen("player_connect") -hook.Add("player_connect", "gmInte:Player:Connect", function(data) - gmInte.playerConnect(data) - gmInte.playerFilter(data) -end) - -gameevent.Listen("server_addban") -hook.Add("server_addban", "gmInte:Player:Ban", function(data) - gmInte.playerBan(data) -end) - -hook.Add("PlayerDisconnected", "gmInte:Player:Disconnect", function(ply) - gmInte.playerDisconnected(ply) -end) - -hook.Add("onPlayerChangedName", "gmInte:PlayerChangeName", function(ply, old, new) - gmInte.playerChangeName(ply, old, new) -end) - -hook.Add("PlayerSay", "gmInte:PlayerSay", function(ply, text, team) - gmInte.playerSay(ply, text, team) -end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_log.lua b/lua/gmod_integration/server/sv_log.lua deleted file mode 100644 index 038c2ff..0000000 --- a/lua/gmod_integration/server/sv_log.lua +++ /dev/null @@ -1,248 +0,0 @@ -// -// Functions -// - -local function logFormatWeapon(weapon, data) - data = data or {} - data.class = weapon:GetClass() - data.printName = weapon:GetPrintName() - return data -end - -local function logFormatEntity(ent, data) - data = data or {} - data.class = ent:GetClass() - data.model = ent:GetModel() - data.pos = ent:GetPos() - data.ang = ent:GetAngles() - return data -end - -local function logFormatVector(vec, data) - data = data or {} - data.x = vec.x - data.y = vec.y - data.z = vec.z - return data -end - -local function logFormatAngle(ang, data) - data = data or {} - data.p = ang.p - data.y = ang.y - data.r = ang.r - return data -end - -local function logFormatTeam(teamID, data) - data = data or {} - data.id = teamID - data.name = team.GetName(teamID) - return data -end - -local function logFormatPlayer(ply, data) - data = data or {} - data.steamID64 = ply:SteamID64() - data.steamID = ply:SteamID() - data.nick = ply:Nick() - data.userGroup = ply:GetUserGroup() - data.team = logFormatTeam(ply:Team()) - return data -end - -local function logDisable() - return !gmInte.config.sendLog -end - -local function validLogAndPlayers(players) - if (logDisable()) then return false end - for _, ply in pairs(players) do - // return if not valid, player or bot and bots logs are disabled - if (!IsValid(ply)) then return false end - if (!ply:IsPlayer()) then return false end - if (!ply:IsBot() && !gmInte.config.logBotActions) then return false end - end - return true -end - -// -// Posts -// - -function gmInte.postLogPlayerSay(ply, text, teamChat) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerSay", - { - ["ply"] = logFormatPlayer(ply), - ["text"] = text, - ["teamChat"] = teamChat - } - ) -end - -function gmInte.postLogPlayerDeath(ply, inflictor, attacker) - if (!validLogAndPlayers({ply, attacker})) then return end - - gmInte.post("/server/log/playerDeath", - { - ["ply"] = logFormatPlayer(ply), - ["inflictor"] = logFormatEntity(inflictor), - ["attacker"] = logFormatPlayer(attacker) - } - ) -end - -function gmInte.postLogPlayerInitialSpawn(ply) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerInitialSpawn", - { - ["ply"] = logFormatPlayer(ply) - } - ) -end - -function gmInte.postLogPlayerHurt(ply, attacker, healthRemaining, damageTaken) - if (!validLogAndPlayers({ply, attacker})) then return end - - // Wait a second to see if the player is going to be hurt again - ply.gmodInteLastHurt = ply.gmodInteLastHurt || {} - local locCurTime = CurTime() - ply.gmodInteLastHurt[attacker:SteamID64()] = locCurTime - - timer.Simple(1, function() - if (ply.gmodInteLastHurt[attacker:SteamID64()] != locCurTime) then - ply.gmodInteTotalDamage = ply.gmodInteTotalDamage || 0 - ply.gmodInteTotalDamage = ply.gmodInteTotalDamage + damageTaken - return - end - - gmInte.post("/server/log/playerHurt", - { - ["ply"] = logFormatPlayer(ply), - ["attacker"] = logFormatPlayer(attacker), - ["healthRemaining"] = healthRemaining, - ["damageTaken"] = ply.gmodInteTotalDamage - } - ) - end) -end - -function gmInte.postLogPlayerSpawnedSomething(object, ply, ent, model) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerSpawnedSomething", - { - ["object"] = object, - ["ply"] = logFormatPlayer(ply), - ["ent"] = logFormatEntity(ent), - ["model"] = model || "" - } - ) -end - -function gmInte.postLogPlayerSpawn(ply) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerSpawn", - { - ["ply"] = logFormatPlayer(ply) - } - ) -end - -function gmInte.postLogPlayerDisconnect(ply) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerDisconnect", - { - ["ply"] = logFormatPlayer(ply) - } - ) -end - -function gmInte.postLogPlayerConnect(data) - if (logDisable() || data.bot) then return end - - gmInte.post("/server/log/playerConnect", - { - ["steamID64"] = util.SteamIDTo64(data.networkid), - ["steamID"] = data.networkid, - ["name"] = data.name, - ["ip"] = data.address - } - ) -end - -function gmInte.postLogPlayerGivet(ply, class, swep) - if (!validLogAndPlayers({ply})) then return end - - gmInte.post("/server/log/playerGive", - { - ["ply"] = logFormatPlayer(ply), - ["class"] = class, - ["swep"] = swep - } - ) -end - -// -// Hooks -// - -gameevent.Listen("player_connect") - -// Sandbox - Player -hook.Add("PlayerSay", "gmInte:Log:PlayerSay", function(ply, text, teamChat) - gmInte.postLogPlayerSay(ply, text, teamChat) -end) -hook.Add("PlayerSpawn", "gmInte:Log:PlayerSpawn", function(ply) - gmInte.postLogPlayerSpawn(ply) -end) -hook.Add("PlayerInitialSpawn", "gmInte:Log:PlayerInitialSpawn", function(ply) - gmInte.postLogPlayerInitialSpawn(ply) -end) -hook.Add("PlayerDisconnected", "gmInte:Log:PlayerDisconnected", function(ply) - gmInte.postLogPlayerDisconnect(ply) -end) -hook.Add("PlayerGiveSWEP", "gmInte:Log:PlayerSWEPs", function( ply, class, swep ) - gmInte.postLogPlayerGivet(ply, class, swep) -end) - -// Sandbox - Server Events -hook.Add("player_connect", "gmInte:Log:PlayerConnect", function(data) - gmInte.postLogPlayerConnect(data) -end) - -// Sandbox - Player Combat -hook.Add("PlayerDeath", "gmInte:Log:PlayerDeath", function(ply, inflictor, attacker) - gmInte.postLogPlayerDeath(ply, inflictor, attacker) -end) -hook.Add("PlayerHurt", "gmInte:Log:PlayerHurt", function(ply, attacker, healthRemaining, damageTaken) - gmInte.postLogPlayerHurt(ply, attacker, healthRemaining, damageTaken) -end) - -// Sandbox - Spawnables -hook.Add("PlayerSpawnedProp", "gmInte:Log:PlayerSpawnedProp", function(ply, model, ent) - gmInte.postLogPlayerSpawnedSomething("SENT", ply, ent, model) -end) -hook.Add("PlayerSpawnedSENT", "gmInte:Log:PlayerSpawnedSENT", function(ply, ent) - gmInte.postLogPlayerSpawnedSomething("SENT", ply, ent) -end) -hook.Add("PlayerSpawnedNPC", "gmInte:Log:PlayerSpawnedNPC", function(ply, ent) - gmInte.postLogPlayerSpawnedSomething("NPC", ply, ent) -end) -hook.Add("PlayerSpawnedVehicle", "gmInte:Log:PlayerSpawnedVehicle", function(ply, ent) - gmInte.postLogPlayerSpawnedSomething("Vehicle", ply, ent) -end) -hook.Add("PlayerSpawnedEffect", "gmInte:Log:PlayerSpawnedEffect", function(ply, model, ent) - gmInte.postLogPlayerSpawnedSomething("Effect", ply, ent, model) -end) -hook.Add("PlayerSpawnedRagdoll", "gmInte:Log:PlayerSpawnedRagdoll", function(ply, model, ent) - gmInte.postLogPlayerSpawnedSomething("Ragdoll", ply, ent, model) -end) -hook.Add("PlayerSpawnedSWEP", "gmInte:Log:PlayerSpawnedSWEP", function(ply, ent) - gmInte.postLogPlayerSpawnedSomething("SWEP", ply, ent) -end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_main.lua b/lua/gmod_integration/server/sv_main.lua deleted file mode 100644 index b56919a..0000000 --- a/lua/gmod_integration/server/sv_main.lua +++ /dev/null @@ -1,300 +0,0 @@ -// -// Functions -// - -function gmInte.removePort(ip) - return string.Explode(":", ip)[1] -end - -function gmInte.plyValid(ply) - return ply:IsValid() && ply:IsPlayer() && !ply:IsBot() -end - -function gmInte.saveSetting(setting, value) - // save this in data/gmod_integration/setting.json but first check if variable is valid - if gmInte.config[setting] == nil then - gmInte.log("Unknown Setting") - return - end - - // Boolean - if (value == "true") then value = true end - if (value == "false") then value = false end - - // Number - if (tonumber(value) != nil) then value = tonumber(value) end - - gmInte.config[setting] = value - file.Write("gm_integration/config.json", util.TableToJSON(gmInte.config, true)) - gmInte.log("Setting Saved") -end - -function gmInte.playerConnect(data) - if (data.bot == 1) then return end - - data.steam = util.SteamIDTo64(data.networkid) - - gmInte.post("/server/user/connect", data) -end - -local function getCustomValues(ply) - local customValues = {} - - customValues.money = ply:gmInteGetTotalMoney() || 0 - - return customValues -end - -local function getTriggerInfo(text) - for k, v in pairs(gmInte.config.chatTrigger) do - if (string.StartWith(text, k)) then - local defaultConfig = { - ["trigger"] = k, - ["prefix"] = "", - ["show_rank"] = false, - ["anonymous"] = false, - ["channel"] = "admin_sync_chat" - } - for k2, v2 in pairs(v) do - defaultConfig[k2] = v2 - end - return defaultConfig - end - end - - return false -end - -function gmInte.playerSay(ply, text, team) - if (!gmInte.config.syncChat) then return end - - gmInte.post("/server/user/say", - { - ["steamID64"] = ply:SteamID64(), - ["message"] = text, - ["name"] = ply:Nick(), - ["usergroup"] = ply:GetUserGroup(), - ["message_info"] = triggerInfo - } - ) -end - -function gmInte.wsPlayerSay(data) - gmInte.SendNet(1, data, nil) -end - -function gmInte.generatePlayerToken(steamID64) - return util.SHA256(steamID64 .. '-' .. gmInte.config.token .. '-' .. gmInte.publicTempToken) -end - -function gmInte.takeScreenshot(ply) - gmInte.SendNet(4, { - ["serverID"] = gmInte.config.id, - ["authToken"] = gmInte.generatePlayerToken(ply:SteamID64()) - }, ply) -end - -function gmInte.wsPlayerScreen(data) - for _, ply in pairs(player.GetAll()) do - if (ply:SteamID64() == data.steamID64) then - gmInte.takeScreenshot(ply) - end - end -end - -function gmInte.wsRcon(data) - gmInte.log("Rcon Command from Discord '" .. data.command .. "' by " .. data.steamID) - game.ConsoleCommand(data.command .. "\n") -end - -function gmInte.playerBan(data) - data.steam = util.SteamIDTo64(data.networkid) - gmInte.post("/server/user/ban", data) -end - -function gmInte.userFinishConnect(ply) - if (!gmInte.plyValid(ply)) then return end - - gmInte.post("/server/user/finishConnect", - { - ["steam"] = ply:SteamID64(), - ["name"] = ply:Nick(), - } - ) -end - -function gmInte.serverShutDown() - gmInte.post("/server/shutdown") -end - -function gmInte.sendStatus(start) - if (gmInte.config.id == "" || gmInte.config.token == "") then - gmInte.logError("ID or Token is empty: (id: " .. (gmInte.config.id == "" && "empty" || gmInte.config.id) .. ", token: " .. (gmInte.config.token == "" && "empty" || "not empty but hide") .. ")") - gmInte.logHint("Use 'gmod-integration setting id YOUR_SERVER_ID' and 'gmod-integration setting token YOUR_SERVER_TOKEN' to set your credentials, you can find them on https://gmod-integration.com/config/servers") - return - end - gmInte.post("/server/status", - { - ["start"] = start || false, - ["hostname"] = GetHostName(), - ["ip"] = game.GetIPAddress(), - ["port"] = GetConVar("hostport"):GetInt(), - ["map"] = game.GetMap(), - ["players"] = #player.GetAll(), - ["maxplayers"] = game.MaxPlayers(), - ["gamemode"] = engine.ActiveGamemode() - }, - function(code, body) - if (body.publicTempToken) then - gmInte.publicTempToken = body.publicTempToken - end - end, - function(code, body, headers) - gmInte.logError("Your Credentials are Invalid: (id: " .. (gmInte.config.id == "" && "empty" || gmInte.config.id) .. ", token: " .. (gmInte.config.token == "" && "empty" || "not empty but hide") .. ")") - gmInte.logHint("Use 'gmod-integration setting id YOUR_SERVER_ID' and 'gmod-integration setting token YOUR_SERVER_TOKEN' to set your credentials, you can find them on https://gmod-integration.com/config/servers") - end - ) -end - -function gmInte.serverStart() - gmInte.sendStatus(true) -end - -// every 5 minutes -timer.Create("gmInte.sendStatus", 300, 0, function() - gmInte.sendStatus() -end) - -function gmInte.playerChangeName(ply, old, new) - if (!gmInte.plyValid(ply)) then return end - - gmInte.post("/server/user/changeName", - { - ["steamID64"] = ply:SteamID64(), - ["oldName"] = old, - ["newName"] = new, - } - ) -end - -function gmInte.playerDisconnected(ply) - if (!gmInte.plyValid(ply)) then return end - - gmInte.post("/server/user/disconnect", - { - ["steam"] = ply:SteamID64(), - ["kills"] = ply:Frags() || 0, - ["deaths"] = ply:Deaths() || 0, - ["customValues"] = getCustomValues(ply), - ["rank"] = ply:GetUserGroup() || "user", - ["time"] = math.Round(RealTime() - ply.gmIntTimeConnect) || 0, - } - ) -end - -function gmInte.tryConfig() - gmInte.get("/server", - function(code, body) - print(" ") - gmInte.log("Congratulations your server is now connected to Gmod Integration") - gmInte.log("Server Name: " .. body.name) - gmInte.log("Server ID: " .. body.id) - print(" ") - end) -end - -function gmInte.testConnection(ply) - gmInte.get("/server", - function(code, body) - gmInte.SendNet(3, body, ply) - end, - function(code, body, headers) - gmInte.SendNet(3, body, ply) - end) -end - -function gmInte.serverShutDown() - for ply, ply in pairs(player.GetAll()) do - gmInte.playerDisconnected(ply) - end -end - -function gmInte.refreshSettings() - gmInte.config = util.JSONToTable(file.Read("gm_integration/config.json", "DATA")) - gmInte.log("Settings Refreshed") - gmInte.tryConfig() -end - -local function filterMessage(reason) - local Message = { - [1] = "\n", - [2] = "This server has player filtering enabled", - [3] = "You are not allowed to join this server", - [4] = "", - [5] = "Reason: " .. reason, - [6] = "", - [7] = "For more information, please contact the server owner", - [8] = "Help URL: " .. (gmInte.config.supportLink && gmInte.config.supportLink || "No Support Link"), - [9] = "", - [10] = "You can also contact us on our discord server", - [11] = "https://gmod-integration.com/discord", - [12] = "", - [13] = "Have a nice day", - [14] = "", - [15] = "Service provided by Gmod Integration", - } - for k, v in pairs(Message) do - Message[k] = v .. "\n" - end - return table.concat(Message) -end - -function gmInte.playerFilter(data) - if (data.bot == 1) then return end - - data.steamID64 = util.SteamIDTo64(data.networkid) - - // get data - gmInte.get("/server/user" .. "?steamID64=" .. data.steamID64, - function(code, body) - if (!body || !body.trust) then return end - - // Gmod Integration Trust - if (gmInte.config.filterOnTrust && (body.trust < gmInte.config.minimalTrust)) then - // kick player - game.KickID(data.networkid, filterMessage("Insufficient Trust Level\nYour Trust Level: " .. body.trust .. "\nMinimal Trust Level: " .. gmInte.config.minimalTrust)) - end - - // Gmod Integration Ban - if (gmInte.config.filterOnBan && body.ban) then - // kick player - game.KickID(data.networkid, filterMessage("You are banned from Gmod Integration")) - end - - // Server Discord Ban - if (gmInte.config.syncBan && body.discord_ban) then - // kick player - game.KickID(data.networkid, filterMessage("You are banned from the discord server\nReason: " .. (body.discord_ban_reason && body.discord_ban_reason || "No Reason"))) - end - end - ) -end - -function gmInte.superadminGetConfig(ply) - if (!gmInte.plyValid(ply) || !ply:IsSuperAdmin()) then return end - - gmInte.config.websocket = GWSockets && true || false - gmInte.SendNet(2, gmInte.config, ply) -end - -function gmInte.superadminSetConfig(ply, data) - if (!gmInte.plyValid(ply) || !ply:IsSuperAdmin()) then return end - - for k, v in pairs(data) do - gmInte.saveSetting(k, v) - end - - if data.token || data.id then - gmInte.testConnection(ply) - end -end \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_meta.lua b/lua/gmod_integration/server/sv_meta.lua deleted file mode 100644 index ec90cd1..0000000 --- a/lua/gmod_integration/server/sv_meta.lua +++ /dev/null @@ -1,12 +0,0 @@ -// Meta -local ply = FindMetaTable("Player") - -function ply:gmInteGetTotalMoney() - // if darkrp - if DarkRP then - return self:getDarkRPVar("money") - end - - // else - return 0 -end \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_net.lua b/lua/gmod_integration/server/sv_net.lua index 11452ee..aa32f56 100644 --- a/lua/gmod_integration/server/sv_net.lua +++ b/lua/gmod_integration/server/sv_net.lua @@ -1,29 +1,30 @@ // -// Network +// Networking // -/* -Upload - 1 - Add Chat Message - 2 - Get Config - 3 - Test Connection - 4 - Take Screenshot -Receive - 0 - Player is Ready - 1 - Test Connection - 2 - Get Config - 3 - Set Config - 4 - Take Screenshot - 5 - Restart Map -*/ - util.AddNetworkString("gmIntegration") +// +// Send Net +// + +local netSend = { + ["wsRelayDiscordChat"] = 1, + ["adminConfig"] = 2, + ["testApiConnection"] = 3, + ["publicConfig"] = 5, + ["chatColorMessage"] = 6, + ["openVerifPopup"] = 7, + ["savePlayerToken"] = 8 +} + // Send function gmInte.SendNet(id, data, ply, func) + if (!netSend[id]) then return end + net.Start("gmIntegration") - net.WriteUInt(id, 8) - net.WriteString(util.TableToJSON(data)) + net.WriteUInt(netSend[id], 8) + net.WriteString(util.TableToJSON(data || {})) if (func) then func() end if (ply == nil) then net.Broadcast() @@ -32,11 +33,13 @@ function gmInte.SendNet(id, data, ply, func) end end -// Receive -local netFuncs = { +// +// Receive net +// + +local netReceive = { [0] = function(ply) - gmInte.userFinishConnect(ply) - ply.gmIntTimeConnect = math.Round(RealTime()) + hook.Run("gmInte:PlayerReady", ply) end, [1] = function(ply, data) gmInte.testConnection(ply, data) @@ -54,11 +57,21 @@ local netFuncs = { if (!ply:IsSuperAdmin()) then return end RunConsoleCommand("changelevel", game.GetMap()) end, + [6] = function(ply) + gmInte.verifyPlayer(ply) + end, + [7] = function(ply, data) + sendPlayerToken(ply) + end } net.Receive("gmIntegration", function(len, ply) - if !ply:IsPlayer() then return end + if (!ply || ply && !ply:IsValid()) then return end + local id = net.ReadUInt(8) local data = util.JSONToTable(net.ReadString() || "{}") - if (netFuncs[id]) then netFuncs[id](ply, data) end + + if (!netReceive[id]) then return end + + netReceive[id](ply, data) end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_players.lua b/lua/gmod_integration/server/sv_players.lua new file mode 100644 index 0000000..8a7d6bb --- /dev/null +++ b/lua/gmod_integration/server/sv_players.lua @@ -0,0 +1,191 @@ +// +// Methods +// + +function gmInte.playerReady(ply) + if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + + // Initialize Time + ply.gmIntTimeConnect = math.Round(RealTime()) + + // Send Public Config + gmInte.publicGetConfig(ply) + + gmInte.http.post("/players/" .. ply:SteamID64() .. "/ready", { + ["player"] = gmInte.getPlayerFormat(ply) + }) +end + +function gmInte.playerConnect(data) + data.steamID64 = util.SteamIDTo64(data.networkid) + gmInte.http.post("/players/" .. util.SteamIDTo64(data.networkid) .. "/connect", data) +end + +function gmInte.playerDisconnected(ply) + if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + + gmInte.http.post("/players/" .. ply:SteamID64() .. "/disconnect", + { + ["player"] = gmInte.getPlayerFormat(ply), + } + ) +end + +-- function gmInte.playerSpawn(ply) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + +-- gmInte.http.post("/players/" .. ply:SteamID64() .. "/spawn", +-- { +-- ["player"] = gmInte.getPlayerFormat(ply) +-- } +-- ) +-- end + +-- function gmInte.postLogPlayerDeath(ply, inflictor, attacker) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end +-- if (!attacker:IsValid() || !attacker:IsPlayer(attacker)) then return end +-- if (!inflictor:IsValid()) then return end + +-- gmInte.http.post("/logs/playerDeath", +-- { +-- ["player"] = gmInte.getPlayerFormat(ply), +-- ["inflictor"] = gmInte.getEntityFormat(inflictor), +-- ["attacker"] = gmInte.getPlayerFormat(attacker) +-- } +-- ) +-- end + +-- function gmInte.postLogPlayerInitialSpawn(ply) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + +-- gmInte.http.post("/logs/playerInitialSpawn", +-- { +-- ["ply"] = gmInte.getPlayerFormat(ply) +-- } +-- ) +-- end + +-- function gmInte.postLogPlayerHurt(ply, attacker, healthRemaining, damageTaken) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end +-- if (!attacker:IsValid() || !attacker:IsPlayer(attacker)) then return end + +-- // Wait a second to see if the player is going to be hurt again +-- ply.gmodInteLastHurt = ply.gmodInteLastHurt || {} +-- local locCurTime = CurTime() +-- ply.gmodInteLastHurt[attacker:SteamID64()] = locCurTime + +-- timer.Simple(1, function() +-- if (ply.gmodInteLastHurt[attacker:SteamID64()] != locCurTime) then +-- ply.gmodInteTotalDamage = ply.gmodInteTotalDamage || 0 +-- ply.gmodInteTotalDamage = ply.gmodInteTotalDamage + damageTaken +-- return +-- end + +-- gmInte.http.post("/logs/playerHurt", +-- { +-- ["victim"] = gmInte.getPlayerFormat(ply), +-- ["attacker"] = gmInte.getPlayerFormat(attacker), +-- ["healthRemaining"] = healthRemaining, +-- ["damageTaken"] = ply.gmodInteTotalDamage +-- } +-- ) +-- end) +-- end + +-- function gmInte.postLogPlayerSpawnedSomething(object, ply, ent, model) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end +-- if (!ent:IsValid()) then return end + +-- gmInte.http.post("/logs/playerSpawnedSomething", +-- { +-- ["object"] = object, +-- ["player"] = gmInte.getPlayerFormat(ply), +-- ["entity"] = gmInte.getEntityFormat(ent), +-- ["model"] = model || "" +-- } +-- ) +-- end + +-- function gmInte.postLogPlayerGivet(ply, class, swep) +-- if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + +-- gmInte.http.post("/logs/playerGive", +-- { +-- ["player"] = gmInte.getPlayerFormat(ply), +-- ["class"] = class, +-- ["swep"] = swep +-- } +-- ) +-- end + + +// +// Hooks +// + +hook.Add("gmInte:PlayerReady", "gmInte:Player:Ready", function(ply) + gmInte.playerReady(ply) +end) + +hook.Add("ShutDown", "gmInte:Server:Shutdown:SavePlayers", function() + for ply, ply in pairs(player.GetAll()) do + gmInte.playerDisconnected(ply) + end +end) + +gameevent.Listen("player_connect") +hook.Add("player_connect", "gmInte:Player:Connect", function(data) + gmInte.playerConnect(data) +end) + +hook.Add("PlayerDisconnected", "gmInte:Player:Disconnect", function(ply) + gmInte.playerDisconnected(ply) +end) + +-- hook.Add("PlayerSpawn", "gmInte:Player:Spawn", function(ply) +-- gmInte.playerSpawn(ply) +-- end) + +-- hook.Add("PlayerInitialSpawn", "gmInte:Player:InitialSpawn", function(ply) +-- gmInte.postLogPlayerInitialSpawn(ply) +-- end) + +-- hook.Add("PlayerGiveSWEP", "gmInte:Player:SWEPs", function( ply, class, swep ) +-- gmInte.postLogPlayerGivet(ply, class, swep) +-- end) + +-- hook.Add("PlayerDeath", "gmInte:Player:Death", function(ply, inflictor, attacker) +-- gmInte.postLogPlayerDeath(ply, inflictor, attacker) +-- end) + +-- hook.Add("PlayerHurt", "gmInte:Player:Hurt", function(ply, attacker, healthRemaining, damageTaken) +-- gmInte.postLogPlayerHurt(ply, attacker, healthRemaining, damageTaken) +-- end) + +-- hook.Add("PlayerSpawnedProp", "gmInte:Player:SpawnedProp", function(ply, model, ent) +-- gmInte.postLogPlayerSpawnedSomething("SENT", ply, ent, model) +-- end) + +-- hook.Add("PlayerSpawnedSENT", "gmInte:Player:SpawnedSENT", function(ply, ent) +-- gmInte.postLogPlayerSpawnedSomething("SENT", ply, ent) +-- end) + +-- hook.Add("PlayerSpawnedNPC", "gmInte:Player:SpawnedNPC", function(ply, ent) +-- gmInte.postLogPlayerSpawnedSomething("NPC", ply, ent) +-- end) + +-- hook.Add("PlayerSpawnedVehicle", "gmInte:Player:SpawnedVehicle", function(ply, ent) +-- gmInte.postLogPlayerSpawnedSomething("Vehicle", ply, ent) +-- end) + +-- hook.Add("PlayerSpawnedEffect", "gmInte:Player:SpawnedEffect", function(ply, model, ent) +-- gmInte.postLogPlayerSpawnedSomething("Effect", ply, ent, model) +-- end) + +-- hook.Add("PlayerSpawnedRagdoll", "gmInte:Player:SpawnedRagdoll", function(ply, model, ent) +-- gmInte.postLogPlayerSpawnedSomething("Ragdoll", ply, ent, model) +-- end) + +-- hook.Add("PlayerSpawnedSWEP", "gmInte:Player:SpawnedSWEP", function(ply, ent) +-- gmInte.postLogPlayerSpawnedSomething("SWEP", ply, ent) +-- end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_players_verif.lua b/lua/gmod_integration/server/sv_players_verif.lua new file mode 100644 index 0000000..e0005a9 --- /dev/null +++ b/lua/gmod_integration/server/sv_players_verif.lua @@ -0,0 +1,42 @@ +// +// Methods +// + +function gmInte.verifyPlayer(ply) + if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + + gmInte.http.get("/players/" .. ply:SteamID64(), function(code, data) + if (!gmInte.config.forcePlayerLink) then return end + + if (data && data.steamID64) then + if (ply.gmIntVerified) then return end + gmInte.SendNet("chatColorMessage", { + [1] = { + ["text"] = "You have been verified", + ["color"] = Color(255, 255, 255) + } + }, ply) + ply:Freeze(false) + ply.gmIntVerified = true + else + gmInte.SendNet("chatColorMessage", { + [1] = { + ["text"] = "You are not verified", + ["color"] = Color(255, 0, 0) + } + }, ply) + ply:Freeze(true) + gmInte.SendNet("openVerifPopup", nil, ply) + end + end) +end + +// +// Hooks +// + +hook.Add("gmInte:PlayerReady", "gmInte:Verif:PlayerReady", function(ply) + if (!gmInte.config.forcePlayerLink) then return end + + gmInte.verifyPlayer(ply) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_rcon.lua b/lua/gmod_integration/server/sv_rcon.lua new file mode 100644 index 0000000..8cb8259 --- /dev/null +++ b/lua/gmod_integration/server/sv_rcon.lua @@ -0,0 +1,8 @@ +// +// Websocket +// + +function gmInte.wsRcon(data) + gmInte.log("Rcon Command from Discord '" .. data.command .. "' by " .. data.steamID) + game.ConsoleCommand(data.command .. "\n") +end \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_settings.lua b/lua/gmod_integration/server/sv_settings.lua new file mode 100644 index 0000000..baa0850 --- /dev/null +++ b/lua/gmod_integration/server/sv_settings.lua @@ -0,0 +1,84 @@ +function gmInte.saveSetting(setting, value) + if gmInte.config[setting] == nil then + gmInte.log("Unknown Setting") + return + end + + // Boolean + if (value == "true") then value = true end + if (value == "false") then value = false end + + // Number + if (tonumber(value) != nil) then value = tonumber(value) end + + gmInte.config[setting] = value + file.Write("gm_integration/config.json", util.TableToJSON(gmInte.config, true)) + gmInte.log("Setting Saved") + + // send to all players if it's a public setting + for _, ply in pairs(player.GetAll()) do + if (ply:IsValid() && ply:IsPlayer(ply)) then + gmInte.log("Sending new Public Config to " .. ply:Nick()) + gmInte.publicGetConfig(ply) + end + end +end + +function gmInte.tryConfig() + gmInte.http.get("", + function(code, body) + print(" ") + gmInte.log("Congratulations your server is now connected to Gmod Integration") + gmInte.log("Server Name: " .. body.name) + gmInte.log("Server ID: " .. body.id) + print(" ") + end + ) +end + +function gmInte.testConnection(ply) + gmInte.http.get("", + function(code, body) + if (ply) then gmInte.SendNet("testApiConnection", body, ply) end + end, + function(code, body) + if (ply) then gmInte.SendNet("testApiConnection", body, ply) end + end + ) +end + +function gmInte.refreshSettings() + gmInte.config = util.JSONToTable(file.Read("gm_integration/config.json", "DATA")) + gmInte.log("Settings Refreshed") + gmInte.tryConfig() +end + +function gmInte.superadminGetConfig(ply) + if (!ply:IsValid() || !ply:IsPlayer(ply) || !ply:IsSuperAdmin()) then return end + + gmInte.config.websocket = GWSockets && true || false + gmInte.SendNet("adminConfig", gmInte.config, ply) +end + +function gmInte.publicGetConfig(ply) + if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + + gmInte.SendNet("publicConfig", { + ["id"] = gmInte.config.id, + ["debug"] = gmInte.config.debug, + ["apiFQDN"] = gmInte.config.apiFQDN, + ["websocketFQDN"] = gmInte.config.websocketFQDN, + }, ply) +end + +function gmInte.superadminSetConfig(ply, data) + if (!ply:IsValid() || !ply:IsPlayer(ply) || !ply:IsSuperAdmin()) then return end + + for k, v in pairs(data) do + gmInte.saveSetting(k, v) + end + + if data.token || data.id then + gmInte.testConnection(ply) + end +end \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_status.lua b/lua/gmod_integration/server/sv_status.lua new file mode 100644 index 0000000..f78809b --- /dev/null +++ b/lua/gmod_integration/server/sv_status.lua @@ -0,0 +1,38 @@ +// +// Methods +// + +function gmInte.sendStatus() + gmInte.http.post("/status", gmInte.getServerFormat()) +end + +-- function gmInte.serverStart() +-- gmInte.http.post("/start", gmInte.getServerFormat()) +-- end + +function gmInte.serverShutDown() + gmInte.http.post("/shutdown") +end + +// +// Timers +// + +timer.Create("gmInte.sendStatus", 300, 0, function() + gmInte.sendStatus() +end) + +// +// Hooks +// + +hook.Add("Initialize", "gmInte:Server:Initialize:SendStatus", function() + timer.Simple(1, function() + -- gmInte.serverStart() + gmInte.sendStatus() + end) +end) + +hook.Add("ShutDown", "gmInte:Server:ShutDown:SendStatus", function() + gmInte.serverShutDown() +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_sync_bans.lua b/lua/gmod_integration/server/sv_sync_bans.lua new file mode 100644 index 0000000..522d46a --- /dev/null +++ b/lua/gmod_integration/server/sv_sync_bans.lua @@ -0,0 +1,29 @@ +// +// Websocket +// + +function gmInte.wsSyncBan(data) + for _, ply in ipairs(player.GetAll()) do + if (ply:SteamID64() == data.steam) then + ply:Kick(data.reason || "You have been banned from the server.") + end + end +end + +// +// Methods +// + +function gmInte.playerBan(data) + data.steamID64 = util.SteamIDTo64(data.networkid) + gmInte.http.post("/players/" .. util.SteamIDTo64(data.networkid) .. "/ban", data) +end + +// +// Hooks +// + +gameevent.Listen("server_addban") +hook.Add("server_addban", "gmInte:Player:Ban", function(data) + gmInte.playerBan(data) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_sync_chat.lua b/lua/gmod_integration/server/sv_sync_chat.lua new file mode 100644 index 0000000..3507080 --- /dev/null +++ b/lua/gmod_integration/server/sv_sync_chat.lua @@ -0,0 +1,31 @@ +// +// Websocket +// + +function gmInte.wsPlayerSay(data) + gmInte.SendNet("wsRelayDiscordChat", data, nil) +end + +// +// Methods +// + +function gmInte.playerSay(ply, text, teamOnly) + if (!gmInte.config.syncChat) then return end + + gmInte.http.post("/players/" .. ply:SteamID64() .. "/say", + { + ["player"] = gmInte.getPlayerFormat(ply), + ["text"] = text, + ["teamOnly"] = teamOnly, + } + ) +end + +// +// Hooks +// + +hook.Add("PlayerSay", "gmInte:SyncChat:PlayerSay", function(ply, text, team) + gmInte.playerSay(ply, text, team) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_sync_kicks.lua b/lua/gmod_integration/server/sv_sync_kicks.lua new file mode 100644 index 0000000..14a9724 --- /dev/null +++ b/lua/gmod_integration/server/sv_sync_kicks.lua @@ -0,0 +1,30 @@ +// +// Websocket +// + +function gmInte.wsSyncKick(data) + for _, ply in ipairs(player.GetAll()) do + if (ply:SteamID64() == data.steam) then + ply:Kick(data.reason || "You have been banned from the server.") + end + end +end + +// +// Methods +// + +function gmInte.playerKick(data) + gmInte.http.post("/players/" .. util.SteamIDTo64(data.networkid) .. "/kick", data) +end + +// +// Hooks +// + +gameevent.Listen("player_disconnect") +hook.Add("player_disconnect", "gmInte:SyncKick:Disconnect", function(data) + if (string.StartWith(data.reason, "Kicked by ") || data.reason == "No reason provided.") then + gmInte.playerKick(data) + end +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_sync_names.lua b/lua/gmod_integration/server/sv_sync_names.lua new file mode 100644 index 0000000..a1b367c --- /dev/null +++ b/lua/gmod_integration/server/sv_sync_names.lua @@ -0,0 +1,33 @@ +// +// Websocket +// + +function gmInte.wsSyncName(data) + local ply = player.GetBySteamID(data.player.steamID64) + if not IsValid(ply) then return end + ply:SetName(data.newName) +end + +// +// Methods +// + +function gmInte.playerChangeName(ply, oldName, newName) + if (!ply:IsValid() || !ply:IsPlayer(ply)) then return end + + gmInte.http.post("/players/" .. ply:SteamID64() .. "/name", + { + ["player"] = gmInte.getPlayerFormat(ply), + ["oldName"] = oldName, + ["newName"] = newName, + } + ) +end + +// +// Hooks +// + +hook.Add("onPlayerChangedName", "gmInte:PlayerChangeName", function(ply, old, new) + gmInte.playerChangeName(ply, old, new) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_sync_roles.lua b/lua/gmod_integration/server/sv_sync_roles.lua new file mode 100644 index 0000000..f965fab --- /dev/null +++ b/lua/gmod_integration/server/sv_sync_roles.lua @@ -0,0 +1,101 @@ +// +// Websocket +// + +local cachedPlayers = {} + +function gmInte.wsPlayerUpdateGroup(data) + if (cachedPlayers[steamID64] == data.group) then return end + + data.steamID = util.SteamIDFrom64(data.steamID64) + data.group = data.add && data.group || "user" + + cachedPlayers[data.steamID64] = data.group + + local ply = player.GetBySteamID(data.steamID) + if (ply && ply:IsValid()) then + ply:SetUserGroup(data.group) + end + + // ULX + if (ULib) then + ULib.ucl.addUser(data.steamID, nil, nil, data.group) + end + + // ServerGuard + if (serverguard) then + local ply = player.GetBySteamID(data.steamID) + if (ply) then + local rankData = serverguard.ranks:GetRank(data.group) + serverguard.player:SetRank(ply, data.group) + serverguard.player:SetImmunity(ply, rankData.immunity) + serverguard.player:SetTargetableRank(ply, rankData.targetable) + serverguard.player:SetBanLimit(ply, rankData.banlimit) + else + serverguard.player:SetRank(data.steamID, data.group) + end + end + + // Evolve + if (evolve) then + evolve:RankPlayer(data.steamID64, data.group) + end + + // SAM + if (SAM) then + SAM:PlayerSetRank(data.steamID64, data.group) + end + + // sam (wtf another one?) + if (sam) then + sam.player.setRank(data.steamID64, data.group) + end + + // xAdmin + if (xAdmin) then + xAdmin.SetRank(data.steamID64, data.group) + end + + // maestro + if (maestro) then + maestro.userrank(data.steamID64, data.group) + end + + // D3A + if (D3A) then + D3A.Ranks.SetSteamIDRank(data.steamID, data.group) + end + + // Mercury + if (Mercury) then + RunConsoleCommand("hg", "setrank", data.steamID, data.group) + end + + // FAdmin + if (FAdmin) then + RunConsoleCommand("fadmin", "setaccess", data.steamID, data.group) + end + + gmInte.log("[Sync Role] Player " .. data.steamID .. " has been updated to group " .. data.group) +end + +function gmInte.playerChangeGroup(steamID64, oldGroup, newGroup) + if (cachedPlayers[steamID64] == newGroup) then return end + cachedPlayers[steamID64] = newGroup + + gmInte.http.post("/players/" .. steamID64 .. "/group", { + ["player"] = gmInte.getPlayerFormat(ply), + ["oldGroup"] = oldGroup || "user", + ["newGroup"] = newGroup + }) +end + +hook.Add('CAMI.PlayerUsergroupChanged', 'gmInte:SyncChat:CAMI:PlayerUsergroupChanged', function(ply, old, new) + if (ply:IsBot() || !ply:IsValid()) then return end + gmInte.playerChangeGroup(ply:SteamID64(), old, new) +end) + +hook.Add('CAMI.SteamIDUsergroupChanged', 'gmInte:SyncChat:CAMI:SteamIDUsergroupChanged', function(SteamID64, old, new) + if (string.StartWith(SteamID64, "STEAM_")) then SteamID64 = util.SteamIDTo64(SteamID64) end + gmInte.playerChangeGroup(SteamID64, old, new) +end) \ No newline at end of file diff --git a/lua/gmod_integration/server/sv_tokens.lua b/lua/gmod_integration/server/sv_tokens.lua new file mode 100644 index 0000000..250b744 --- /dev/null +++ b/lua/gmod_integration/server/sv_tokens.lua @@ -0,0 +1,50 @@ +// +// Methods +// + +gmInte.serverPublicToken = gmInte.serverPublicToken || nil +function gmInte.getPublicServerToken(callback) + if (gmInte.serverPublicToken) then + if (callback) then callback(gmInte.serverPublicToken) end + return + end + + gmInte.http.get("/public-token", function(code, data) + gmInte.serverPublicToken = data.publicTempToken + callback(data.publicTempToken) + end) +end + +hook.Add("Initialize", "gmInte:Server:Initialize:GetPublicToken", function() + timer.Simple(1, function() + gmInte.getPublicServerToken(function(publicToken) + gmInte.log("Server Public Token Received: " .. publicToken) + end) + end) +end) + +gmInte.serverPlayerTempTokens = gmInte.serverPlayerTempTokens || {} +function gmInte.getPlayerTempToken(ply, callback) + if (gmInte.serverPlayerTempTokens[ply:SteamID64()] && gmInte.serverPlayerTempTokens[ply:SteamID64()].userID == ply:UserID()) then + if (callback) then callback(gmInte.serverPlayerTempTokens[ply:SteamID64()].token) end + return + end + + gmInte.getPublicServerToken(function(publicToken) + local token = util.SHA256(ply:SteamID64() .. "-" .. publicToken .. "-" .. gmInte.config.token .. "-" .. ply:UserID()) .. " " .. ply:UserID() + gmInte.serverPlayerTempTokens[ply:SteamID64()] = { token = token, userID = ply:UserID() } + callback(token) + end) +end + +function sendPlayerToken(ply) + gmInte.getPlayerTempToken(ply, function(token) + gmInte.SendNet("savePlayerToken", { + token = token, + }, ply) + end) +end + +hook.Add("gmInte:PlayerReady", "gmInte:Verif:PlayerReady", function(ply) + sendPlayerToken(ply) +end) \ No newline at end of file diff --git a/lua/gmod_integration/shared/sh_api_format.lua b/lua/gmod_integration/shared/sh_api_format.lua new file mode 100644 index 0000000..df07d83 --- /dev/null +++ b/lua/gmod_integration/shared/sh_api_format.lua @@ -0,0 +1,74 @@ +function gmInte.getPlayerFormat(ply) + if (!IsValid(ply) || !ply:IsPlayer()) then return end + return { + ["steamID"] = ply:SteamID(), + ["steamID64"] = ply:SteamID64(), + ["userGroup"] = ply:GetUserGroup(), + ["team"] = gmInte.getTeamFormat(ply:Team()), + ["name"] = ply:Nick(), + ["kills"] = ply:Frags(), + ["deaths"] = ply:Deaths(), + ["customValues"] = ply:gmIntGetCustomValues(), + ["connectTime"] = math.Round(RealTime() - ply:gmIntGetConnectTime()), + ["ping"] = ply:Ping(), + ["position"] = gmInte.getVectorFormat(ply:GetPos()), + ["angle"] = gmInte.getAngleFormat(ply:EyeAngles()) + } +end + +function gmInte.getServerFormat() + return { + ["hostname"] = GetHostName(), + ["ip"] = game.GetIPAddress(), + ["port"] = GetConVar("hostport"):GetInt(), + ["map"] = game.GetMap(), + ["players"] = #player.GetAll(), + ["maxPlayers"] = game.MaxPlayers(), + ["gameMode"] = engine.ActiveGamemode(), + ["uptime"] = math.Round(RealTime()) + } +end + +function gmInte.getWeaponFormat(weapon) + if (!IsValid(weapon) || !weapon:IsWeapon()) then return end + return { + ["class"] = weapon:GetClass(), + ["printName"] = weapon:GetPrintName() + } +end + +function gmInte.getEntityFormat(ent) + if (!IsValid(ent)) then return end + return { + ["class"] = ent:GetClass(), + ["model"] = ent:GetModel(), + ["position"] = gmInte.getVectorFormat(ent:GetPos()), + ["angle"] = gmInte.getAngleFormat(ent:GetAngles()) + } +end + +function gmInte.getVectorFormat(vec) + if (!isvector(vec)) then return end + return { + ["x"] = vec.x, + ["y"] = vec.y, + ["z"] = vec.z + } +end + +function gmInte.getAngleFormat(ang) + if (!isangle(ang)) then return end + return { + ["p"] = ang.p, + ["y"] = ang.y, + ["r"] = ang.r + } +end + +function gmInte.getTeamFormat(teamID) + if (!isnumber(teamID)) then return end + return { + ["id"] = teamID, + ["name"] = team.GetName(teamID) + } +end \ No newline at end of file diff --git a/lua/gmod_integration/shared/sh_errors.lua b/lua/gmod_integration/shared/sh_errors.lua new file mode 100644 index 0000000..95c8359 --- /dev/null +++ b/lua/gmod_integration/shared/sh_errors.lua @@ -0,0 +1,39 @@ +// +// Methods +// + +function gmInte.sendLuaErrorReport(err, realm, stack, name, id, uptime) + if (name != "gmod_integration") then return end + + if (SERVER && math.Round(RealTime()) == 0) then + return timer.Simple(1, function() + gmInte.sendLuaErrorReport(err, realm, stack, name, id, math.Round(RealTime())) + end) + end + + if (CLIENT && (!IsValid(LocalPlayer()) || !gmInte.config.token)) then + return timer.Simple(1, function() + gmInte.sendLuaErrorReport(err, realm, stack, name, id, math.Round(RealTime())) + end) + end + + gmInte.http.post("/errors", + { + ["error"] = err, + ["realm"] = realm, + ["stack"] = stack, + ["name"] = name, + ["id"] = id, + ["uptime"] = uptime || math.Round(RealTime()), + ["identifier"] = SERVER && gmInte.config.id || LocalPlayer():SteamID64() + } + ) +end + +// +// Hooks +// + +hook.Add("OnLuaError", "gmInte:OnLuaError:SendReport", function(err, realm, stack, name, id) + gmInte.sendLuaErrorReport(err, realm, stack, name, id) +end) \ No newline at end of file diff --git a/lua/gmod_integration/shared/sh_http.lua b/lua/gmod_integration/shared/sh_http.lua index b592152..fd848b5 100644 --- a/lua/gmod_integration/shared/sh_http.lua +++ b/lua/gmod_integration/shared/sh_http.lua @@ -1,126 +1,144 @@ +local apiVersion = "v3" +gmInte.http = gmInte.http || {} + // // HTTP // -local failMessage = { - ["401"] = "Bad Credentials", - ["403"] = "Forbidden", - ["404"] = "Not Found", - ["500"] = "Internal Server Error", - ["503"] = "Service Unavailable", - ["504"] = "Gateway Timeout", -} +local function getAPIURL(endpoint) + local url = "https://" .. gmInte.config.apiFQDN .. "/" .. apiVersion -local function errorMessage(body, code) - if (body && body.error) then - if (failMessage[code]) then - return failMessage[code] - else - return body.error - end - elseif (failMessage[code]) then - return failMessage[code] + if (SERVER) then + url = url .. "/servers/" .. gmInte.config.id else - return code - end -end + if (string.sub(endpoint, 1, 8) == "/users") then + return url .. endpoint + end -local function getAPIURL() - local url = "https://api.gmod-integration.com" - local devURL = "https://dev-api.gmod-integration.com" - - if (!gmInte.config.debug) then return url end - if (gmInte.config.devInstance) then - gmInte.log("Using dev Instance", true) - return devURL + url = url .. "/clients/" .. LocalPlayer():SteamID64() .. "/servers/" .. gmInte.config.id end - return url + return url .. endpoint end -local function sendHTTP(params) - // Log the HTTP request - gmInte.log("HTTP Request: " .. params.method .. " " .. params.endpoint, true) - gmInte.log("HTTP Body: " .. (params.body || "No body"), true) +local function showableBody(endpoint) + // if start with /streams or /screenshots return false + if (string.sub(endpoint, 1, 8) == "/streams" || string.sub(endpoint, 1, 12) == "/screenshots") then + return false + end - // Send the HTTP request + return true +end + +local function genRequestID() + return "gmInte-" .. util.CRC(tostring(SysTime())) +end + +function gmInte.http.requestAPI(params) + local body = params.body && util.TableToJSON(params.body || {}) || "" + local bodyLength = string.len(body) + local token = params.token || gmInte.config.token || "" + local url = getAPIURL(params.endpoint) + local method = params.method + local success = params.success || function() end + local failed = params.failed || function() if (!gmInte.config.debug) then gmInte.log("HTTP Failed, if this error persists please contact support") end end + local version = gmInte.config.version + local showableBody = showableBody(params.endpoint) + local requestID = genRequestID() + + local headers = { + ["Content-Type"] = "application/json", + ["Content-Length"] = bodyLength, + ["Authorization"] = "Bearer " .. token, + ["Version"] = version + } + local type = "application/json" + + // Log + gmInte.log("HTTP FQDN: " .. gmInte.config.apiFQDN, true) + gmInte.log("HTTP Request ID: " .. requestID, true) + gmInte.log("HTTP Request: " .. method .. " " .. url, true) + gmInte.log("HTTP Body: " .. (showableBody && body || "HIDDEN"), true) + + // Send HTTP({ - url = getAPIURL() .. params.endpoint, - method = params.method, - headers = { - ["Content-Type"] = "application/json", - ["Content-Length"] = params.body && string.len(params.body) || 0, - ["id"] = gmInte.config.id, - ["token"] = gmInte.config.token, - ["version"] = gmInte.version - }, - body = params.body && params.body || "", - type = "application/json", - success = function(code, body, headers) - // Log the HTTP response + ["url"] = url, + ["method"] = method, + ["headers"] = headers, + ["body"] = body, + ["type"] = type, + ["success"] = function(code, body, headers) + // Log + gmInte.log("HTTP Request ID: " .. requestID, true) gmInte.log("HTTP Response: " .. code, true) if (gmInte.config.debug) then gmInte.log("HTTP Body: " .. body, true) end - // if body and is json extract it - if (body && string.sub(headers["Content-Type"], 1, 16) == "application/json") then - body = util.JSONToTable(body) + // if not application/json return failed + if (string.sub(headers["Content-Type"], 1, 16) != "application/json") then + gmInte.log("HTTP Failed: Invalid Content-Type", true) + return failed({ ["error"] = "Invalid Content-Type" }, code, headers) end - // Check if the request was successful - if (string.sub(code, 1, 1) == "2") then - if (params.success) then - params.success(code, body, headers) - else - gmInte.log("HTTP Request Successful", true) - end - else - if (params.failed) then - params.failed(code, body, headers) - else - gmInte.logError(errorMessage(body, code)) - end + // Parse body + body = util.JSONToTable(body || "{}") + + // if not 2xx return failed + if (code < 200 || code >= 300) then + return failed(code, body, headers) end + + // Return success + return success(code, body) end, - failed = function(error) - gmInte.logError(error) + ["failed"] = function(error) + // Log + gmInte.log("HTTP Request ID: " .. requestID, true) + gmInte.log("HTTP Failed: " .. error, true) + + // Return failed + return failed({ ["error"] = error }) end }) end -function gmInte.get(endpoint, onSuccess, onFailed) - sendHTTP({ - endpoint = endpoint, - method = "GET", - success = onSuccess, - failed = onFailed +// +// HTTP Methods +// + +function gmInte.http.get(endpoint, onSuccess, onFailed) + gmInte.http.requestAPI({ + ["endpoint"] = endpoint, + ["method"] = "GET", + ["success"] = onSuccess, + ["failed"] = onFailed }) end -function gmInte.post(endpoint, data, onSuccess, onFailed) - sendHTTP({ - endpoint = endpoint, - method = "POST", - body = util.TableToJSON(data), - success = onSuccess, - failed = onFailed +function gmInte.http.post(endpoint, data, onSuccess, onFailed) + gmInte.http.requestAPI({ + ["endpoint"] = endpoint, + ["method"] = "POST", + ["body"] = data, + ["success"] = onSuccess, + ["failed"] = onFailed }) end -function gmInte.put(endpoint, data, onSuccess, onFailed) - sendHTTP({ - endpoint = endpoint, - method = "PUT", - body = util.TableToJSON(data), - success = onSuccess, - failed = onFailed +function gmInte.http.put(endpoint, data, onSuccess, onFailed) + gmInte.http.requestAPI({ + ["endpoint"] = endpoint, + ["method"] = "PUT", + ["body"] = data, + ["success"] = onSuccess, + ["failed"] = onFailed }) end -function gmInte.delete(endpoint, onSuccess, onFailed) - sendHTTP({ - endpoint = endpoint, - method = "DELETE", - success = onSuccess, - failed = onFailed +function gmInte.http.delete(endpoint, onSuccess, onFailed) + gmInte.http.requestAPI({ + ["endpoint"] = endpoint, + ["method"] = "DELETE", + ["success"] = onSuccess, + ["failed"] = onFailed }) end \ No newline at end of file diff --git a/lua/gmod_integration/shared/sh_player_meta.lua b/lua/gmod_integration/shared/sh_player_meta.lua new file mode 100644 index 0000000..c4fcae1 --- /dev/null +++ b/lua/gmod_integration/shared/sh_player_meta.lua @@ -0,0 +1,72 @@ +// +// Meta +// + +local ply = FindMetaTable("Player") + +function ply:gmIntGetConnectTime() + return self.gmIntTimeConnect || 0 +end + +function ply:gmIntSetCustomValue(key, value) + self.gmIntCustomValues = self.gmIntCustomValues || {} + self.gmIntCustomValues[key] = value +end + +function ply:gmIntGetCustomValue(key) + return self.gmIntCustomValues && self.gmIntCustomValues[key] +end + +function ply:gmIntRemoveCustomValue(key) + if (self.gmIntCustomValues) then + self.gmIntCustomValues[key] = nil + end +end + +// +// Compatibility +// + +local function getCustomCompatability(ply) + local values = {} + + // DarkRP + if (DarkRP) then + values.money = ply:getDarkRPVar("money") + values.job = ply:getDarkRPVar("job") + end + + // GUI Level System + if (GUILevelSystem) then + values.level = ply:GetLevel() + values.xp = ply:GetXP() + end + + return values +end + +// +// Methods +// + +local function getCustomValues(ply) + local values = {} + + // Get compatability values + for key, value in pairs(getCustomCompatability(ply)) do + values[key] = value + end + + // Get custom values or overwrite compatability values + if (ply.gmIntCustomValues) then + for key, value in pairs(ply.gmIntCustomValues) do + values[key] = value + end + end + + return values +end + +function ply:gmIntGetCustomValues() + return getCustomValues(self) +end \ No newline at end of file diff --git a/lua/gmod_integration/sv_config.lua b/lua/gmod_integration/sv_config.lua index b365912..6a7290e 100644 --- a/lua/gmod_integration/sv_config.lua +++ b/lua/gmod_integration/sv_config.lua @@ -1,6 +1,6 @@ /* Informations: - This file is prioritized over the configuration file in data/gmod-integration/config.json until the id are set. + This file is prioritized over the configuration file in data/gmod-integration/config.json if id and token are set in this file. We don't recommend to use a static version of our addon, you should use the workshop version instead. Add Server: @@ -25,50 +25,31 @@ // API Connection gmInte.config.id = "" // Server ID gmInte.config.token = "" // Server Token - -// Websocket -/* - This is a premium feature, you can buy premium on our website: https://gmod-integration.com/premium - Websocket allow you to made a real-time connection between your server and our servers. - And so use the real-time features of our addon (like the chat syncronization, role syncronization, ...) -*/ -gmInte.config.websocket = false // If true, the addon will use the websocket instead of the http requests +gmInte.config.websocketFQDN = "ws.gmod-integration.com" // The FQDN of the websocket server +gmInte.config.apiFQDN = "api.gmod-integration.com" // The FQDN of the API server // Other gmInte.config.forcePlayerLink = false // If true, the addon will force the players to link their discord account to their steam account before playing gmInte.config.supportLink = "" // The link of your support (shown when a player do not have the requiments to join the server) -gmInte.config.logBotActions = false // If true, the addon will log the messages of the bot in the console +gmInte.config.maintenance = false // If true, the addon will only allow the players with the "gmod-integration.maintenance" permission to join the server // // Syncronization // -// General -gmInte.config.syncChat = false // If true, the addon will sync the chat gmod with a selected channel on discord (need to be enabled on the dashboard) -gmInte.config.syncPlayerStat = true // If true, the addon will sync the player stats (kills, deaths, playtime, ...) - // Punishment gmInte.config.syncBan = true // If true, the addon will sync gmod bans with discord bans (and vice versa) gmInte.config.syncTimeout = false // If true, the addon will sync gmod timeouts with discord timeouts (and vice versa) gmInte.config.syncKick = false // If true, the addon will sync gmod kicks with discord kicks (and vice versa) -// -// Player Filter -// - -// Trust Factor -gmInte.config.minimalTrust = 30 // The minimal trust factor of an user to be able to join the server (0 to 100) -gmInte.config.filterOnTrust = true // If true, the addon will filter the players according to their trust factor - // Ban gmInte.config.filterOnBan = true // If true, the addon will filter the players according to their ban status // -// Features Kill Switch +// Materials // -// Will disable the features of the addon -gmInte.config.sendLog = false // Disable the logs +gmInte.config.redownloadMaterials = false // If true, the addon will redownload the materials of the addon (useful if you have a problem with the materials) // // Debug & Development