· 9 years ago · Oct 21, 2016, 02:44 PM
1#pragma semicolon 1
2
3#include <sourcemod>
4#include <geoip>
5#undef REQUIRE_EXTENSIONS
6#include <steamtools>
7#include <geoipcity>
8
9#define PLUGIN_VERSION "1.3.1"
10
11enum OS {
12 OS_Unknown = -1,
13 OS_Windows = 0,
14 OS_Mac = 1,
15 OS_Linux = 2,
16 OS_Total = 3
17};
18
19public Plugin:myinfo = {
20 name = "[ANY] Player Analytics",
21 author = "Dr. McKay",
22 description = "Logs analytical data about connecting players",
23 version = PLUGIN_VERSION,
24 url = "http://www.doctormckay.com"
25};
26
27//#define DEBUG
28
29#if !defined DEBUG
30new Handle:g_DB;
31#endif
32new bool:g_SteamTools;
33new String:g_IP[64];
34new String:g_GameFolder[64];
35new Handle:g_OSGamedata;
36new String:g_OSConVar[OS_Total][64];
37
38new g_ConnectTime[MAXPLAYERS + 1];
39new g_NumPlayers[MAXPLAYERS + 1];
40new g_RowID[MAXPLAYERS + 1] = {-1, ...};
41new String:g_ConnectMethod[MAXPLAYERS + 1][64];
42new g_MOTDDisabled[MAXPLAYERS + 1] = {-1, ...};
43new Handle:g_MOTDTimer[MAXPLAYERS + 1];
44new OS:g_OS[MAXPLAYERS + 1];
45new Handle:g_OSTimer[MAXPLAYERS + 1];
46new g_OSQueries[MAXPLAYERS + 1];
47
48#define STEAMTOOLS_AVAILABLE() (g_SteamTools && GetFeatureStatus(FeatureType_Native, "Steam_IsConnected") == FeatureStatus_Available)
49
50#if !defined DEBUG
51#define UPDATE_FILE "player_analytics.txt"
52#define CONVAR_PREFIX "player_analytics"
53
54#include "mckayupdater.sp"
55#endif
56
57public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) {
58 MarkNativeAsOptional("Steam_GetNumClientSubscriptions");
59 MarkNativeAsOptional("Steam_GetClientSubscription");
60 MarkNativeAsOptional("Steam_GetNumClientDLCs");
61 MarkNativeAsOptional("Steam_GetClientDLC");
62 MarkNativeAsOptional("Steam_GetPublicIP");
63 MarkNativeAsOptional("Steam_IsConnected");
64
65#if !defined DEBUG
66 if(SQL_CheckConfig("player_analytics")) {
67 g_DB = SQL_Connect("player_analytics", true, error, err_max);
68 } else {
69 g_DB = SQL_Connect("default", true, error, err_max);
70 }
71
72 if(g_DB == INVALID_HANDLE) {
73 return APLRes_Failure;
74 }
75
76 SQL_SetCharset(g_DB, "utf8");
77 SQL_TQuery(g_DB, OnTableCreated, "CREATE TABLE IF NOT EXISTS `player_analytics` (id int(11) NOT NULL AUTO_INCREMENT, server_ip varchar(32) NOT NULL, name varchar(32), auth varchar(32), connect_time int(11) NOT NULL, connect_date date NOT NULL, connect_method varchar(64) DEFAULT NULL, numplayers tinyint(4) NOT NULL, map varchar(64) NOT NULL, duration int(11) DEFAULT NULL, flags varchar(32) NOT NULL, ip varchar(32) NOT NULL, city varchar(45), region varchar(45), country varchar(45), country_code varchar(2), country_code3 varchar(3), premium tinyint(1), html_motd_disabled tinyint(1), os varchar(32), PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8");
78#endif
79
80 RegPluginLibrary("player_analytics");
81 CreateNative("PA_GetConnectionID", Native_GetConnectionID);
82
83 return APLRes_Success;
84}
85
86#if !defined DEBUG
87public OnTableCreated(Handle:owner, Handle:hndl, const String:error[], any:data) {
88 if(hndl == INVALID_HANDLE) {
89 SetFailState("Unable to create table. %s", error);
90 }
91}
92#endif
93
94public OnPluginStart() {
95 if(!g_SteamTools || !Steam_IsConnected()) {
96 new ip = GetConVarInt(FindConVar("hostip"));
97 Format(g_IP, sizeof(g_IP), "%d.%d.%d.%d:%d", ((ip & 0xFF000000) >> 24) & 0xFF, ((ip & 0x00FF0000) >> 16) & 0xFF, ((ip & 0x0000FF00) >> 8) & 0xFF, ((ip & 0x000000FF) >> 0) & 0xFF, GetConVarInt(FindConVar("hostport")));
98 }
99
100 GetGameFolderName(g_GameFolder, sizeof(g_GameFolder));
101 g_OSGamedata = LoadGameConfigFile("detect_os.games");
102 if(g_OSGamedata == INVALID_HANDLE) {
103 LogError("Failed to load gamedata file detect_os.games.txt: client operating system data will be unavailable.");
104 } else {
105 GameConfGetKeyValue(g_OSGamedata, "Convar_Windows", g_OSConVar[OS_Windows], sizeof(g_OSConVar[]));
106 GameConfGetKeyValue(g_OSGamedata, "Convar_Mac", g_OSConVar[OS_Mac], sizeof(g_OSConVar[]));
107 GameConfGetKeyValue(g_OSGamedata, "Convar_Linux", g_OSConVar[OS_Linux], sizeof(g_OSConVar[]));
108 }
109}
110
111public Steam_SteamServersConnected() {
112 new octets[4];
113 Steam_GetPublicIP(octets);
114 Format(g_IP, sizeof(g_IP), "%d.%d.%d.%d:%d", octets[0], octets[1], octets[2], octets[3], GetConVarInt(FindConVar("hostport")));
115}
116
117public Steam_FullyLoaded() {
118 g_SteamTools = true;
119}
120
121public Steam_Shutdown() {
122 g_SteamTools = false;
123}
124
125public OnClientConnected(client) {
126 if(IsFakeClient(client)) {
127 return;
128 }
129
130 g_MOTDDisabled[client] = -1;
131 g_ConnectTime[client] = GetTime();
132 g_NumPlayers[client] = GetRealClientCount();
133 g_RowID[client] = -1;
134 g_OS[client] = OS_Unknown;
135
136 decl String:buffer[30];
137 if(GetClientInfo(client, "cl_connectmethod", buffer, sizeof(buffer))) {
138#if !defined DEBUG
139 SQL_EscapeString(g_DB, buffer, g_ConnectMethod[client], sizeof(g_ConnectMethod[]));
140#else
141 strcopy(g_ConnectMethod[client], sizeof(g_ConnectMethod[]), buffer);
142#endif
143 Format(g_ConnectMethod[client], sizeof(g_ConnectMethod[]), "'%s'", g_ConnectMethod[client]);
144 } else {
145 strcopy(g_ConnectMethod[client], sizeof(g_ConnectMethod[]), "NULL");
146 }
147}
148
149public OnClientPutInServer(client) {
150 if(IsFakeClient(client)) {
151 return;
152 }
153
154 QueryClientConVar(client, "cl_disablehtmlmotd", OnMOTDQueried);
155 g_MOTDTimer[client] = CreateTimer(30.0, Timer_MOTDTimeout, GetClientUserId(client));
156
157 for(new i = 0; i < _:OS_Total; i++) {
158 QueryClientConVar(client, g_OSConVar[i], OnOSQueried);
159 }
160 g_OSTimer[client] = CreateTimer(30.0, Timer_OSTimeout, GetClientUserId(client));
161}
162
163public OnMOTDQueried(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) {
164 if(g_MOTDTimer[client] == INVALID_HANDLE) {
165 return; // Timed out
166 }
167
168 if(result == ConVarQuery_Okay) {
169 g_MOTDDisabled[client] = (bool:StringToInt(cvarValue)) ? 1 : 0;
170 } else {
171 g_MOTDDisabled[client] = -1;
172 }
173
174 CloseHandle(g_MOTDTimer[client]);
175 g_MOTDTimer[client] = INVALID_HANDLE;
176}
177
178public Action:Timer_MOTDTimeout(Handle:timer, any:userid) {
179 new client = GetClientOfUserId(userid);
180 if(client == 0) {
181 return;
182 }
183
184 g_MOTDDisabled[client] = -1;
185 g_MOTDTimer[client] = INVALID_HANDLE;
186}
187
188public OnOSQueried(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) {
189 if(g_OSTimer[client] == INVALID_HANDLE) {
190 return; // Timed out
191 }
192
193 if(result == ConVarQuery_NotFound) {
194 g_OSQueries[client]++;
195 if(g_OSQueries[client] >= _:OS_Total) {
196 CloseHandle(g_OSTimer[client]);
197 g_OSTimer[client] = INVALID_HANDLE;
198 }
199 return;
200 } else {
201 for(new i = 0; i < _:OS_Total; i++) {
202 if(StrEqual(cvarName, g_OSConVar[i])) {
203 g_OS[client] = OS:i;
204 break;
205 }
206 }
207
208 CloseHandle(g_OSTimer[client]);
209 g_OSTimer[client] = INVALID_HANDLE;
210 }
211}
212
213public Action:Timer_OSTimeout(Handle:timer, any:userid) {
214 new client = GetClientOfUserId(userid);
215 if(client == 0) {
216 return;
217 }
218
219 g_OSTimer[client] = INVALID_HANDLE;
220}
221
222public OnClientPostAdminCheck(client) {
223 if(IsFakeClient(client)) {
224 return;
225 }
226
227 CreateTimer(1.0, Timer_HandleConnect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
228}
229
230public Action:Timer_HandleConnect(Handle:timer, any:userid) {
231 new client = GetClientOfUserId(userid);
232 if(client == 0) {
233 return Plugin_Stop;
234 }
235
236 if(g_MOTDTimer[client] != INVALID_HANDLE || g_OSTimer[client] != INVALID_HANDLE || g_ConnectTime[client] == 0) {
237 return Plugin_Continue;
238 }
239
240 new String:date[64], String:map[64], AdminFlag:flags[32], String:flagstring[64], String:ip[64], String:city[45], String:region[45], String:country_name[45], String:country_code[3], String:country_code3[4];
241
242 new String:buffers[10][256];
243 FormatTime(date, sizeof(date), "%Y-%m-%d");
244 GetCurrentMap(map, sizeof(map));
245 GetClientName(client, buffers[0], sizeof(buffers[]));
246 GetClientAuthString(client, buffers[1], sizeof(buffers[]));
247 new num = FlagBitsToArray(GetUserFlagBits(client), flags, sizeof(flags));
248 for(new i = 0; i < num; i++) {
249 new flagchar;
250 FindFlagChar(flags[i], flagchar);
251 flagstring[i] = flagchar;
252 }
253 flagstring[num] = '\0';
254 GetClientIP(client, ip, sizeof(ip));
255
256 if(GetFeatureStatus(FeatureType_Native, "GeoipGetRecord") != FeatureStatus_Available || !GeoipGetRecord(ip, city, region, country_name, country_code, country_code3)) {
257 GeoipCountry(ip, country_name, sizeof(country_name));
258 }
259
260 strcopy(buffers[2], sizeof(buffers[]), city);
261 strcopy(buffers[3], sizeof(buffers[]), region);
262 strcopy(buffers[4], sizeof(buffers[]), country_name);
263 strcopy(buffers[5], sizeof(buffers[]), country_code);
264 strcopy(buffers[6], sizeof(buffers[]), country_code3);
265
266 if(STEAMTOOLS_AVAILABLE() && StrEqual(g_GameFolder, "tf")) {
267 if(Steam_CheckClientSubscription(client, 0) && !Steam_CheckClientDLC(client, 459)) {
268 strcopy(buffers[7], sizeof(buffers[]), "0");
269 } else {
270 strcopy(buffers[7], sizeof(buffers[]), "1");
271 }
272 }
273
274 if(g_MOTDDisabled[client] != -1) {
275 IntToString(g_MOTDDisabled[client], buffers[8], sizeof(buffers[]));
276 }
277
278 if(g_OS[client] == OS_Windows) {
279 strcopy(buffers[9], sizeof(buffers[]), "Windows");
280 } else if(g_OS[client] == OS_Mac) {
281 strcopy(buffers[9], sizeof(buffers[]), "MacOS");
282 } else if(g_OS[client] == OS_Linux) {
283 strcopy(buffers[9], sizeof(buffers[]), "Linux");
284 }
285
286 decl String:escapedBuffers[10][513];
287 for(new i = 0; i < sizeof(buffers); i++) {
288 if(strlen(buffers[i]) == 0) {
289 strcopy(escapedBuffers[i], sizeof(escapedBuffers[]), "NULL");
290 } else {
291#if !defined DEBUG
292 SQL_EscapeString(g_DB, buffers[i], escapedBuffers[i], sizeof(escapedBuffers[]));
293#endif
294 Format(escapedBuffers[i], sizeof(escapedBuffers[]), "'%s'", escapedBuffers[i]);
295 }
296 }
297
298 decl String:query[512];
299 Format(query, sizeof(query), "INSERT INTO `player_analytics` SET server_ip = '%s', name = %s, auth = %s, connect_time = %d, connect_date = '%s', connect_method = %s, numplayers = %d, map = '%s', flags = '%s', ip = '%s', city = %s, region = %s, country = %s, country_code = %s, country_code3 = %s, premium = %s, html_motd_disabled = %s, os = %s",
300 g_IP, escapedBuffers[0], escapedBuffers[1], g_ConnectTime[client], date, g_ConnectMethod[client], g_NumPlayers[client], map, flagstring, ip, escapedBuffers[2], escapedBuffers[3], escapedBuffers[4], escapedBuffers[5], escapedBuffers[6], escapedBuffers[7], escapedBuffers[8], escapedBuffers[9]);
301
302#if !defined DEBUG
303 SQL_TQuery(g_DB, OnRowInserted, query, GetClientUserId(client));
304#else
305 PrintToServer("%s", query);
306#endif
307 return Plugin_Stop;
308}
309
310GetRealClientCount() {
311 new total = 0;
312 for(new i = 1; i <= MaxClients; i++) {
313 if(IsClientConnected(i) && !IsFakeClient(i)) {
314 total++;
315 }
316 }
317 return total; // Note that this value will include the client who's connecting. If you want to get the number of players in-game when they actually initiated their connection, decrement this by one.
318}
319
320#if !defined DEBUG
321public OnRowInserted(Handle:owner, Handle:hndl, const String:error[], any:userid) {
322 new client = GetClientOfUserId(userid);
323 if(client == 0) {
324 return;
325 }
326
327 if(hndl == INVALID_HANDLE) {
328 LogError("Unable to insert row for client %L. %s", client, error);
329 return;
330 }
331
332 g_RowID[client] = SQL_GetInsertId(hndl);
333
334 new Handle:fwd = CreateGlobalForward("PA_OnConnectionLogged", ET_Ignore, Param_Cell, Param_Cell);
335 Call_StartForward(fwd);
336 Call_PushCell(client);
337 Call_PushCell(g_RowID[client]);
338 Call_Finish();
339 CloseHandle(fwd);
340}
341#endif
342
343public OnClientDisconnect(client) {
344 if(g_RowID[client] == -1 || g_ConnectTime[client] == 0) {
345 g_ConnectTime[client] = 0;
346 return;
347 }
348
349 decl String:query[256];
350 Format(query, sizeof(query), "UPDATE `player_analytics` SET duration = %d WHERE id = %d", GetTime() - g_ConnectTime[client], g_RowID[client]);
351#if !defined DEBUG
352 SQL_TQuery(g_DB, OnRowUpdated, query, g_RowID[client]);
353#else
354 PrintToServer("%s", query);
355#endif
356
357 g_ConnectTime[client] = 0;
358}
359
360#if !defined DEBUG
361public OnRowUpdated(Handle:owner, Handle:hndl, const String:error[], any:id) {
362 if(hndl == INVALID_HANDLE) {
363 LogError("Unable to update row %d. %s", id, error);
364 }
365}
366#endif
367
368public Native_GetConnectionID(Handle:plugin, numParams) {
369 new client = GetNativeCell(1);
370 if(client < 1 || client > MaxClients || !IsClientConnected(client) || IsFakeClient(client)) {
371 ThrowNativeError(SP_ERROR_PARAM, "Client index %d is invalid, not connected, or fake", client);
372 return -1;
373 }
374
375 return g_RowID[client];
376}