#pragma semicolon 1 #include #include #include new String:listOfChar[] = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789"; new Handle:g_hCvarTournament = INVALID_HANDLE; new Handle:g_hCvarKey = INVALID_HANDLE; new Handle:g_hCvarTitle = INVALID_HANDLE; public Plugin:myinfo = { name = "logs.tf uploader", author = "Nikki", description = "Adds a log auto uploader for tournament rounds", version = "1.0", url = "" }; public OnPluginStart() { g_hCvarKey = CreateConVar("sm_logupload_key", "", "Your logs.tf API key", FCVAR_PROTECTED); g_hCvarTitle = CreateConVar("sm_logupload_title", "Auto Uploaded Log", "Title to use on logs.tf", FCVAR_PROTECTED); g_hCvarTournament = FindConVar("mp_tournament"); // Win conditions met (maxrounds, timelimit) HookEvent("teamplay_game_over", Event_GameOver); // Win conditions met (windifference) HookEvent("tf_game_over", Event_GameOver); RegConsoleCmd("sm_forceupload", Command_ForceUpload, "Force a log upload"); } public Action:Command_ForceUpload(client, args) { ReplyToCommand(client, "Uploading log..."); SearchForLog(); } public Event_GameOver(Handle:event, const String:name[], bool:dontBroadcast) { new bool:tournament = GetConVarBool(g_hCvarTournament); if(!tournament) { return; } SearchForLog(); } SearchForLog() { decl String:fileName[32], String:fullPath[64]; new Handle:dir = OpenDirectory("logs/"); while(ReadDirEntry(dir, fileName, sizeof(fileName))) { if(StrEqual(fileName, ".")) { continue; } Format(fullPath, sizeof(fullPath), "logs/%s", fileName); new fileTime = GetFileTime(fullPath, FileTime_LastChange); if(GetTime() - fileTime <= 60) { PrintToServer("Found log %s", fullPath); UploadLog(fileName, fullPath); break; } } CloseHandle(dir); } UploadLog(const String:log[], const String:fullPath[]) { new Handle:socket = SocketCreate(SOCKET_TCP, OnSocketError); new Handle:pack = CreateDataPack(); WritePackString(pack, log); WritePackString(pack, fullPath); SocketSetArg(socket, pack); SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, "logs.tf", 80); } public OnSocketConnected(Handle:socket, any:pack) { // Generate boundary new String:genString[26]; for(new i = 1; i <= 26; i++) { new randomInt = GetRandomInt(0, 62); StrCat(genString, sizeof(genString), listOfChar[randomInt]); } // Put it together with some extra stuff. decl String:boundary[54]; Format(boundary, sizeof(boundary), "---------------------------%s", genString); // Read the file name from the data pack decl String:fileName[64], String:filePath[64]; ResetPack(pack); ReadPackString(pack, fileName, sizeof(fileName)); ReadPackString(pack, filePath, sizeof(filePath)); // Read the title cvar decl String:title[64]; GetConVarString(g_hCvarTitle, title, sizeof(title)); // Read the API key cvar decl String:apiKey[64]; GetConVarString(g_hCvarKey, apiKey, sizeof(apiKey)); // Read the current map decl String:map[64]; GetCurrentMap(map, sizeof(map)); // This part is tricky since we don't want to store it in memory before sending, since sourcemod limits the length of strings heavily new boundaryLength = strlen(boundary); // Base = 47 + Boundary Length + 2 for extra -- and another 2 for \r\n new standardLength = 47 + boundaryLength + 2; // File property - Base = 72 + File Size + Boundary Length + Field Name + File Name + 2 for extra -- new contentLength = 0; // File upload contentLength += FileSize(filePath) + 86 + boundaryLength + 7 + strlen(fileName) + 2; // Request properties (key, map, title) contentLength += (standardLength + 3 + strlen(apiKey)); contentLength += (standardLength + 3 + strlen(map)); contentLength += (standardLength + 5 + strlen(title)); // End boundary contentLength += (boundaryLength + 4); // Start the request decl String:requestStr[256]; Format(requestStr, sizeof(requestStr), "POST /upload HTTP/1.0\r\nHost: logs.tf\r\nConnection: close\r\nContent-Type: multipart/form-data; boundary=%s\r\nContent-Length: %i\r\n\r\n", boundary, contentLength); SocketSend(socket, requestStr); // Write the API key WriteFormData(socket, boundary, "key", apiKey); // Write the title WriteFormData(socket, boundary, "title", title); // Write the current map WriteFormData(socket, boundary, "map", map); // Open the file and use the WriteFormFile function new Handle:logFile = OpenFile(filePath, "r"); WriteFormFile(socket, boundary, "logfile", fileName, logFile); CloseHandle(logFile); // Write the final boundary Format(requestStr, sizeof(requestStr), "--%s--", boundary); SocketSend(socket, requestStr); } WriteFormData(Handle:socket, const String:boundary[], const String:name[], const String:value[]) { decl String:dataStr[512]; Format(dataStr, sizeof(dataStr), "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n", boundary, name); SocketSend(socket, dataStr); Format(dataStr, sizeof(dataStr), "%s\r\n", value); SocketSend(socket, dataStr); } WriteFormFile(Handle:socket, const String:boundary[], const String:name[], const String:fileName[], Handle:file) { decl String:dataStr[512]; // Write file header Format(dataStr, sizeof(dataStr), "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: text/plain\r\n\r\n", boundary, name, fileName); SocketSend(socket, dataStr); new totalData = 0; // Read file into the block while(ReadFileLine(file, dataStr, sizeof(dataStr))) { totalData += strlen(dataStr); SocketSend(socket, dataStr, strlen(dataStr)); } SocketSend(socket, "\r\n"); } public OnSocketReceive(Handle:socket, String:receiveData[], const dataSize, any:hFile) { //TODO read data and find the line with 'log_id' PrintToServer("Got data: %s", receiveData); new idx = StrContains(receiveData, "\r\n\r\n"); if(idx > -1) { new JSON:json = json_decode(receiveData[idx+4]); new logId = -1; if(json_get_cell(json, "log_id", logId)) { PrintToServer("Log Id: %i", logId); } json_destroy(json); } } public OnSocketDisconnected(Handle:socket, any:hFile) { CloseHandle(socket); } public OnSocketError(Handle:socket, const errorType, const errorNum, any:hFile) { LogError("socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }