new auto masterserver

This commit is contained in:
proller 2013-02-22 02:00:44 +04:00 committed by Ilya Zhuravlev
parent ef6b8bee07
commit ee07c3f7cf
20 changed files with 6919 additions and 49 deletions

2
.gitignore vendored
View File

@ -40,6 +40,8 @@ src/cguittfont/CMakeFiles/
src/cguittfont/libcguittfont.a
src/cguittfont/cmake_install.cmake
src/cguittfont/Makefile
src/json/CMakeFiles/
src/json/libjson.a
CMakeCache.txt
CPackConfig.cmake
CPackSourceConfig.cmake

View File

@ -0,0 +1,18 @@
# Look for json, use our own if not found
#FIND_PATH(JSON_INCLUDE_DIR json.h)
#FIND_LIBRARY(JSON_LIBRARY NAMES json)
#IF(JSON_LIBRARY AND JSON_INCLUDE_DIR)
# SET( JSON_FOUND TRUE )
#ENDIF(JSON_LIBRARY AND JSON_INCLUDE_DIR)
#IF(JSON_FOUND)
# MESSAGE(STATUS "Found system json header file in ${JSON_INCLUDE_DIR}")
# MESSAGE(STATUS "Found system json library ${JSON_LIBRARY}")
#ELSE(JSON_FOUND)
SET(JSON_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/json)
SET(JSON_LIBRARY json)
MESSAGE(STATUS "Using project json library")
#ENDIF(JSON_FOUND)

View File

@ -159,7 +159,7 @@
#media_fetch_threads = 8
# Url to the server list displayed in the Multiplayer Tab
#serverlist_url = servers.minetest.ru/server.list
#serverlist_url = servers.minetest.net
# File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab
#serverlist_file = favoriteservers.txt
@ -172,6 +172,16 @@
# Server stuff
#
# Name of server
#server_name = Minetest server
# Description of server
#server_description = mine here
# Domain name of server
#server_address = game.minetest.net
# Homepage of server
#server_url = http://minetest.net
# Automaticaly report to masterserver
#server_announce = 0
# Default game (default when creating a new world)
#default_game = minetest
# World directory (everything in the world is stored here)

View File

@ -5,6 +5,7 @@ cmake_minimum_required( VERSION 2.6 )
mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH)
mark_as_advanced(JTHREAD_INCLUDE_DIR JTHREAD_LIBRARY)
mark_as_advanced(SQLITE3_INCLUDE_DIR SQLITE3_LIBRARY)
mark_as_advanced(JSON_INCLUDE_DIR JSON_LIBRARY)
option(ENABLE_CURL "Enable cURL support for fetching media" 1)
@ -170,6 +171,7 @@ endif()
find_package(Jthread REQUIRED)
find_package(Sqlite3 REQUIRED)
find_package(Json REQUIRED)
if(USE_FREETYPE)
find_package(Freetype REQUIRED)
@ -242,6 +244,7 @@ set(common_SRCS
biome.cpp
clientserver.cpp
staticobject.cpp
serverlist.cpp
util/serialize.cpp
util/directiontables.cpp
util/numeric.cpp
@ -303,7 +306,6 @@ set(minetest_SRCS
filecache.cpp
tile.cpp
shader.cpp
serverlist.cpp
game.cpp
main.cpp
)
@ -332,6 +334,7 @@ include_directories(
${JTHREAD_INCLUDE_DIR}
${SQLITE3_INCLUDE_DIR}
${LUA_INCLUDE_DIR}
${JSON_INCLUDE_DIR}
)
if(USE_FREETYPE)
@ -341,6 +344,12 @@ if(USE_FREETYPE)
)
endif(USE_FREETYPE)
if(USE_CURL)
include_directories(
${CURL_INCLUDE_DIR}
)
endif(USE_CURL)
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin")
if(BUILD_CLIENT)
@ -359,18 +368,15 @@ if(BUILD_CLIENT)
${JTHREAD_LIBRARY}
${SQLITE3_LIBRARY}
${LUA_LIBRARY}
${JSON_LIBRARY}
${PLATFORM_LIBS}
${CLIENT_PLATFORM_LIBS}
)
if(USE_CURL)
target_link_libraries(
${PROJECT_NAME}
${CURL_LIBRARY}
)
include_directories(
${CURL_INCLUDE_DIR}
)
endif(USE_CURL)
if(USE_FREETYPE)
target_link_libraries(
@ -388,12 +394,20 @@ if(BUILD_SERVER)
${ZLIB_LIBRARIES}
${JTHREAD_LIBRARY}
${SQLITE3_LIBRARY}
${JSON_LIBRARY}
${GETTEXT_LIBRARY}
${LUA_LIBRARY}
${PLATFORM_LIBS}
)
if(USE_CURL)
target_link_libraries(
${PROJECT_NAME}server
${CURL_LIBRARY}
)
endif(USE_CURL)
endif(BUILD_SERVER)
#
# Set some optimizations and tweaks
#
@ -569,4 +583,9 @@ else (LUA_FOUND)
add_subdirectory(lua)
endif (LUA_FOUND)
if (JSON_FOUND)
else (JSON_FOUND)
add_subdirectory(json)
endif (JSON_FOUND)
#end

View File

@ -130,8 +130,13 @@ void set_default_settings(Settings *settings)
settings->setDefault("media_fetch_threads", "8");
settings->setDefault("serverlist_url", "servers.minetest.ru/server.list");
settings->setDefault("serverlist_url", "servers.minetest.net");
settings->setDefault("serverlist_file", "favoriteservers.txt");
settings->setDefault("server_announce", "false");
settings->setDefault("server_url", "");
settings->setDefault("server_address", "");
settings->setDefault("server_name", "");
settings->setDefault("server_description", "");
settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf"));
settings->setDefault("font_size", "13");

View File

@ -108,6 +108,7 @@ enum
GUI_ID_ENABLE_PARTICLES_CB,
GUI_ID_DAMAGE_CB,
GUI_ID_CREATIVE_CB,
GUI_ID_PUBLIC_CB,
GUI_ID_JOIN_GAME_BUTTON,
GUI_ID_CHANGE_KEYS_BUTTON,
GUI_ID_DELETE_WORLD_BUTTON,
@ -562,6 +563,14 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
Environment->addCheckBox(m_data->enable_damage, rect, this, GUI_ID_DAMAGE_CB,
wgettext("Enable Damage"));
}
#if USE_CURL
{
core::rect<s32> rect(0, 0, 250, 30);
rect += m_topleft_server + v2s32(30+20+250+20, 60);
Environment->addCheckBox(m_data->enable_public, rect, this, GUI_ID_PUBLIC_CB,
wgettext("Public"));
}
#endif
// Delete world button
{
core::rect<s32> rect(0, 0, 130, 30);
@ -841,6 +850,11 @@ void GUIMainMenu::readInput(MainMenuData *dst)
if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
dst->enable_damage = ((gui::IGUICheckBox*)e)->isChecked();
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_PUBLIC_CB);
if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
dst->enable_public = ((gui::IGUICheckBox*)e)->isChecked();
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_FANCYTREE_CB);
if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
@ -912,8 +926,8 @@ void GUIMainMenu::readInput(MainMenuData *dst)
{
ServerListSpec server =
getServerListSpec(wide_to_narrow(dst->address), wide_to_narrow(dst->port));
dst->servername = server.name;
dst->serverdescription = server.description;
dst->servername = server["name"].asString();
dst->serverdescription = server["description"].asString();
}
}
@ -1174,12 +1188,30 @@ void GUIMainMenu::updateGuiServerList()
i != m_data->servers.end(); i++)
{
std::string text;
if (i->name != "" && i->description != "")
text = i->name + " (" + i->description + ")";
else if (i->name !="")
text = i->name;
if ((*i)["clients"].asString().size())
text += (*i)["clients"].asString();
if ((*i)["clients_max"].asString().size())
text += "/" + (*i)["clients_max"].asString();
text += " ";
if ((*i)["version"].asString().size())
text += (*i)["version"].asString() + " ";
if ((*i)["password"].asString().size())
text += "*";
if ((*i)["creative"].asString().size())
text += "C";
if ((*i)["damage"].asString().size())
text += "D";
if ((*i)["pvp"].asString().size())
text += "P";
text += " ";
if ((*i)["name"] != "" && (*i)["description"] != "")
text += (*i)["name"].asString() + " (" + (*i)["description"].asString() + ")";
else if ((*i)["name"] !="")
text += (*i)["name"].asString();
else
text = i->address + ":" + i->port;
text += (*i)["address"].asString() + ":" + (*i)["port"].asString();
serverlist->addItem(narrow_to_wide(text).c_str());
}
@ -1191,26 +1223,26 @@ void GUIMainMenu::serverListOnSelected()
{
gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST);
u16 id = serverlist->getSelected();
if (id < 0) return;
//if (id < 0) return; // u16>0!
((gui::IGUIEditBox*)getElementFromId(GUI_ID_ADDRESS_INPUT))
->setText(narrow_to_wide(m_data->servers[id].address).c_str());
->setText(narrow_to_wide(m_data->servers[id]["address"].asString()).c_str());
((gui::IGUIEditBox*)getElementFromId(GUI_ID_PORT_INPUT))
->setText(narrow_to_wide(m_data->servers[id].port).c_str());
->setText(narrow_to_wide(m_data->servers[id]["port"].asString()).c_str());
}
}
ServerListSpec GUIMainMenu::getServerListSpec(std::string address, std::string port)
{
ServerListSpec server;
server.address = address;
server.port = port;
server["address"] = address;
server["port"] = port;
for(std::vector<ServerListSpec>::iterator i = m_data->servers.begin();
i != m_data->servers.end(); i++)
{
if (i->address == address && i->port == port)
if ((*i)["address"] == address && (*i)["port"] == port)
{
server.description = i->description;
server.name = i->name;
server["description"] = (*i)["description"];
server["name"] = (*i)["name"];
break;
}
}

View File

@ -55,6 +55,7 @@ struct MainMenuData
// Server options
bool creative_mode;
bool enable_damage;
bool enable_public;
int selected_world;
bool simple_singleplayer_mode;
// Actions
@ -77,6 +78,7 @@ struct MainMenuData
// Server opts
creative_mode(false),
enable_damage(false),
enable_public(false),
selected_world(0),
simple_singleplayer_mode(false),
// Actions

14
src/json/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
if( UNIX )
set(json_SRCS jsoncpp.cpp)
set(json_platform_LIBS "")
else( UNIX )
set(json_SRCS jsoncpp.cpp)
set(json_platform_LIBS "")
endif( UNIX )
add_library(json ${json_SRCS})
target_link_libraries(
json
${json_platform_LIBS}
)

16
src/json/UPDATING Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
cd ..
svn co https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp jsoncpp
svn up jsoncpp
cd jsoncpp
python amalgamate.py
cp -R dist/json ..
cp dist/jsoncpp.cpp ../json
# maybe you need to patch:
# src/json/jsoncpp.cpp:
# -#include <json/json.h>
# +#include "json/json.h"
#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/src/lib_json json
#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/include/json json

1914
src/json/json.h Normal file

File diff suppressed because it is too large Load Diff

4367
src/json/jsoncpp.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1095,6 +1095,7 @@ int main(int argc, char *argv[])
#else
bool run_dedicated_server = cmd_args.getFlag("server");
#endif
g_settings->set("server_dedicated", run_dedicated_server ? "true" : "false");
if(run_dedicated_server)
{
DSTACK("Dedicated server branch");
@ -1593,6 +1594,7 @@ int main(int argc, char *argv[])
g_settings->set("creative_mode", itos(menudata.creative_mode));
g_settings->set("enable_damage", itos(menudata.enable_damage));
g_settings->set("server_announce", itos(menudata.enable_public));
g_settings->set("name", playername);
g_settings->set("address", address);
g_settings->set("port", itos(port));
@ -1619,10 +1621,10 @@ int main(int argc, char *argv[])
else if (address != "")
{
ServerListSpec server;
server.name = menudata.servername;
server.address = wide_to_narrow(menudata.address);
server.port = wide_to_narrow(menudata.port);
server.description = menudata.serverdescription;
server["name"] = menudata.servername;
server["address"] = wide_to_narrow(menudata.address);
server["port"] = wide_to_narrow(menudata.port);
server["description"] = menudata.serverdescription;
ServerList::insert(server);
}

View File

@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "sound.h" // dummySoundManager
#include "event_manager.h"
#include "hex.h"
#include "serverlist.h"
#include "util/string.h"
#include "util/pointedthing.h"
#include "util/mathconstants.h"
@ -961,9 +962,11 @@ Server::Server(
{
m_liquid_transform_timer = 0.0;
m_print_info_timer = 0.0;
m_masterserver_timer = 0.0;
m_objectdata_timer = 0.0;
m_emergethread_trigger_timer = 0.0;
m_savemap_timer = 0.0;
m_clients_number = 0;
m_env_mutex.Init();
m_con_mutex.Init();
@ -1505,7 +1508,7 @@ void Server::AsyncRunStep()
counter = 0.0;
JMutexAutoLock lock2(m_con_mutex);
m_clients_number = 0;
if(m_clients.size() != 0)
infostream<<"Players:"<<std::endl;
for(core::map<u16, RemoteClient*>::Iterator
@ -1519,10 +1522,25 @@ void Server::AsyncRunStep()
continue;
infostream<<"* "<<player->getName()<<"\t";
client->PrintInfo(infostream);
++m_clients_number;
}
}
}
#if USE_CURL
// send masterserver announce
{
float &counter = m_masterserver_timer;
if((!counter || counter >= 300.0) && g_settings->getBool("server_announce") == true)
{
ServerList::sendAnnounce(!counter ? "start" : "update", m_clients_number);
counter = 0.01;
}
counter += dtime;
}
#endif
//if(g_settings->getBool("enable_experimental"))
{
@ -5186,6 +5204,10 @@ void dedicated_server_loop(Server &server, bool &kill)
if(server.getShutdownRequested() || kill)
{
infostream<<"Dedicated server quitting"<<std::endl;
#if USE_CURL
if(g_settings->getBool("server_announce") == true)
ServerList::sendAnnounce("delete");
#endif
break;
}

View File

@ -720,6 +720,7 @@ private:
// Some timers
float m_liquid_transform_timer;
float m_print_info_timer;
float m_masterserver_timer;
float m_objectdata_timer;
float m_emergethread_trigger_timer;
float m_savemap_timer;
@ -737,6 +738,7 @@ private:
JMutex m_con_mutex;
// Connected clients (behind the con mutex)
core::map<u16, RemoteClient*> m_clients;
u16 m_clients_number; //for announcing masterserver
// Bann checking
BanManager m_banmanager;

View File

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "porting.h"
#include "log.h"
#include "json/json.h"
#if USE_CURL
#include <curl/curl.h>
#endif
@ -83,7 +84,7 @@ std::vector<ServerListSpec> getOnline()
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, g_settings->get("serverlist_url").c_str());
curl_easy_setopt(curl, CURLOPT_URL, (g_settings->get("serverlist_url")+"/list").c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
@ -92,8 +93,7 @@ std::vector<ServerListSpec> getOnline()
errorstream<<"Serverlist at url "<<g_settings->get("serverlist_url")<<" not found (internet connection?)"<<std::endl;
curl_easy_cleanup(curl);
}
return ServerList::deSerialize(liststring);
return ServerList::deSerializeJson(liststring);
}
#endif
@ -106,8 +106,8 @@ bool deleteEntry (ServerListSpec server)
std::vector<ServerListSpec> serverlist = ServerList::getLocal();
for(unsigned i = 0; i < serverlist.size(); i++)
{
if (serverlist[i].address == server.address
&& serverlist[i].port == server.port)
if (serverlist[i]["address"] == server["address"]
&& serverlist[i]["port"] == server["port"])
{
serverlist.erase(serverlist.begin() + i);
}
@ -150,17 +150,21 @@ std::vector<ServerListSpec> deSerialize(std::string liststring)
{
std::vector<ServerListSpec> serverlist;
std::istringstream stream(liststring);
std::string line;
std::string line, tmp;
while (std::getline(stream, line))
{
std::transform(line.begin(), line.end(),line.begin(), ::toupper);
if (line == "[SERVER]")
{
ServerListSpec thisserver;
std::getline(stream, thisserver.name);
std::getline(stream, thisserver.address);
std::getline(stream, thisserver.port);
std::getline(stream, thisserver.description);
std::getline(stream, tmp);
thisserver["name"] = tmp;
std::getline(stream, tmp);
thisserver["address"] = tmp;
std::getline(stream, tmp);
thisserver["port"] = tmp;
std::getline(stream, tmp);
thisserver["description"] = tmp;
serverlist.push_back(thisserver);
}
}
@ -173,13 +177,100 @@ std::string serialize(std::vector<ServerListSpec> serverlist)
for(std::vector<ServerListSpec>::iterator i = serverlist.begin(); i != serverlist.end(); i++)
{
liststring += "[server]\n";
liststring += i->name + "\n";
liststring += i->address + "\n";
liststring += i->port + "\n";
liststring += i->description + "\n";
liststring += (*i)["name"].asString() + "\n";
liststring += (*i)["address"].asString() + "\n";
liststring += (*i)["port"].asString() + "\n";
liststring += (*i)["description"].asString() + "\n";
liststring += "\n";
}
return liststring;
}
std::vector<ServerListSpec> deSerializeJson(std::string liststring)
{
std::vector<ServerListSpec> serverlist;
Json::Value root;
Json::Reader reader;
std::istringstream stream(liststring);
if (!liststring.size()) {
return serverlist;
}
if (!reader.parse( stream, root ) )
{
errorstream << "Failed to parse server list " << reader.getFormattedErrorMessages();
return serverlist;
}
if (root["list"].isArray())
for (unsigned int i = 0; i < root["list"].size(); i++)
{
if (root["list"][i].isObject()) {
serverlist.push_back(root["list"][i]);
}
}
return serverlist;
}
std::string serializeJson(std::vector<ServerListSpec> serverlist)
{
Json::Value root;
Json::Value list(Json::arrayValue);
for(std::vector<ServerListSpec>::iterator i = serverlist.begin(); i != serverlist.end(); i++)
{
list.append(*i);
}
root["list"] = list;
Json::StyledWriter writer;
return writer.write( root );
}
#if USE_CURL
static size_t ServerAnnounceCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
return 0;
//((std::string*)userp)->append((char*)contents, size * nmemb);
//return size * nmemb;
}
void sendAnnounce(std::string action, u16 clients) {
Json::Value server;
if (action.size())
server["action"] = action;
server["port"] = g_settings->get("port");
if (action != "del") {
server["name"] = g_settings->get("server_name");
server["description"] = g_settings->get("server_description");
server["address"] = g_settings->get("server_address");
server["version"] = VERSION_STRING;
server["url"] = g_settings->get("server_url");
server["creative"] = g_settings->get("creative_mode");
server["damage"] = g_settings->get("enable_damage");
server["dedicated"] = g_settings->get("server_dedicated");
server["password"] = g_settings->getBool("disallow_empty_password");
server["pvp"] = g_settings->getBool("enable_pvp");
server["clients"] = clients;
server["clients_max"] = g_settings->get("max_users");
}
if(server["action"] == "start")
actionstream << "announcing to " << g_settings->get("serverlist_url") << std::endl;
Json::StyledWriter writer;
CURL *curl;
curl = curl_easy_init();
if (curl)
{
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, (g_settings->get("serverlist_url")+std::string("/announce?json=")+curl_easy_escape(curl, writer.write( server ).c_str(), 0)).c_str());
//curl_easy_setopt(curl, CURLOPT_USERAGENT, "minetest");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::ServerAnnounceCallback);
//curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1);
res = curl_easy_perform(curl);
//if (res != CURLE_OK)
// errorstream<<"Serverlist at url "<<g_settings->get("serverlist_url")<<" not found (internet connection?)"<<std::endl;
curl_easy_cleanup(curl);
}
}
#endif
} //namespace ServerList

View File

@ -19,17 +19,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include "config.h"
#include "json/json.h"
#ifndef SERVERLIST_HEADER
#define SERVERLIST_HEADER
struct ServerListSpec
{
std::string name;
std::string address;
std::string port;
std::string description;
};
typedef Json::Value ServerListSpec;
namespace ServerList
{
@ -41,6 +36,11 @@ namespace ServerList
bool insert(ServerListSpec server);
std::vector<ServerListSpec> deSerialize(std::string liststring);
std::string serialize(std::vector<ServerListSpec>);
std::vector<ServerListSpec> deSerializeJson(std::string liststring);
std::string serializeJson(std::vector<ServerListSpec>);
#if USE_CURL
void sendAnnounce(std::string action = "", u16 clients = 0);
#endif
} //ServerList namespace
#endif

11
util/master/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Minetest server list</title>
<link rel="stylesheet" href="style.css"/>
</head>
<body><div id="table"></div></body>
</html>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="list.js"></script>

72
util/master/list.js Normal file
View File

@ -0,0 +1,72 @@
function e(s) {
if (typeof s === "undefined") s = '';
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); //mc"
}
function human_time(t) {
var n = 's';
if (!t || t < 0) t = 0;
var f = 0;
var s = parseInt((new Date().getTime() / 1000 - (t || 0)));
if (!s || s <= 0) s = 0;
if (s == 0) return 'now';
if (s >= 60) {
s /= 60;
n = 'm';
if (s >= 60) {
s /= 60;
n = 'h';
f = 1;
if (s >= 24) {
s /= 24;
n = 'd';
f = 1;
if (s >= 30) {
s /= 30;
n = 'M';
f = 1;
if (s >= 12) {
s /= 12;
n = 'y';
f = 1;
}
}
}
}
}
return ((f ? parseFloat(s).toFixed(1) : parseInt(s)) + n);
}
function success(r) {
if (!r || !r.list) return;
var h = '<table><tr><th>ip:port</th><th>clients, max</th><th>version</th><th>name</th><th>desc</th><th>flags</th><th>updated/started</th><th>ping</th></tr>';
for (var i = 0; i < r.list.length; ++i) {
var s = r.list[i];
if (!s) continue;
h += '<tr>';
h += '<td>' + e(s.address) + ':' + e(s.port) + '</td>';
h += '<td>' + e(s.clients) + (s.clients_max ? '/' + e(s.clients_max) : '') + (s.clients_top ? ', ' + s.clients_top : '') + '</td>';
h += '<td>' + e(s.version) + '</td>';
h += '<td>';
if (s.url) h += '<a href="' + e(s.url) + '">';
h += e(s.name || s.url);
if (s.url) h += '</a>';
h += '</td>';
h += '<td>' + e(s.description) + '</td>';
h += '<td>' + e(s.password ? 'Pwd ' : '') + (s.creative ? 'Cre ' : '') + (s.damage ? 'Dmg ' : '') + (s.pvp ? 'Pvp ' : '') + (s.dedicated ? 'Ded ' : '') + '</td>';
if (!s.time || s.time < 0) s.time = 0;
if (!s.start || s.start < 0) s.start = 0;
h += '<td>' + human_time(s.time) + (s.start ? '/' + human_time(s.start) : '') + '</td>';
h += '<td>' + (s.ping ? parseFloat(s.ping).toFixed(3)*1000 : '') + '</td>';
h += '</tr>';
}
h += '</table>'
jQuery('#table').html(h);
}
function get() {
jQuery.ajax({
url: 'list',
dataType: 'json',
success: success
});
setTimeout(get, 60000);
}
get();

257
util/master/master.cgi Executable file
View File

@ -0,0 +1,257 @@
#!/usr/bin/perl
=info
install:
cpan JSON JSON::XS
touch list_full list
chmod a+rw list_full list
freebsd:
www/fcgiwrap www/nginx
rc.conf.local:
nginx_enable="YES"
fcgiwrap_enable="YES"
fcgiwrap_user="www"
nginx:
location / {
index index.html;
}
location /announce {
fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock;
fastcgi_param SCRIPT_FILENAME $document_root/master.cgi;
include fastcgi_params;
}
apache .htaccess:
AddHandler cgi-script .cgi
DirectoryIndex index.html
Options +ExecCGI +FollowSymLinks
Order allow,deny
<FilesMatch (\.(html?|cgi|fcgi|css|js|gif|png|jpe?g|ico)|(^)|\w+)$>
Allow from all
</FilesMatch>
Deny from all
=cut
use strict;
no strict qw(refs);
use warnings "NONFATAL" => "all";
no warnings qw(uninitialized);
use utf8;
use Socket;
use Time::HiRes qw(time sleep);
use IO::Socket::INET;
use JSON;
use Net::Ping;
our $root_path;
($ENV{'SCRIPT_FILENAME'} || $0) =~ m|^(.+)[/\\].+?$|; #v0w
$root_path = $1 . '/' if $1;
$root_path =~ s|\\|/|g;
our %config = (
#debug => 1,
list_full => $root_path . 'list_full',
list_pub => $root_path . 'list',
time_purge => 86400 * 30,
time_alive => 650,
source_check => 1,
ping_timeout => 3,
ping => 1,
mineping => 1,
pingable => 1,
trusted => [qw( 176.9.122.10 )], #masterserver self ip - if server on same ip with masterserver doesnt announced
#blacklist => [], # [qw(2.3.4.5 4.5.6.7 8.9.0.1), '1.2.3.4', qr/^10\.20\.30\./, ], # list, or quoted, ips, or regex
);
do($root_path . 'config.pl');
our $ping = Net::Ping->new("udp", $config{ping_timeout});
$ping->hires();
sub get_params_one(@) {
local %_ = %{ref $_[0] eq 'HASH' ? shift : {}};
for (@_) {
tr/+/ /, s/%([a-f\d]{2})/pack 'H*', $1/gei for my ($k, $v) = /^([^=]+=?)=(.+)$/ ? ($1, $2) : (/^([^=]*)=?$/, /^-/);
$_{$k} = $v;
}
wantarray ? %_ : \%_;
}
sub get_params(;$$) { #v7
my ($string, $delim) = @_;
$delim ||= '&';
read(STDIN, local $_ = '', $ENV{'CONTENT_LENGTH'}) if !$string and $ENV{'CONTENT_LENGTH'};
local %_ =
$string
? get_params_one split $delim, $string
: (get_params_one(@ARGV), map { get_params_one split $delim, $_ } split(/;\s*/, $ENV{'HTTP_COOKIE'}), $ENV{'QUERY_STRING'}, $_);
wantarray ? %_ : \%_;
}
sub get_params_utf8(;$$) {
local $_ = &get_params;
utf8::decode $_ for %$_;
wantarray ? %$_ : $_;
}
sub file_rewrite(;$@) {
local $_ = shift;
return unless open my $fh, '>', $_;
print $fh @_;
}
sub file_read ($) {
open my $f, '<', $_[0] or return;
local $/ = undef;
my $ret = <$f>;
close $f;
return \$ret;
}
sub read_json {
my $ret = {};
eval { $ret = JSON->new->utf8->relaxed(1)->decode(${ref $_[0] ? $_[0] : file_read($_[0]) or \''} || '{}'); }; #'mc
warn "json error [$@] on [", ${ref $_[0] ? $_[0] : \$_[0]}, "]" if $@;
$ret;
}
sub printu (@) {
for (@_) {
print($_), next unless utf8::is_utf8($_);
my $s = $_;
utf8::encode($s);
print($s);
}
}
sub name_to_ip_noc($) {
my ($name) = @_;
unless ($name =~ /^\d+\.\d+\.\d+\.\d+$/) {
local $_ = (gethostbyname($name))[4];
return ($name, 1) unless length($_) == 4;
$name = inet_ntoa($_);
}
return $name;
}
sub float {
return ($_[0] < 8 and $_[0] - int($_[0]))
? sprintf('%.' . ($_[0] < 1 ? 3 : ($_[0] < 3 ? 2 : 1)) . 'f', $_[0])
: int($_[0]);
}
sub mineping ($$) {
my ($addr, $port) = @_;
warn "mineping($addr, $port)" if $config{debug};
my $data;
my $time = time;
eval {
my $socket = IO::Socket::INET->new(
'PeerAddr' => $addr,
'PeerPort' => $port,
'Proto' => 'udp',
'Timeout' => $config{ping_timeout},
);
$socket->send("\x4f\x45\x74\x03\x00\x00\x00\x01");
local $SIG{ALRM} = sub { die "alarm time out"; };
alarm $config{ping_timeout};
$socket->recv($data, POSIX::BUFSIZ) or die "recv: $!";
alarm 0;
1; # return value from eval on normalcy
} or return 0;
return 0 unless length $data;
$time = float(time - $time);
warn "recvd: ", length $data, " [$time]" if $config{debug};
return $time;
}
sub request (;$) {
my ($r) = @_;
$r ||= \%ENV;
my $param = get_params_utf8;
my $after = sub {
if ($param->{json}) {
my $j = {};
eval { $j = JSON->new->decode($param->{json}) || {} };
$param->{$_} = $j->{$_} for keys %$j;
delete $param->{json};
}
if (%$param) {
s/^false$// for values %$param;
$param->{ip} = $r->{REMOTE_ADDR};
for (@{$config{blacklist}}) {
return if $param->{ip} ~~ $_;
}
$param->{address} ||= $param->{ip};
if ($config{source_check} and name_to_ip_noc($param->{address}) ne $param->{ip} and !($param->{ip} ~~ $config{trusted})) {
warn("bad address [$param->{address}] ne [$param->{ip}]") if $config{debug};
return;
}
$param->{port} ||= 30000;
$param->{key} = "$param->{ip}:$param->{port}";
$param->{off} = time if $param->{action} ~~ 'delete';
if ($config{ping} and $param->{action} ne 'delete') {
if ($config{mineping}) {
$param->{ping} = mineping($param->{ip}, $param->{port});
} else {
$ping->port_number($param->{port});
$ping->service_check(0);
my ($pingret, $duration, $ip) = $ping->ping($param->{address});
if ($ip ne $param->{ip} and !($param->{ip} ~~ $config{trusted})) {
warn "strange ping ip [$ip] != [$param->{ip}]" if $config{debug};
return if $config{source_check} and !($param->{ip} ~~ $config{trusted});
}
$param->{ping} = $duration if $pingret;
warn " PING t=$config{ping_timeout}, $param->{address}:$param->{port} = ( $pingret, $duration, $ip )" if $config{debug};
}
}
my $list = read_json($config{list_full}) || {};
warn "readed[$config{list_full}] list size=", scalar @{$list->{list}};
my $listk = {map { $_->{key} => $_ } @{$list->{list}}};
my $old = $listk->{$param->{key}};
$param->{time} = $old->{time} if $param->{off};
$param->{time} ||= int time;
$param->{start} = $param->{action} ~~ 'start' ? $param->{time} : $old->{start} || $param->{time};
delete $param->{start} if $param->{off};
$param->{first} ||= $old->{first} || $old->{time} || $param->{time};
$param->{clients_top} = $old->{clients_top} if $old->{clients_top} > $param->{clients};
$param->{clients_top} ||= $param->{clients} || 0;
delete $param->{action};
$listk->{$param->{key}} = $param;
$list->{list} = [grep { $_->{time} > time - $config{time_purge} } values %$listk];
file_rewrite($config{list_full}, JSON->new->encode($list));
warn "writed[$config{list_full}] list size=", scalar @{$list->{list}};
$list->{list} = [
sort { $b->{clients} <=> $a->{clients} || $a->{start} <=> $b->{start} }
grep { $_->{time} > time - $config{time_alive} and !$_->{off} and (!$config{ping} or !$config{pingable} or $_->{ping}) }
@{$list->{list}}
];
file_rewrite($config{list_pub}, JSON->new->encode($list));
warn "writed[$config{list_pub}] list size=", scalar @{$list->{list}};
}
};
return [200, ["Content-type", "application/json"], [JSON->new->encode({})]], $after;
}
sub request_cgi {
my ($p, $after) = request(@_);
shift @$p;
printu join "\n", map { join ': ', @$_ } shift @$p;
printu "\n\n";
printu join '', map { join '', @$_ } @$p;
if (fork) {
unless ($config{debug}) {
close STDOUT;
close STDERR;
}
} else {
$after->() if ref $after ~~ 'CODE';
}
}
request_cgi() unless caller;

14
util/master/style.css Normal file
View File

@ -0,0 +1,14 @@
table {
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
}
td, th {
border: 1px solid gray;
}
div#table table {
width: 100%;
}