Skip to content

Commit a3fc78a

Browse files
committed
Initial commit
0 parents  commit a3fc78a

File tree

11 files changed

+382
-0
lines changed

11 files changed

+382
-0
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# JetBrains
2+
.idea/
3+
*.iml
4+
*.iws
5+
6+
# Visual Studio Code
7+
.vscode/
8+
.history
9+
10+
# Mac
11+
.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Azuriom
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# AzLink-GMod
2+
3+
[![Latest release](https://img.shields.io/github/v/release/Azuriom/AzLink-GMod?style=flat-square)](https://github.com/Azuriom/AzLink-GMod/releases)
4+
[![Chat](https://img.shields.io/discord/625774284823986183?color=5865f2&label=Discord&logo=discord&logoColor=fff&style=flat-square)](https://azuriom.com/discord)
5+
6+
AzLink-GMod is a [Garry's Mod](https://gmod.facepunch.com/) addon to link a Garry's Mod server with Azuriom.
7+
8+
## Download
9+
10+
You can download the plugin on [Azuriom's website](https://azuriom.com/azlink) or in the [GitHub releases](https://github.com/Azuriom/AzLink-GMod/releases).
11+
12+
## Contributing
13+
14+
This project follows the [CFC Glua Style Guidelines](https://github.com/CFC-Servers/cfc_glua_style_guidelines).
15+
16+
## Dependencies
17+
18+
This addon uses [gmsv_serverstat](https://github.com/WilliamVenner/gmsv_serverstat) as an optional dependency to get
19+
process resources usages.

glualint.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lint_unusedParameters": true,
3+
"lint_unusedLoopVars": true,
4+
"lint_spaceAfterParens": true,
5+
"lint_spaceAfterBraces": true,
6+
"prettyprint_minimizeParens": true,
7+
"prettyprint_spaceAfterParens": true,
8+
"prettyprint_spaceAfterBraces": true,
9+
"prettyprint_spaceAfterLabel": true
10+
}

lua/autorun/azlink_autorun.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
if SERVER then
2+
include( "azlink/azlink.lua" )
3+
include( "azlink/logger/logger.lua" )
4+
include( "azlink/fetcher.lua" )
5+
include( "azlink/http/http_client.lua" )
6+
include( "azlink/http/promise.lua" )
7+
include( "azlink/commands/azlink_command.lua" )
8+
end

lua/azlink/azlink.lua

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
AZLINK_VERSION = "0.1.0" -- TODO
2+
AzLink = AzLink or { }
3+
AzLink.lastSent = 0
4+
AzLink.lastFullSent = 0
5+
AzLink.config = { }
6+
7+
function AzLink:Fetch( force )
8+
local siteKey = AzLink.config.site_key
9+
local baseUrl = AzLink.config.url
10+
if siteKey == nil or baseUrl == nil or not force and RealTime( ) - AzLink.lastSent < 15 then return end
11+
local sendFull = os.date( "*t" ).min % 15 == 0 and RealTime( ) - AzLink.lastFullSent > 60
12+
AzLink.lastSent = RealTime( )
13+
14+
if sendFull then
15+
AzLink.lastFullSent = RealTime( )
16+
end
17+
18+
return AzLink.Fetcher:Run( sendFull )
19+
end
20+
21+
function AzLink:Ping( )
22+
local siteKey = AzLink.config.site_key
23+
local baseUrl = AzLink.config.url
24+
if siteKey == nil or baseUrl == nil then return nil end
25+
26+
return AzLink.HttpClient:Request( "GET", "", data ):Catch( function( error, status )
27+
if status == nil then
28+
AzLink.Logger:Error( "Unable to ping: " .. error )
29+
else
30+
local errorMessage = ( error.message or error ) .. " (" .. status .. ")"
31+
AzLink.Logger:Error( "An HTTP error occured during ping: " .. errorMessage )
32+
end
33+
end )
34+
end
35+
36+
function AzLink:GetServerData( full )
37+
local players = { }
38+
39+
for _, player in ipairs( player.GetHumans( ) ) do
40+
table.insert( players, {
41+
["name"] = player:Nick( ),
42+
["uid"] = player:SteamID64( ),
43+
} )
44+
end
45+
46+
local baseData = {
47+
["platform"] = {
48+
["type"] = "GMOD",
49+
["name"] = "Garry's Mod",
50+
["version"] = VERSIONSTR,
51+
},
52+
["version"] = AZLINK_VERSION,
53+
["players"] = players,
54+
["maxPlayers"] = game.MaxPlayers( ),
55+
["full"] = full,
56+
}
57+
58+
if not full then return baseData end
59+
60+
baseData.worlds = {
61+
["entities"] = ents.GetCount( ),
62+
}
63+
64+
if serverstat ~= nil then
65+
baseData.system = {
66+
["cpu"] = serverstat.ProcessCPUUsage( ) * 100.0,
67+
["ram"] = serverstat.ProcessMemoryUsage( ),
68+
}
69+
end
70+
71+
return baseData
72+
end
73+
74+
function AzLink:SaveConfig( )
75+
file.Write( "azlink/config.json", util.TableToJSON( AzLink.config ) )
76+
end
77+
78+
MsgN( "[AzLink] Starting AzLink v" .. AZLINK_VERSION .. "..." )
79+
80+
hook.Add( "Initialize", "azlink-init", function( )
81+
timer.Create( "azlink-fetcher-task", 60, 0, function( )
82+
AzLink:Fetch( )
83+
end )
84+
85+
timer.Start( "azlink-fetcher-task" )
86+
end )
87+
88+
file.CreateDir( "azlink" )
89+
local rawConfig = file.Read( "azlink/config.json" )
90+
91+
if rawConfig ~= nil then
92+
AzLink.config = util.JSONToTable( rawConfig )
93+
end
94+
95+
if #file.Find( "lua/bin/gmsv_serverstat*", "GAME" ) == 0 or not pcall( require, "serverstat" ) then
96+
MsgN( "[AzLink] Unable to load serverstat, please install the module by following" )
97+
MsgN( "these instructions: https://github.com/WilliamVenner/gmsv_serverstat#installation" )
98+
end
99+
100+
MsgN( "[AzLink] AzLink successfully enabled." )
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
local function setupAzLink( baseUrl, siteKey )
2+
AzLink.config.url = baseUrl
3+
AzLink.config.site_key = siteKey
4+
5+
AzLink:Ping( ):Then( function( )
6+
AzLink.Logger:Info( "Linked to the website successfully." )
7+
AzLink:SaveConfig( )
8+
end )
9+
end
10+
11+
concommand.Add( "azlink", function( _, _, args )
12+
if args[2] == "setup" then
13+
if args[3] == nil or args[4] == nil then
14+
AzLink.Logger:Error( "Mising URL and/or site key!" )
15+
16+
return
17+
end
18+
19+
-- TODO find a cleaner way to support column?
20+
setupAzLink( args[3]:gsub( "!", ":" ):gsub( "|", "/" ), args[4] )
21+
22+
return
23+
end
24+
25+
if args[2] == "status" then
26+
local promise = AzLink:Ping( )
27+
28+
if promise == nil then
29+
AzLink.Logger:Error( "No URL and/or site key configured yet!" )
30+
end
31+
32+
promise:Then( function( )
33+
AzLink.Logger:Info( "Connected to the website successfully." )
34+
end )
35+
36+
return
37+
end
38+
39+
if args[2] == "fetch" then
40+
AzLink:Fetch( true ):Then( function( )
41+
AzLink.Logger:Info( "Data has been fetched successfully." )
42+
end )
43+
44+
return
45+
end
46+
47+
AzLink.Logger:Info( "Uknown subcommand. Must be 'setup', 'status' or 'fetch'." )
48+
end )

lua/azlink/fetcher.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
local fetcher = { }
2+
3+
function fetcher:Run( sendFull )
4+
local data = AzLink:GetServerData( sendFull )
5+
6+
return AzLink.HttpClient:Request( "POST", "", data ):Then( function( commands )
7+
self:DispatchCommands( commands )
8+
end ):Catch( function( error, status )
9+
if status == nil then
10+
AzLink.Logger:Error( "A connection error occured: " .. error )
11+
else
12+
local errorMessage = ( error.message or error ) .. " (" .. status .. ")"
13+
AzLink.Logger:Error( "An HTTP error occured: " .. errorMessage )
14+
end
15+
end )
16+
end
17+
18+
function fetcher:DispatchCommands( data )
19+
if not data.commands then return end
20+
local total = 0
21+
22+
for _, commandData in pairs( data.commands ) do
23+
local player = player.GetBySteamID64( commandData.uid )
24+
local playerName = player and player:Nick( ) or commandData.name
25+
local steamId32 = player and player:SteamID() or commandData.steamid_32
26+
27+
for _, command in pairs( commandData.values ) do
28+
local display = playerName .. " (" .. commandData.uid .. ")"
29+
local playerCommand = command:gsub( "{player}", playerName )
30+
playerCommand = playerCommand:gsub( "{steam_id}", commandData.uid )
31+
playerCommand = playerCommand:gsub( "{steam_id_32}", steamId32 )
32+
AzLink.Logger:Info( "Dispatching command to " .. display .. ": " .. playerCommand )
33+
game.ConsoleCommand( playerCommand .. "\n" )
34+
end
35+
end
36+
37+
if total > 0 then
38+
AzLink.Logger:Info( "Dispatched commands to " .. total .. " players." )
39+
end
40+
end
41+
42+
AzLink.Fetcher = fetcher

lua/azlink/http/http_client.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
local httpClient = {
2+
timeout = 5000
3+
}
4+
5+
function httpClient:Request( requestMethod, endpoint, data )
6+
return AzLink.Promise( function( onResolve, onReject )
7+
HTTP( {
8+
method = requestMethod,
9+
url = AzLink.config.url .. "/api/azlink" .. endpoint,
10+
timeout = self.timeout,
11+
headers = {
12+
["Azuriom-Link-Token"] = AzLink.config.site_key,
13+
["Accept"] = "application/json",
14+
["User-Agent"] = "AzLink Garry's Mod - v" .. AZLINK_VERSION,
15+
},
16+
type = "application/json",
17+
body = data and util.TableToJSON( data ) or nil,
18+
success = function( code, body )
19+
body = body and util.JSONToTable( body ) or body
20+
21+
if code >= 300 then
22+
onReject( body, code )
23+
24+
return
25+
end
26+
27+
onResolve( body )
28+
end,
29+
failed = onReject,
30+
} )
31+
end )
32+
end
33+
34+
AzLink.HttpClient = httpClient

lua/azlink/http/promise.lua

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
-- Lua Promise implementation inspired by JavaScript's Promises
2+
local Promise = { }
3+
Promise.__index = Promise
4+
local PENDING = 0
5+
local RESOLVED = 1
6+
local REJECTED = -1
7+
8+
function Promise:new( resolver )
9+
local target = {
10+
state = PENDING,
11+
queue = { },
12+
handlers = { },
13+
}
14+
15+
setmetatable( target, Promise )
16+
17+
local resolve = function( ... )
18+
if target.state ~= PENDING then return end
19+
20+
for _, callback in pairs( target.queue ) do
21+
callback( ... )
22+
end
23+
24+
target.values = { ... }
25+
26+
target.state = RESOLVED
27+
end
28+
29+
local reject = function( ... )
30+
if target.state ~= PENDING then return end
31+
32+
for _, callback in pairs( target.handlers ) do
33+
callback( ... )
34+
end
35+
36+
target.values = { ... }
37+
38+
target.state = REJECTED
39+
end
40+
41+
resolver( resolve, reject )
42+
43+
return target
44+
end
45+
46+
function Promise:Then( callback )
47+
if self.state == PENDING then
48+
table.insert( self.queue, callback )
49+
elseif self.state == RESOLVED then
50+
callback( self.values )
51+
end
52+
53+
return self
54+
end
55+
56+
function Promise:Catch( callback )
57+
if self.state == PENDING then
58+
table.insert( self.handlers, callback )
59+
elseif self.state == RESOLVED then
60+
callback( self.values )
61+
end
62+
63+
return self
64+
end
65+
66+
setmetatable( Promise, {
67+
__call = Promise.new
68+
} )
69+
70+
AzLink.Promise = Promise

0 commit comments

Comments
 (0)