mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
1157: Add a way to call for external moderator help r=Learath2 a=heinrich5991 This is done by HTTP POSTing to a location specified by `sv_modhelp_url`. We also provide a `src/modhelp/server.py` which can use theses POSTs to forward them to Discord servers. The POST contains a JSON object payload, with the keys `"port"` which contains the server port, `"player_id"` which contains the calling player's client ID, `"player_name"` which contains the calling player's nick and `"message"` which is the user-specified message. Make JSON-escaping function public, add tests and fix bugs uncovered by these tests. Supersedes #1129. 1160: Fix warning about incompatible function pointers r=Learath2 a=heinrich5991 This comes at the cost of one allocation per started thread. This should be okay because we're about to invoke a syscall anyway. Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
commit
ebb9481857
|
@ -349,6 +349,9 @@ if(WEBSOCKETS)
|
|||
show_dependency_status("Websockets" WEBSOCKETS)
|
||||
endif()
|
||||
|
||||
if(NOT(CURL_FOUND))
|
||||
message(SEND_ERROR "You must install Curl to compile the DDNet")
|
||||
endif()
|
||||
if(NOT(PYTHONINTERP_FOUND))
|
||||
message(SEND_ERROR "You must install Python to compile DDNet")
|
||||
endif()
|
||||
|
@ -361,9 +364,6 @@ if(WEBSOCKETS AND NOT(WEBSOCKETS_FOUND))
|
|||
message(SEND_ERROR "You must install libwebsockets to compile the DDNet server with websocket support")
|
||||
endif()
|
||||
|
||||
if(CLIENT AND NOT(CURL_FOUND))
|
||||
message(SEND_ERROR "You must install Curl to compile the DDNet client")
|
||||
endif()
|
||||
if(CLIENT AND NOT(FREETYPE_FOUND))
|
||||
message(SEND_ERROR "You must install Freetype to compile the DDNet client")
|
||||
endif()
|
||||
|
@ -606,6 +606,8 @@ set_glob(ENGINE_SHARED GLOB src/engine/shared
|
|||
econ.cpp
|
||||
econ.h
|
||||
engine.cpp
|
||||
fetcher.cpp
|
||||
fetcher.h
|
||||
fifo.cpp
|
||||
fifo.h
|
||||
filecollection.cpp
|
||||
|
@ -693,7 +695,7 @@ list(APPEND TARGETS_DEP md5)
|
|||
set(DEPS ${DEP_MD5} ${ZLIB_DEP})
|
||||
|
||||
# Libraries
|
||||
set(LIBS ${CMAKE_THREAD_LIBS_INIT} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
|
||||
set(LIBS ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
|
||||
|
||||
# Targets
|
||||
add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_GENERATED_SHARED} ${BASE})
|
||||
|
@ -712,8 +714,6 @@ if(CLIENT)
|
|||
backend_sdl.h
|
||||
client.cpp
|
||||
client.h
|
||||
fetcher.cpp
|
||||
fetcher.h
|
||||
friends.cpp
|
||||
friends.h
|
||||
graphics_threaded.cpp
|
||||
|
@ -840,7 +840,6 @@ if(CLIENT)
|
|||
# Libraries
|
||||
set(LIBS_CLIENT
|
||||
${LIBS}
|
||||
${CURL_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${PNGLITE_LIBRARIES}
|
||||
|
@ -880,7 +879,6 @@ if(CLIENT)
|
|||
target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT})
|
||||
|
||||
target_include_directories(${TARGET_CLIENT} PRIVATE
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
${OGG_INCLUDE_DIRS}
|
||||
|
@ -1107,6 +1105,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
fs.cpp
|
||||
git_revision.cpp
|
||||
jobs.cpp
|
||||
json.cpp
|
||||
mapbugs.cpp
|
||||
name_ban.cpp
|
||||
str.cpp
|
||||
|
@ -1476,11 +1475,10 @@ foreach(target ${TARGETS})
|
|||
endif()
|
||||
endforeach()
|
||||
|
||||
if(MSVC)
|
||||
set_property(TARGET ${TARGET_CLIENT} APPEND PROPERTY LINK_FLAGS /SAFESEH:NO) # Disable SafeSEH because the shipped libraries don't support it.
|
||||
endif()
|
||||
|
||||
foreach(target ${TARGETS_LINK})
|
||||
if(MSVC)
|
||||
set_property(TARGET ${target} APPEND PROPERTY LINK_FLAGS /SAFESEH:NO) # Disable SafeSEH because the shipped libraries don't support it (would cause error LNK2026 otherwise).
|
||||
endif()
|
||||
if(TARGET_OS STREQUAL "mac")
|
||||
target_link_libraries(${target} -stdlib=libc++)
|
||||
target_link_libraries(${target} -mmacosx-version-min=10.7)
|
||||
|
@ -1510,6 +1508,7 @@ foreach(target ${TARGETS_OWN})
|
|||
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
|
||||
target_include_directories(${target} PRIVATE src)
|
||||
target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
|
||||
target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS})
|
||||
target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_compile_definitions(${target} PRIVATE GLEW_STATIC)
|
||||
if(WEBSOCKETS)
|
||||
|
|
10
appveyor.yml
10
appveyor.yml
|
@ -21,8 +21,14 @@ test_script:
|
|||
- cmd: cmake --build build64 --config Debug --target run_tests
|
||||
- cmd: cmake --build build32 --config Release --target run_tests
|
||||
- cmd: cmake --build build64 --config Release --target run_tests
|
||||
- cmd: build32\Release\DDNet-Server shutdown
|
||||
- cmd: build64\Release\DDNet-Server shutdown
|
||||
- cmd: |
|
||||
cd build32
|
||||
Release\DDNet-Server shutdown
|
||||
cd ..
|
||||
- cmd: |
|
||||
cd build64
|
||||
Release\DDNet-Server shutdown
|
||||
cd ..
|
||||
|
||||
after_build:
|
||||
- cmd: cmake --build build32 --config Release --target package
|
||||
|
|
|
@ -664,17 +664,44 @@ void aio_wait(ASYNCIO *aio)
|
|||
thread_wait(thread);
|
||||
}
|
||||
|
||||
struct THREAD_RUN
|
||||
{
|
||||
void (*threadfunc)(void *);
|
||||
void *u;
|
||||
};
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
static void *thread_run(void *user)
|
||||
#elif defined(CONF_FAMILY_WINDOWS)
|
||||
static unsigned int __stdcall thread_run(void *user)
|
||||
#else
|
||||
#error not implemented
|
||||
#endif
|
||||
{
|
||||
struct THREAD_RUN *data = user;
|
||||
void (*threadfunc)(void *) = data->threadfunc;
|
||||
void *u = data->u;
|
||||
free(data);
|
||||
threadfunc(u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *thread_init(void (*threadfunc)(void *), void *u)
|
||||
{
|
||||
struct THREAD_RUN *data = malloc(sizeof(*data));
|
||||
data->threadfunc = threadfunc;
|
||||
data->u = u;
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
{
|
||||
pthread_t id;
|
||||
if(pthread_create(&id, NULL, (void *(*)(void*))threadfunc, u) != 0)
|
||||
if(pthread_create(&id, NULL, thread_run, data) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (void*)id;
|
||||
}
|
||||
#elif defined(CONF_FAMILY_WINDOWS)
|
||||
return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadfunc, u, 0, NULL);
|
||||
return CreateThread(NULL, 0, thread_run, data, 0, NULL);
|
||||
#else
|
||||
#error not implemented
|
||||
#endif
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <engine/shared/compression.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/shared/demo.h>
|
||||
#include <engine/shared/fetcher.h>
|
||||
#include <engine/shared/filecollection.h>
|
||||
#include <engine/shared/ghost.h>
|
||||
#include <engine/shared/network.h>
|
||||
|
@ -65,7 +66,6 @@
|
|||
|
||||
#include "friends.h"
|
||||
#include "serverbrowser.h"
|
||||
#include "fetcher.h"
|
||||
#include "updater.h"
|
||||
#include "client.h"
|
||||
|
||||
|
@ -1521,7 +1521,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
|
|||
|
||||
str_append(aUrl, aEscaped, sizeof(aUrl));
|
||||
|
||||
m_pMapdownloadTask = std::make_shared<CFetchTask>(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true);
|
||||
m_pMapdownloadTask = std::make_shared<CGetFile>(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true);
|
||||
Engine()->AddJob(m_pMapdownloadTask);
|
||||
}
|
||||
else
|
||||
|
@ -2478,15 +2478,15 @@ void CClient::Update()
|
|||
|
||||
if(m_pMapdownloadTask)
|
||||
{
|
||||
if(m_pMapdownloadTask->State() == CFetchTask::STATE_DONE)
|
||||
if(m_pMapdownloadTask->State() == HTTP_DONE)
|
||||
FinishMapDownload();
|
||||
else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ERROR)
|
||||
else if(m_pMapdownloadTask->State() == HTTP_ERROR)
|
||||
{
|
||||
dbg_msg("webdl", "http failed, falling back to gameserver");
|
||||
ResetMapDownload();
|
||||
SendMapRequest();
|
||||
}
|
||||
else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ABORTED)
|
||||
else if(m_pMapdownloadTask->State() == HTTP_ABORTED)
|
||||
{
|
||||
m_pMapdownloadTask = NULL;
|
||||
}
|
||||
|
@ -2494,14 +2494,14 @@ void CClient::Update()
|
|||
|
||||
if(m_pDDNetInfoTask)
|
||||
{
|
||||
if(m_pDDNetInfoTask->State() == CFetchTask::STATE_DONE)
|
||||
if(m_pDDNetInfoTask->State() == HTTP_DONE)
|
||||
FinishDDNetInfo();
|
||||
else if(m_pDDNetInfoTask->State() == CFetchTask::STATE_ERROR)
|
||||
else if(m_pDDNetInfoTask->State() == HTTP_ERROR)
|
||||
{
|
||||
dbg_msg("ddnet-info", "download failed");
|
||||
ResetDDNetInfo();
|
||||
}
|
||||
else if(m_pDDNetInfoTask->State() == CFetchTask::STATE_ABORTED)
|
||||
else if(m_pDDNetInfoTask->State() == HTTP_ABORTED)
|
||||
{
|
||||
m_pDDNetInfoTask = NULL;
|
||||
}
|
||||
|
@ -3684,7 +3684,7 @@ void CClient::RequestDDNetInfo()
|
|||
str_append(aUrl, aEscaped, sizeof(aUrl));
|
||||
}
|
||||
|
||||
m_pDDNetInfoTask = std::make_shared<CFetchTask>(Storage(), aUrl, "ddnet-info.json.tmp", IStorage::TYPE_SAVE, true, true);
|
||||
m_pDDNetInfoTask = std::make_shared<CGetFile>(Storage(), aUrl, "ddnet-info.json.tmp", IStorage::TYPE_SAVE, true, true);
|
||||
Engine()->AddJob(m_pDDNetInfoTask);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "fetcher.h"
|
||||
#include <engine/shared/fetcher.h>
|
||||
|
||||
class CGraph
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
char m_aCmdConnect[256];
|
||||
|
||||
// map download
|
||||
std::shared_ptr<CFetchTask> m_pMapdownloadTask;
|
||||
std::shared_ptr<CGetFile> m_pMapdownloadTask;
|
||||
char m_aMapdownloadFilename[256];
|
||||
char m_aMapdownloadName[256];
|
||||
IOHANDLE m_MapdownloadFile;
|
||||
|
@ -140,7 +140,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
int m_MapdownloadAmount;
|
||||
int m_MapdownloadTotalsize;
|
||||
|
||||
std::shared_ptr<CFetchTask> m_pDDNetInfoTask;
|
||||
std::shared_ptr<CGetFile> m_pDDNetInfoTask;
|
||||
|
||||
// time
|
||||
CSmoothTime m_GameTime[2];
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
#include <base/system.h>
|
||||
#include <engine/engine.h>
|
||||
#include <engine/storage.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <game/version.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
|
||||
#include "fetcher.h"
|
||||
|
||||
double CFetchTask::Current() const { return m_Current; }
|
||||
double CFetchTask::Size() const { return m_Size; }
|
||||
int CFetchTask::Progress() const { return m_Progress; }
|
||||
int CFetchTask::State() const { return m_State; }
|
||||
const char *CFetchTask::Dest() const { return m_aDest; }
|
||||
|
||||
void CFetchTask::Abort() { m_Abort = true; };
|
||||
|
||||
CFetchTask::CFetchTask(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType, bool UseDDNetCA, bool CanTimeout) :
|
||||
m_pStorage(pStorage),
|
||||
m_StorageType(StorageType),
|
||||
m_UseDDNetCA(UseDDNetCA),
|
||||
m_CanTimeout(CanTimeout),
|
||||
m_Size(0),
|
||||
m_Progress(0),
|
||||
m_State(CFetchTask::STATE_QUEUED),
|
||||
m_Abort(false)
|
||||
{
|
||||
str_copy(m_aUrl, pUrl, sizeof(m_aUrl));
|
||||
str_copy(m_aDest, pDest, sizeof(m_aDest));
|
||||
}
|
||||
|
||||
|
||||
bool FetcherInit()
|
||||
{
|
||||
return !curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
}
|
||||
|
||||
void EscapeUrl(char *pBuf, int Size, const char *pStr)
|
||||
{
|
||||
char *pEsc = curl_easy_escape(0, pStr, 0);
|
||||
str_copy(pBuf, pEsc, Size);
|
||||
curl_free(pEsc);
|
||||
}
|
||||
|
||||
void CFetchTask::Run()
|
||||
{
|
||||
CURL *pHandle = curl_easy_init();
|
||||
if(!pHandle)
|
||||
{
|
||||
m_State = STATE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char aPath[512];
|
||||
if(m_StorageType == -2)
|
||||
m_pStorage->GetBinaryPath(m_aDest, aPath, sizeof(aPath));
|
||||
else
|
||||
m_pStorage->GetCompletePath(m_StorageType, m_aDest, aPath, sizeof(aPath));
|
||||
|
||||
if(fs_makedir_rec_for(aPath) < 0)
|
||||
dbg_msg("fetcher", "i/o error, cannot create folder for: %s", aPath);
|
||||
|
||||
IOHANDLE File = io_open(aPath, IOFLAG_WRITE);
|
||||
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("fetcher", "i/o error, cannot open file: %s", m_aDest);
|
||||
m_State = CFetchTask::STATE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char aErr[CURL_ERROR_SIZE];
|
||||
curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, aErr);
|
||||
|
||||
//curl_easy_setopt(pHandle, CURLOPT_VERBOSE, 1L);
|
||||
if(m_CanTimeout)
|
||||
{
|
||||
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, (long)g_Config.m_ClHTTPConnectTimeoutMs);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, (long)g_Config.m_ClHTTPLowSpeedLimit);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, (long)g_Config.m_ClHTTPLowSpeedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, 0);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, 0);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, 0);
|
||||
}
|
||||
curl_easy_setopt(pHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_MAXREDIRS, 4L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_FAILONERROR, 1L);
|
||||
if(m_UseDDNetCA)
|
||||
{
|
||||
char aCAFile[512];
|
||||
m_pStorage->GetBinaryPath("data/ca-ddnet.pem", aCAFile, sizeof aCAFile);
|
||||
curl_easy_setopt(pHandle, CURLOPT_CAINFO, aCAFile);
|
||||
}
|
||||
curl_easy_setopt(pHandle, CURLOPT_URL, m_aUrl);
|
||||
curl_easy_setopt(pHandle, CURLOPT_WRITEDATA, File);
|
||||
curl_easy_setopt(pHandle, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOPROGRESS, 0);
|
||||
curl_easy_setopt(pHandle, CURLOPT_PROGRESSDATA, this);
|
||||
curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_USERAGENT, "DDNet " GAME_RELEASE_VERSION " (" CONF_PLATFORM_STRING "; " CONF_ARCH_STRING ")");
|
||||
|
||||
dbg_msg("fetcher", "downloading %s", m_aDest);
|
||||
m_State = CFetchTask::STATE_RUNNING;
|
||||
int Ret = curl_easy_perform(pHandle);
|
||||
io_close(File);
|
||||
if(Ret != CURLE_OK)
|
||||
{
|
||||
dbg_msg("fetcher", "task failed. libcurl error: %s", aErr);
|
||||
m_State = (Ret == CURLE_ABORTED_BY_CALLBACK) ? CFetchTask::STATE_ABORTED : CFetchTask::STATE_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("fetcher", "task done %s", m_aDest);
|
||||
m_State = CFetchTask::STATE_DONE;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(pHandle);
|
||||
|
||||
OnCompletion();
|
||||
}
|
||||
|
||||
size_t CFetchTask::WriteCallback(char *pData, size_t Size, size_t Number, void *pFile)
|
||||
{
|
||||
return io_write((IOHANDLE)pFile, pData, Size * Number);
|
||||
}
|
||||
|
||||
int CFetchTask::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
|
||||
{
|
||||
CFetchTask *pTask = (CFetchTask *)pUser;
|
||||
pTask->m_Current = DlCurr;
|
||||
pTask->m_Size = DlTotal;
|
||||
pTask->m_Progress = (100 * DlCurr) / (DlTotal ? DlTotal : 1);
|
||||
pTask->OnProgress();
|
||||
return pTask->m_Abort ? -1 : 0;
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
using std::string;
|
||||
using std::map;
|
||||
|
||||
class CUpdaterFetchTask : public CFetchTask
|
||||
class CUpdaterFetchTask : public CGetFile
|
||||
{
|
||||
char m_aBuf[256];
|
||||
char m_aBuf2[256];
|
||||
|
@ -41,7 +41,7 @@ static const char *GetUpdaterDestPath(char *pBuf, int BufSize, const char *pFile
|
|||
}
|
||||
|
||||
CUpdaterFetchTask::CUpdaterFetchTask(CUpdater *pUpdater, const char *pFile, const char *pDestPath) :
|
||||
CFetchTask(pUpdater->m_pStorage, GetUpdaterUrl(m_aBuf, sizeof(m_aBuf), pFile), GetUpdaterDestPath(m_aBuf2, sizeof(m_aBuf), pFile, pDestPath), -2, true, false),
|
||||
CGetFile(pUpdater->m_pStorage, GetUpdaterUrl(m_aBuf, sizeof(m_aBuf), pFile), GetUpdaterDestPath(m_aBuf2, sizeof(m_aBuf), pFile, pDestPath), -2, true, false),
|
||||
m_pUpdater(pUpdater)
|
||||
{
|
||||
}
|
||||
|
@ -63,16 +63,16 @@ void CUpdaterFetchTask::OnCompletion()
|
|||
b = b ? b : Dest();
|
||||
if(!str_comp(b, "update.json"))
|
||||
{
|
||||
if(State() == CFetchTask::STATE_DONE)
|
||||
if(State() == HTTP_DONE)
|
||||
m_pUpdater->SetCurrentState(IUpdater::GOT_MANIFEST);
|
||||
else if(State() == CFetchTask::STATE_ERROR)
|
||||
else if(State() == HTTP_ERROR)
|
||||
m_pUpdater->SetCurrentState(IUpdater::FAIL);
|
||||
}
|
||||
else if(!str_comp(b, m_pUpdater->m_aLastFile))
|
||||
{
|
||||
if(State() == CFetchTask::STATE_DONE)
|
||||
if(State() == HTTP_DONE)
|
||||
m_pUpdater->SetCurrentState(IUpdater::MOVE_FILES);
|
||||
else if(State() == CFetchTask::STATE_ERROR)
|
||||
else if(State() == HTTP_ERROR)
|
||||
m_pUpdater->SetCurrentState(IUpdater::FAIL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define ENGINE_CLIENT_UPDATER_H
|
||||
|
||||
#include <engine/updater.h>
|
||||
#include "fetcher.h"
|
||||
#include <engine/shared/fetcher.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <engine/shared/datafile.h>
|
||||
#include <engine/shared/demo.h>
|
||||
#include <engine/shared/econ.h>
|
||||
#include <engine/shared/fetcher.h>
|
||||
#include <engine/shared/filecollection.h>
|
||||
#include <engine/shared/netban.h>
|
||||
#include <engine/shared/network.h>
|
||||
|
@ -2875,6 +2876,8 @@ int main(int argc, const char **argv) // ignore_convention
|
|||
pEngineMasterServer->Init();
|
||||
pEngineMasterServer->Load();
|
||||
|
||||
FetcherInit();
|
||||
|
||||
// register all console commands
|
||||
pServer->RegisterCommands();
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@ MACRO_CONFIG_INT(SvPlayerDemoRecord, sv_player_demo_record, 0, 0, 1, CFGFLAG_SER
|
|||
MACRO_CONFIG_INT(SvDemoChat, sv_demo_chat, 0, 0, 1, CFGFLAG_SERVER, "Record chat for demos")
|
||||
MACRO_CONFIG_INT(SvServerInfoPerSecond, sv_server_info_per_second, 50, 1, 1000, CFGFLAG_SERVER, "Maximum number of complete server info responses that are sent out per second")
|
||||
MACRO_CONFIG_INT(SvVanConnPerSecond, sv_van_conn_per_second, 10, 1, 1000, CFGFLAG_SERVER, "Antispoof specific ratelimit")
|
||||
MACRO_CONFIG_STR(SvModhelpUrl, sv_modhelp_url, 128, "", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, "HTTP URL to POST moderator help requests to")
|
||||
|
||||
MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_ECON, "Address to bind the external console to. Anything but 'localhost' is dangerous")
|
||||
MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_ECON, "Port to use for the external console")
|
||||
|
|
253
src/engine/shared/fetcher.cpp
Normal file
253
src/engine/shared/fetcher.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
#include <base/system.h>
|
||||
#include <engine/engine.h>
|
||||
#include <engine/storage.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <game/version.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
|
||||
#include "fetcher.h"
|
||||
|
||||
double CGetFile::Current() const { return m_Current; }
|
||||
double CGetFile::Size() const { return m_Size; }
|
||||
int CGetFile::Progress() const { return m_Progress; }
|
||||
int CGetFile::State() const { return m_State; }
|
||||
const char *CGetFile::Dest() const { return m_aDest; }
|
||||
|
||||
void CGetFile::Abort() { m_Abort = true; };
|
||||
|
||||
CGetFile::CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType, bool UseDDNetCA, bool CanTimeout) :
|
||||
m_pStorage(pStorage),
|
||||
m_StorageType(StorageType),
|
||||
m_UseDDNetCA(UseDDNetCA),
|
||||
m_CanTimeout(CanTimeout),
|
||||
m_Size(0),
|
||||
m_Progress(0),
|
||||
m_State(HTTP_QUEUED),
|
||||
m_Abort(false)
|
||||
{
|
||||
str_copy(m_aUrl, pUrl, sizeof(m_aUrl));
|
||||
str_copy(m_aDest, pDest, sizeof(m_aDest));
|
||||
}
|
||||
|
||||
|
||||
bool FetcherInit()
|
||||
{
|
||||
return !curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
}
|
||||
|
||||
void EscapeUrl(char *pBuf, int Size, const char *pStr)
|
||||
{
|
||||
char *pEsc = curl_easy_escape(0, pStr, 0);
|
||||
str_copy(pBuf, pEsc, Size);
|
||||
curl_free(pEsc);
|
||||
}
|
||||
|
||||
static void SetCurlOptions(CURL *pHandle, char *pErrorBuffer, const char *pUrl, bool CanTimeout)
|
||||
{
|
||||
//curl_easy_setopt(pHandle, CURLOPT_VERBOSE, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, pErrorBuffer);
|
||||
|
||||
if(CanTimeout)
|
||||
{
|
||||
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, (long)g_Config.m_ClHTTPConnectTimeoutMs);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, (long)g_Config.m_ClHTTPLowSpeedLimit);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, (long)g_Config.m_ClHTTPLowSpeedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, 0L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, 0L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, 0L);
|
||||
}
|
||||
curl_easy_setopt(pHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_MAXREDIRS, 4L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_FAILONERROR, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_URL, pUrl);
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_USERAGENT, "DDNet " GAME_RELEASE_VERSION " (" CONF_PLATFORM_STRING "; " CONF_ARCH_STRING ")");
|
||||
}
|
||||
|
||||
void CGetFile::Run()
|
||||
{
|
||||
CURL *pHandle = curl_easy_init();
|
||||
if(!pHandle)
|
||||
{
|
||||
m_State = HTTP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char aPath[512];
|
||||
if(m_StorageType == -2)
|
||||
m_pStorage->GetBinaryPath(m_aDest, aPath, sizeof(aPath));
|
||||
else
|
||||
m_pStorage->GetCompletePath(m_StorageType, m_aDest, aPath, sizeof(aPath));
|
||||
|
||||
if(fs_makedir_rec_for(aPath) < 0)
|
||||
dbg_msg("fetcher", "i/o error, cannot create folder for: %s", aPath);
|
||||
|
||||
IOHANDLE File = io_open(aPath, IOFLAG_WRITE);
|
||||
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("fetcher", "i/o error, cannot open file: %s", m_aDest);
|
||||
m_State = HTTP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char aErr[CURL_ERROR_SIZE];
|
||||
SetCurlOptions(pHandle, aErr, m_aUrl, m_CanTimeout);
|
||||
if(m_UseDDNetCA)
|
||||
{
|
||||
char aCAFile[512];
|
||||
m_pStorage->GetBinaryPath("data/ca-ddnet.pem", aCAFile, sizeof aCAFile);
|
||||
curl_easy_setopt(pHandle, CURLOPT_CAINFO, aCAFile);
|
||||
}
|
||||
curl_easy_setopt(pHandle, CURLOPT_WRITEDATA, File);
|
||||
curl_easy_setopt(pHandle, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(pHandle, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(pHandle, CURLOPT_PROGRESSDATA, this);
|
||||
curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
|
||||
|
||||
dbg_msg("fetcher", "downloading %s", m_aDest);
|
||||
m_State = HTTP_RUNNING;
|
||||
int Ret = curl_easy_perform(pHandle);
|
||||
io_close(File);
|
||||
if(Ret != CURLE_OK)
|
||||
{
|
||||
dbg_msg("fetcher", "task failed. libcurl error: %s", aErr);
|
||||
m_State = (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("fetcher", "task done %s", m_aDest);
|
||||
m_State = HTTP_DONE;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(pHandle);
|
||||
|
||||
OnCompletion();
|
||||
}
|
||||
|
||||
size_t CGetFile::WriteCallback(char *pData, size_t Size, size_t Number, void *pFile)
|
||||
{
|
||||
return io_write((IOHANDLE)pFile, pData, Size * Number);
|
||||
}
|
||||
|
||||
int CGetFile::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
|
||||
{
|
||||
CGetFile *pTask = (CGetFile *)pUser;
|
||||
pTask->m_Current = DlCurr;
|
||||
pTask->m_Size = DlTotal;
|
||||
pTask->m_Progress = (100 * DlCurr) / (DlTotal ? DlTotal : 1);
|
||||
pTask->OnProgress();
|
||||
return pTask->m_Abort ? -1 : 0;
|
||||
}
|
||||
|
||||
int CPostJson::State() const { return m_State; }
|
||||
|
||||
CPostJson::CPostJson(const char *pUrl, const char *pJson)
|
||||
: m_State(HTTP_QUEUED)
|
||||
{
|
||||
str_copy(m_aUrl, pUrl, sizeof(m_aUrl));
|
||||
str_copy(m_aJson, pJson, sizeof(m_aJson));
|
||||
}
|
||||
|
||||
void CPostJson::Run()
|
||||
{
|
||||
CURL *pHandle = curl_easy_init();
|
||||
if(!pHandle)
|
||||
{
|
||||
m_State = HTTP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char aErr[CURL_ERROR_SIZE];
|
||||
SetCurlOptions(pHandle, aErr, m_aUrl, true);
|
||||
|
||||
curl_slist *pHeaders = NULL;
|
||||
pHeaders = curl_slist_append(pHeaders, "Content-Type: application/json");
|
||||
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, pHeaders);
|
||||
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_aJson);
|
||||
|
||||
dbg_msg("fetcher", "posting to %s", m_aUrl);
|
||||
m_State = HTTP_RUNNING;
|
||||
int Result = curl_easy_perform(pHandle);
|
||||
|
||||
curl_slist_free_all(pHeaders);
|
||||
if(Result != CURLE_OK)
|
||||
{
|
||||
dbg_msg("fetcher", "task failed. libcurl error: %s", aErr);
|
||||
m_State = HTTP_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("fetcher", "posting to %s done", m_aUrl);
|
||||
m_State = HTTP_DONE;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(pHandle);
|
||||
|
||||
OnCompletion();
|
||||
}
|
||||
|
||||
static char EscapeJsonChar(char c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '\"': return '\"';
|
||||
case '\\': return '\\';
|
||||
case '\b': return 'b';
|
||||
case '\n': return 'n';
|
||||
case '\r': return 'r';
|
||||
case '\t': return 't';
|
||||
// Don't escape '\f', who uses that. :)
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString)
|
||||
{
|
||||
dbg_assert(BufferSize > 0, "can't null-terminate the string");
|
||||
// Subtract the space for null termination early.
|
||||
BufferSize--;
|
||||
|
||||
char *pResult = pBuffer;
|
||||
while(BufferSize && *pString)
|
||||
{
|
||||
char c = *pString;
|
||||
pString++;
|
||||
char Escaped = EscapeJsonChar(c);
|
||||
if(Escaped)
|
||||
{
|
||||
if(BufferSize < 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
*pBuffer++ = '\\';
|
||||
*pBuffer++ = Escaped;
|
||||
BufferSize -= 2;
|
||||
}
|
||||
// Assuming ASCII/UTF-8, "if control character".
|
||||
else if((unsigned char)c < 0x20)
|
||||
{
|
||||
// \uXXXX
|
||||
if(BufferSize < 6)
|
||||
{
|
||||
break;
|
||||
}
|
||||
str_format(pBuffer, BufferSize, "\\u%04x", c);
|
||||
pBuffer += 6;
|
||||
BufferSize -= 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pBuffer++ = c;
|
||||
BufferSize--;
|
||||
}
|
||||
}
|
||||
*pBuffer = 0;
|
||||
return pResult;
|
||||
}
|
|
@ -5,7 +5,16 @@
|
|||
#include <engine/storage.h>
|
||||
#include <engine/kernel.h>
|
||||
|
||||
class CFetchTask : public IJob
|
||||
enum
|
||||
{
|
||||
HTTP_ERROR = -1,
|
||||
HTTP_QUEUED,
|
||||
HTTP_RUNNING,
|
||||
HTTP_DONE,
|
||||
HTTP_ABORTED,
|
||||
};
|
||||
|
||||
class CGetFile : public IJob
|
||||
{
|
||||
private:
|
||||
IStorage *m_pStorage;
|
||||
|
@ -19,7 +28,7 @@ private:
|
|||
double m_Size;
|
||||
double m_Current;
|
||||
int m_Progress;
|
||||
int m_State;
|
||||
std::atomic<int> m_State;
|
||||
|
||||
std::atomic<bool> m_Abort;
|
||||
|
||||
|
@ -32,16 +41,7 @@ private:
|
|||
void Run();
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
STATE_ERROR = -1,
|
||||
STATE_QUEUED,
|
||||
STATE_RUNNING,
|
||||
STATE_DONE,
|
||||
STATE_ABORTED,
|
||||
};
|
||||
|
||||
CFetchTask(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType = -2, bool UseDDNetCA = false, bool CanTimeout = true);
|
||||
CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType = -2, bool UseDDNetCA = false, bool CanTimeout = true);
|
||||
|
||||
double Current() const;
|
||||
double Size() const;
|
||||
|
@ -51,6 +51,24 @@ public:
|
|||
void Abort();
|
||||
};
|
||||
|
||||
|
||||
class CPostJson : public IJob
|
||||
{
|
||||
private:
|
||||
char m_aUrl[256];
|
||||
char m_aJson[1024];
|
||||
std::atomic<int> m_State;
|
||||
|
||||
void Run();
|
||||
|
||||
virtual void OnCompletion() { }
|
||||
|
||||
public:
|
||||
CPostJson(const char *pUrl, const char *pJson);
|
||||
int State() const;
|
||||
};
|
||||
|
||||
bool FetcherInit();
|
||||
void EscapeUrl(char *pBud, int Size, const char *pStr);
|
||||
void EscapeUrl(char *pBuf, int Size, const char *pStr);
|
||||
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString);
|
||||
#endif
|
|
@ -1,6 +1,5 @@
|
|||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
#include <base/system.h>
|
||||
#include "jobs.h"
|
||||
|
||||
IJob::IJob() :
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#ifndef ENGINE_SHARED_JOBS_H
|
||||
#define ENGINE_SHARED_JOBS_H
|
||||
|
||||
#include <base/system.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
|
||||
#include "gamecontext.h"
|
||||
#include <engine/engine.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/protocol.h>
|
||||
#include <game/server/teams.h>
|
||||
|
@ -1392,7 +1393,7 @@ void CGameContext::ConProtectedKill(IConsole::IResult *pResult, void *pUserData)
|
|||
}
|
||||
}
|
||||
|
||||
void CGameContext::ConModHelp(IConsole::IResult *pResult, void *pUserData)
|
||||
void CGameContext::ConModhelp(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CGameContext *pSelf = (CGameContext *) pUserData;
|
||||
|
||||
|
@ -1403,16 +1404,28 @@ void CGameContext::ConModHelp(IConsole::IResult *pResult, void *pUserData)
|
|||
if(!pPlayer)
|
||||
return;
|
||||
|
||||
if(pPlayer->m_ModHelpTick > pSelf->Server()->Tick())
|
||||
if(pPlayer->m_pPostJson)
|
||||
{
|
||||
char aBuf[126];
|
||||
str_format(aBuf, sizeof(aBuf), "You must wait %d seconds to execute this command again.",
|
||||
(pPlayer->m_ModHelpTick - pSelf->Server()->Tick()) / pSelf->Server()->TickSpeed());
|
||||
pSelf->SendChatTarget(pResult->m_ClientID, aBuf);
|
||||
pSelf->SendChatTarget(pResult->m_ClientID, "Your last request hasn't finished processing yet, please slow down");
|
||||
return;
|
||||
}
|
||||
|
||||
pPlayer->m_ModHelpTick = pSelf->Server()->Tick() + g_Config.m_SvModHelpDelay * pSelf->Server()->TickSpeed();
|
||||
int CurTick = pSelf->Server()->Tick();
|
||||
if(pPlayer->m_ModhelpTick != -1)
|
||||
{
|
||||
int TickSpeed = pSelf->Server()->TickSpeed();
|
||||
int NextModhelpTick = pPlayer->m_ModhelpTick + g_Config.m_SvModhelpDelay * TickSpeed;
|
||||
if(NextModhelpTick > CurTick)
|
||||
{
|
||||
char aBuf[128];
|
||||
str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before you can execute this command again.",
|
||||
(NextModhelpTick - CurTick) / TickSpeed);
|
||||
pSelf->SendChatTarget(pResult->m_ClientID, aBuf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pPlayer->m_ModhelpTick = CurTick;
|
||||
|
||||
char aBuf[512];
|
||||
str_format(aBuf, sizeof(aBuf), "Moderator help is requested by '%s' (ID: %d):",
|
||||
|
@ -1424,10 +1437,33 @@ void CGameContext::ConModHelp(IConsole::IResult *pResult, void *pUserData)
|
|||
{
|
||||
if(pSelf->m_apPlayers[i] && pSelf->Server()->ClientAuthed(i))
|
||||
{
|
||||
pSelf->SendChatTarget(pSelf->m_apPlayers[i]->GetCID(), aBuf);
|
||||
pSelf->SendChatTarget(pSelf->m_apPlayers[i]->GetCID(), pResult->GetString(0));
|
||||
pSelf->SendChatTarget(i, aBuf);
|
||||
pSelf->SendChatTarget(i, pResult->GetString(0));
|
||||
}
|
||||
}
|
||||
if(g_Config.m_SvModhelpUrl[0])
|
||||
{
|
||||
bool ModeratorPresent = false;
|
||||
for(int i = 0; i < MAX_CLIENTS; i++)
|
||||
{
|
||||
if(pSelf->m_apPlayers[i] && pSelf->Server()->ClientAuthed(i))
|
||||
{
|
||||
ModeratorPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char aJson[512];
|
||||
char aPlayerName[64];
|
||||
char aMessage[128];
|
||||
str_format(aJson, sizeof(aJson), "{\"port\":%d,\"moderator_present\":%s,\"player_id\":%d,\"player_name\":\"%s\",\"message\":\"%s\"}",
|
||||
g_Config.m_SvPort,
|
||||
ModeratorPresent ? "true" : "false",
|
||||
pResult->m_ClientID,
|
||||
EscapeJson(aPlayerName, sizeof(aPlayerName), pSelf->Server()->ClientName(pResult->m_ClientID)),
|
||||
EscapeJson(aMessage, sizeof(aMessage), pResult->GetString(0)));
|
||||
pSelf->Engine()->AddJob(pPlayer->m_pPostJson = std::make_shared<CPostJson>(g_Config.m_SvModhelpUrl, aJson));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONF_SQL)
|
||||
|
|
|
@ -50,7 +50,7 @@ CHAT_COMMAND("rescue", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConRescue, this, "Telepo
|
|||
|
||||
CHAT_COMMAND("kill", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConProtectedKill, this, "Kill yourself")
|
||||
|
||||
CHAT_COMMAND("modhelp", "r[message]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConModHelp, this, "Request the help of a moderator with a description of the problem")
|
||||
CHAT_COMMAND("modhelp", "r[message]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConModhelp, this, "Request the help of a moderator with a description of the problem")
|
||||
|
||||
#if defined(CONF_SQL)
|
||||
CHAT_COMMAND("times", "?s[player name] ?i[number of times to skip]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimes, this, "/times ?s?i shows last 5 times of the server or of a player beginning with name s starting with time i (i = 1 by default)")
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <engine/shared/config.h>
|
||||
#include <engine/map.h>
|
||||
#include <engine/console.h>
|
||||
#include <engine/engine.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/shared/linereader.h>
|
||||
#include <engine/storage.h>
|
||||
|
@ -891,6 +892,22 @@ void CGameContext::OnTick()
|
|||
m_aMutes[i] = m_aMutes[m_NumMutes];
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < MAX_CLIENTS; i++)
|
||||
{
|
||||
if(m_apPlayers[i] && m_apPlayers[i]->m_pPostJson)
|
||||
{
|
||||
switch(m_apPlayers[i]->m_pPostJson->State())
|
||||
{
|
||||
case HTTP_DONE:
|
||||
m_apPlayers[i]->m_pPostJson = NULL;
|
||||
break;
|
||||
case HTTP_ERROR:
|
||||
dbg_msg("modhelp", "http request failed for cid=%d", i);
|
||||
m_apPlayers[i]->m_pPostJson = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Server()->Tick() % (g_Config.m_SvAnnouncementInterval * Server()->TickSpeed() * 60) == 0)
|
||||
{
|
||||
|
@ -2551,6 +2568,7 @@ void CGameContext::OnConsoleInit()
|
|||
{
|
||||
m_pServer = Kernel()->RequestInterface<IServer>();
|
||||
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
||||
m_pEngine = Kernel()->RequestInterface<IEngine>();
|
||||
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
||||
|
||||
m_ChatPrintCBIndex = Console()->RegisterPrintCallback(0, SendChatResponse, this);
|
||||
|
@ -2593,6 +2611,7 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
|
|||
{
|
||||
m_pServer = Kernel()->RequestInterface<IServer>();
|
||||
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
||||
m_pEngine = Kernel()->RequestInterface<IEngine>();
|
||||
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
||||
m_World.SetGameServer(this);
|
||||
m_Events.SetGameServer(this);
|
||||
|
|
|
@ -53,12 +53,15 @@ enum
|
|||
NUM_TUNEZONES = 256
|
||||
};
|
||||
|
||||
class IConsole;
|
||||
class IEngine;
|
||||
class IStorage;
|
||||
|
||||
class CGameContext : public IGameServer
|
||||
{
|
||||
IServer *m_pServer;
|
||||
class IConsole *m_pConsole;
|
||||
IConsole *m_pConsole;
|
||||
IEngine *m_pEngine;
|
||||
IStorage *m_pStorage;
|
||||
CLayers m_Layers;
|
||||
CCollision m_Collision;
|
||||
|
@ -113,7 +116,8 @@ class CGameContext : public IGameServer
|
|||
bool m_Resetting;
|
||||
public:
|
||||
IServer *Server() const { return m_pServer; }
|
||||
class IConsole *Console() { return m_pConsole; }
|
||||
IConsole *Console() { return m_pConsole; }
|
||||
IEngine *Engine() { return m_pEngine; }
|
||||
IStorage *Storage() { return m_pStorage; }
|
||||
CCollision *Collision() { return &m_Collision; }
|
||||
CTuningParams *Tuning() { return &m_Tuning; }
|
||||
|
@ -353,7 +357,7 @@ private:
|
|||
static void ConUnmute(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConMutes(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConModerate(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConModHelp(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConModhelp(IConsole::IResult *pResult, void *pUserData);
|
||||
|
||||
static void ConList(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConSetDDRTeam(IConsole::IResult *pResult, void *pUserData);
|
||||
|
|
|
@ -70,7 +70,7 @@ void CPlayer::Reset()
|
|||
m_LastWhisperTo = -1;
|
||||
m_LastSetSpectatorMode = 0;
|
||||
m_TimeoutCode[0] = '\0';
|
||||
m_ModHelpTick = 0;
|
||||
m_ModhelpTick = -1;
|
||||
|
||||
m_TuneZone = 0;
|
||||
m_TuneZoneOld = m_TuneZone;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// this include should perhaps be removed
|
||||
#include "entities/character.h"
|
||||
#include "gamecontext.h"
|
||||
#include <engine/shared/fetcher.h>
|
||||
|
||||
// player object
|
||||
class CPlayer
|
||||
|
@ -174,7 +175,7 @@ public:
|
|||
int m_ChatScore;
|
||||
|
||||
bool m_Moderating;
|
||||
int m_ModHelpTick;
|
||||
int m_ModhelpTick;
|
||||
|
||||
bool AfkTimer(int new_target_x, int new_target_y); //returns true if kicked
|
||||
void AfkVoteTimer(CNetObj_PlayerInput *NewTarget);
|
||||
|
@ -197,6 +198,7 @@ public:
|
|||
#if defined(CONF_SQL)
|
||||
int64 m_LastSQLQuery;
|
||||
#endif
|
||||
std::shared_ptr<CPostJson> m_pPostJson;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -28,63 +28,6 @@ enum
|
|||
TEEHISTORIAN_EX,
|
||||
};
|
||||
|
||||
static char EscapeJsonChar(char c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '\"': return '\"';
|
||||
case '\\': return '\\';
|
||||
case '\b': return 'b';
|
||||
case '\n': return 'n';
|
||||
case '\r': return 'r';
|
||||
case '\t': return 't';
|
||||
// Don't escape '\f', who uses that. :)
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *EscapeJson(char *pBuffer, int BufferSize, const char *pString)
|
||||
{
|
||||
dbg_assert(BufferSize > 0, "can't null-terminate the string");
|
||||
// Subtract the space for null termination early.
|
||||
BufferSize--;
|
||||
|
||||
char *pResult = pBuffer;
|
||||
while(BufferSize && *pString)
|
||||
{
|
||||
char c = *pString;
|
||||
pString++;
|
||||
char Escaped = EscapeJsonChar(c);
|
||||
if(Escaped)
|
||||
{
|
||||
if(BufferSize < 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
*pBuffer++ = '\\';
|
||||
*pBuffer++ = Escaped;
|
||||
BufferSize -= 2;
|
||||
}
|
||||
// Assuming ASCII/UTF-8, "if control character".
|
||||
else if(c < 0x20)
|
||||
{
|
||||
// \uXXXX
|
||||
if(BufferSize < 6)
|
||||
{
|
||||
break;
|
||||
}
|
||||
str_format(pBuffer, BufferSize, "\\u%04x", c);
|
||||
BufferSize -= 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pBuffer++ = c;
|
||||
}
|
||||
}
|
||||
*pBuffer = 0;
|
||||
return pResult;
|
||||
}
|
||||
|
||||
CTeeHistorian::CTeeHistorian()
|
||||
{
|
||||
m_State = STATE_START;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <engine/console.h>
|
||||
#include <engine/shared/fetcher.h>
|
||||
#include <engine/shared/packer.h>
|
||||
#include <engine/shared/protocol.h>
|
||||
#include <game/generated/protocol.h>
|
||||
|
|
|
@ -159,7 +159,7 @@ MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_S
|
|||
MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze")
|
||||
MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 5, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues")
|
||||
|
||||
MACRO_CONFIG_INT(SvModHelpDelay, sv_modhelp_delay, 60, 0, 0, CFGFLAG_SERVER, "Number of seconds to wait before executing /modhelp again")
|
||||
MACRO_CONFIG_INT(SvModhelpDelay, sv_modhelp_delay, 60, 0, 0, CFGFLAG_SERVER, "Number of seconds to wait before executing /modhelp again")
|
||||
|
||||
// debug
|
||||
#ifdef CONF_DEBUG // this one can crash the server if not used correctly
|
||||
|
|
43
src/modhelp/server.py
Executable file
43
src/modhelp/server.py
Executable file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python3
|
||||
from flask import Flask, make_response, request
|
||||
import http
|
||||
import re
|
||||
import requests
|
||||
|
||||
SERVER=None
|
||||
# Generate one by right-clicking on the server icon in the sidebar, clicking on
|
||||
# "Server Settings" → "Webhooks" → "Create Webhook". You can then select the
|
||||
# channel in which the messages should appear. Copy the "Webhook URL" to the
|
||||
# following config variable:
|
||||
# DISCORD_WEBHOOK="https://discordapp.com/api/webhooks/.../..."
|
||||
DISCORD_WEBHOOK=None
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def sanitize(s):
|
||||
return re.sub(r"([^\0- 0-9A-Za-z])", r"\\\1", s)
|
||||
|
||||
def no_content():
|
||||
return make_response("", http.HTTPStatus.NO_CONTENT)
|
||||
|
||||
@app.route("/modhelp", methods=['POST'])
|
||||
def modhelp():
|
||||
json = request.get_json()
|
||||
|
||||
if "server" not in json:
|
||||
if SERVER:
|
||||
json["server"] = SERVER
|
||||
|
||||
if "server" not in json:
|
||||
user = "{port}".format(**json)
|
||||
else:
|
||||
user = "{server}:{port}".format(**json)
|
||||
message = "<{player_id}:{player_name}> {message}".format(**json)
|
||||
|
||||
if DISCORD_WEBHOOK:
|
||||
try:
|
||||
requests.post(DISCORD_WEBHOOK, data={"username": user, "content": sanitize(message)}).raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
print(repr(e))
|
||||
raise
|
||||
return no_content()
|
22
src/test/json.cpp
Normal file
22
src/test/json.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <engine/shared/fetcher.h>
|
||||
|
||||
TEST(Json, Escape)
|
||||
{
|
||||
char aBuf[128];
|
||||
char aSmall[2];
|
||||
char aSix[6];
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), ""), "");
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "a"), "a");
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "\n"), "\\n");
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "\\"), "\\\\"); // https://www.xkcd.com/1638/
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "\x1b"), "\\u001b"); // escape
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "愛"), "愛");
|
||||
EXPECT_STREQ(EscapeJson(aBuf, sizeof(aBuf), "😂"), "😂");
|
||||
|
||||
// Truncations
|
||||
EXPECT_STREQ(EscapeJson(aSmall, sizeof(aSmall), "\\"), "");
|
||||
EXPECT_STREQ(EscapeJson(aSix, sizeof(aSix), "\x01"), "");
|
||||
EXPECT_STREQ(EscapeJson(aSix, sizeof(aSix), "aaaaaa"), "aaaaa");
|
||||
}
|
Loading…
Reference in a new issue