Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
29e31f90a6 | ||
|
b076bfc71a | ||
|
11962b7a70 | ||
|
971915faf4 | ||
|
0d6db033af | ||
|
64c189faff | ||
|
46b65222a4 | ||
|
5668140d13 | ||
|
2062971458 | ||
|
66e459334e | ||
|
b1b74fcb18 | ||
|
d22cbad4e4 | ||
|
c33cc36a96 | ||
|
7af9ba6966 | ||
a5a386ec2a | |||
|
eb80896b3a |
26
README.md
26
README.md
@ -1 +1,25 @@
|
|||||||
Just ping server and get stats.
|
## Что это?
|
||||||
|
|
||||||
|
Это проект, который предоставялет: простого онлайн ТГ бота ; аналитику сервера по типу пинг сервера, количество игроков.
|
||||||
|
|
||||||
|
<img src="https://gitea.404.mn/justuser/minestatsping/raw/branch/main/preview/2024-03-09_12-20.png" alt="drawing" width="400"/>
|
||||||
|
|
||||||
|
## Как использовать?
|
||||||
|
|
||||||
|
### Использование ТГ бота
|
||||||
|
|
||||||
|
1. Получить токен в https://t.me/BotFather
|
||||||
|
2. Запустить: `python online.py`
|
||||||
|
3. Вставить токен в `config.json`, где написано "token"
|
||||||
|
4. Запустить: `python online.py`
|
||||||
|
5. Проверить команду бота: `/online`
|
||||||
|
|
||||||
|
### Использование аналитики
|
||||||
|
|
||||||
|
1. Указать данные сервера в `ping.py`
|
||||||
|
- адрес - host
|
||||||
|
- порт - port
|
||||||
|
- протокол - prot ( https://minecraft.fandom.com/wiki/Protocol_version#Java_Edition )
|
||||||
|
2. Запустить сбор статистики: `python ping.py`
|
||||||
|
3. Запустить сайт: `python site_stat.py`
|
||||||
|
4. Зайти на сайт: `127.0.0.1:8050` (браузер)
|
26
db.py
Normal file
26
db.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not os.path.exists('config.json'):
|
||||||
|
db = {}
|
||||||
|
js = json.dumps(db, indent=2)
|
||||||
|
with open("config.json", "w") as outfile:
|
||||||
|
outfile.write(js)
|
||||||
|
print('Created new config.json')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
def read(file = 'config.json'):
|
||||||
|
if not os.path.exists(file):
|
||||||
|
with open(file, "w") as f:
|
||||||
|
f.write("{}")
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
with open(file, "r", encoding="utf-8") as openfile:
|
||||||
|
db = json.load(openfile)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def write(db, file = 'config.json'):
|
||||||
|
js = json.dumps(db, indent=2, ensure_ascii=False)
|
||||||
|
with open(file, "w", encoding="utf-8") as outfile:
|
||||||
|
outfile.write(js)
|
38
dump.py
38
dump.py
@ -1,38 +0,0 @@
|
|||||||
#Json
|
|
||||||
import json
|
|
||||||
def read():
|
|
||||||
global db
|
|
||||||
with open('db.json', 'r') as openfile:
|
|
||||||
db = json.load(openfile)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def dump():
|
|
||||||
read()
|
|
||||||
tmes = ''
|
|
||||||
for i in db:
|
|
||||||
ttime = db[i]
|
|
||||||
|
|
||||||
hours = ttime//60//60 ; ttime = ttime - hours*60*60
|
|
||||||
minutes = ttime//60 ; ttime = ttime - minutes*60
|
|
||||||
seconds = ttime
|
|
||||||
|
|
||||||
tmes = tmes + f'{i[:i.find("[")]} >> {hours}:{minutes}:{seconds}'+'<br>'
|
|
||||||
return tmes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Simple server
|
|
||||||
import tornado.httpserver, tornado.ioloop, tornado.web
|
|
||||||
|
|
||||||
class MainHandler(tornado.web.RequestHandler):
|
|
||||||
def get(self):
|
|
||||||
self.write(dump())
|
|
||||||
application = tornado.web.Application([
|
|
||||||
(r"/", MainHandler),
|
|
||||||
])
|
|
||||||
if __name__ == "__main__":
|
|
||||||
http_server = tornado.httpserver.HTTPServer(application)
|
|
||||||
http_server.listen(8888)
|
|
||||||
tornado.ioloop.IOLoop.instance().start()
|
|
||||||
|
|
20
func.py
Normal file
20
func.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from db import *
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
def stat_exist(date):
|
||||||
|
if not os.path.exists(f'data/{date}.json'):
|
||||||
|
db = {}
|
||||||
|
js = json.dumps(db, indent=2)
|
||||||
|
with open(f'data/{date}.json', "w") as outfile:
|
||||||
|
outfile.write(js)
|
||||||
|
|
||||||
|
# Заполняем БД, если она пустая
|
||||||
|
# Пинг
|
||||||
|
# "ping": {"time": ["14:30:36", "14:30:41"], "ms": [42, 39]}
|
||||||
|
db["ping"] = {"time": [], "ms": []}
|
||||||
|
# Онлайн
|
||||||
|
# "online": {"time": ["14:30:36", "14:30:41"], "count": [1, 0]}
|
||||||
|
db["online"] = {"time": [], "count":[]}
|
||||||
|
|
||||||
|
write(db, f'data/{date}.json')
|
63
online.py
Normal file
63
online.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from mctools import PINGClient
|
||||||
|
import telebot
|
||||||
|
|
||||||
|
from db import *
|
||||||
|
|
||||||
|
API_TOKEN = read()['token']
|
||||||
|
bot = telebot.TeleBot(API_TOKEN)
|
||||||
|
|
||||||
|
host = 'CoolFunZone.aternos.me'
|
||||||
|
port = 36413
|
||||||
|
# 764 - 1.20.2
|
||||||
|
prot = 764
|
||||||
|
global c
|
||||||
|
c = PINGClient(host, port, proto_num = prot)
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['online'])
|
||||||
|
def check_online(message):
|
||||||
|
global c
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = c.get_stats()
|
||||||
|
ms = c.ping()
|
||||||
|
except:
|
||||||
|
bot.reply_to(message, "🔴 Сервер оффлайн")
|
||||||
|
c.stop()
|
||||||
|
c = PINGClient(host, port, proto_num = prot)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
maxp = stats['players']['max']
|
||||||
|
onp = stats['players']['online']
|
||||||
|
|
||||||
|
# Фикс для aternos
|
||||||
|
if maxp == 0:
|
||||||
|
bot.reply_to(message, "🔴 Сервер оффлайн")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
first = True
|
||||||
|
for i in stats['players']['sample']:
|
||||||
|
if first == True:
|
||||||
|
pp = i[0][:i[0].find('[')]
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
pp = pp+ ' ; ' +i[0][:i[0].find('[')]
|
||||||
|
except:
|
||||||
|
pp = ''
|
||||||
|
|
||||||
|
bot.reply_to(message, f"""🟢 Игроки онлайн >> {onp}/{maxp}
|
||||||
|
|
||||||
|
{pp}
|
||||||
|
|
||||||
|
📡 {round(ms)} ms""")
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
bot.infinity_polling()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
115
ping.py
115
ping.py
@ -1,48 +1,87 @@
|
|||||||
from mctools import PINGClient
|
from mctools import PINGClient
|
||||||
ping = PINGClient('play.dmcraft.online')
|
from time import sleep, time
|
||||||
#stats = ping.get_stats()
|
|
||||||
|
|
||||||
#Work with JSON
|
from datetime import datetime
|
||||||
import json
|
now = datetime.now
|
||||||
def read():
|
|
||||||
global db
|
|
||||||
with open('db.json', 'r') as openfile:
|
|
||||||
db = json.load(openfile)
|
|
||||||
def write():
|
|
||||||
global db
|
|
||||||
js = json.dumps(db, indent=4)
|
|
||||||
with open("db.json", "w") as outfile:
|
|
||||||
outfile.write(js)
|
|
||||||
|
|
||||||
#My libraries
|
host = 'CoolFunZone.aternos.me'
|
||||||
from time import sleep
|
port = 36413
|
||||||
|
# 764 - 1.20.2
|
||||||
|
prot = 764
|
||||||
|
global c
|
||||||
|
c = PINGClient(host, port, proto_num = prot)
|
||||||
|
|
||||||
#Read
|
from db import *
|
||||||
read()
|
from func import *
|
||||||
|
|
||||||
|
date = now().strftime("%Y-%m-%d")
|
||||||
|
# Проверяем существует ли
|
||||||
|
stat_exist(date)
|
||||||
|
db = read(f'data/{date}.json')
|
||||||
|
|
||||||
|
# КАК ЧАСТО ОБНОВЛЯЕМ (секунды)
|
||||||
|
update = 60
|
||||||
|
|
||||||
ttime = 0
|
|
||||||
while True:
|
while True:
|
||||||
sleep(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#if True:
|
raw = c.get_stats()
|
||||||
stats = ping.get_stats()
|
ms = round( c.ping() ) # Пинг
|
||||||
if stats['players']['online'] != 0:
|
except:
|
||||||
for i in stats['players']['sample']:
|
c.stop()
|
||||||
#Add in db if not in db
|
c = PINGClient(host, port, proto_num = prot)
|
||||||
if i[0] not in db:
|
continue
|
||||||
db[i[0]] = 1 + ttime
|
|
||||||
write()
|
|
||||||
else:
|
|
||||||
db[i[0]] = db[i[0]] + 1 + ttime
|
|
||||||
write()
|
|
||||||
ttime = 0
|
|
||||||
|
|
||||||
except Exception as e:
|
if "sample" in raw["players"]:
|
||||||
if e == '[Errno 32] Broken pipe':
|
# Список игроков
|
||||||
sleep(60)
|
players_raw = raw["players"]["sample"]
|
||||||
ttime = 59
|
# Оставляем только ники (без айди)
|
||||||
print("CATCHED")
|
players = []
|
||||||
|
for i in players_raw:
|
||||||
|
players.append(i[0][:i[0].find('[')])
|
||||||
|
# Онлайн
|
||||||
|
online = raw["players"]["online"]
|
||||||
|
else:
|
||||||
|
players = []
|
||||||
|
online = 0
|
||||||
|
|
||||||
|
# Фикс атерноса
|
||||||
|
max = raw["players"]["max"]
|
||||||
|
if max == 0:
|
||||||
|
ms = 0
|
||||||
|
|
||||||
|
# Открываем БД.
|
||||||
|
# Дата
|
||||||
|
date = now().strftime("%Y-%m-%d")
|
||||||
|
# Проверяем существует ли
|
||||||
|
stat_exist(date)
|
||||||
|
db = read(f'data/{date}.json')
|
||||||
|
|
||||||
|
# Заполняем БД
|
||||||
|
# Пинг
|
||||||
|
db["ping"]["time"].append( now().strftime('%H:%M') )
|
||||||
|
db["ping"]["ms"].append( ms )
|
||||||
|
# Онлайн
|
||||||
|
db["online"]["time"].append( now().strftime('%H:%M') )
|
||||||
|
db["online"]["count"].append( online )
|
||||||
|
|
||||||
|
|
||||||
|
# Топ игроков по времени и последнее время захода
|
||||||
|
stat = read('data/stat.json')
|
||||||
|
# Перебираем игроков
|
||||||
|
for i in players:
|
||||||
|
# Если игрок уже в базе
|
||||||
|
if i in stat["players"]["time"]:
|
||||||
|
stat["players"]["time"][i] += update
|
||||||
else:
|
else:
|
||||||
print(e)
|
stat["players"]["time"][i] = update
|
||||||
|
# Время захода
|
||||||
|
stat["players"]["last"][i] = time()
|
||||||
|
|
||||||
|
|
||||||
|
# Записываем изменения
|
||||||
|
write(db, f'data/{date}.json')
|
||||||
|
|
||||||
|
write(stat, 'data/stat.json')
|
||||||
|
|
||||||
|
# Задержка
|
||||||
|
sleep(update)
|
||||||
|
BIN
preview/2024-03-09_12-20.png
Normal file
BIN
preview/2024-03-09_12-20.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
176
site_stat.py
Normal file
176
site_stat.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
from dash import Dash, html, dcc, Input, Output, callback
|
||||||
|
import plotly.graph_objs as go
|
||||||
|
|
||||||
|
from time import strftime, localtime
|
||||||
|
|
||||||
|
from db import *
|
||||||
|
|
||||||
|
from func import stat_exist
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now
|
||||||
|
|
||||||
|
app = Dash(__name__)
|
||||||
|
|
||||||
|
app.layout = html.Div([
|
||||||
|
|
||||||
|
html.Div([
|
||||||
|
dcc.Dropdown(["Сервер", "Игроки"], "Сервер", id = "select", clearable=False, searchable=False, style = {"flex": 70}),
|
||||||
|
dcc.Dropdown(id = "date", clearable=True, searchable=False, style = {"flex": 30}, placeholder="Дата"),
|
||||||
|
], style = {"display": "flex", "flexDirection": "row"}),
|
||||||
|
|
||||||
|
html.Div([], id = "out")
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def server(date = None):
|
||||||
|
if not date:
|
||||||
|
date = now().strftime("%Y-%m-%d")
|
||||||
|
stat_exist(date)
|
||||||
|
db = read(f'data/{date}.json')
|
||||||
|
|
||||||
|
# Настройка для графика без рамок
|
||||||
|
layout = go.Layout( margin=go.layout.Margin(l=0, r=0, b=0, t=0,), height = 300 )
|
||||||
|
|
||||||
|
ping = db["ping"]
|
||||||
|
ping_fig = go.Figure(data=[go.Scatter(x = ping["time"], y = ping["ms"] )], layout = layout )
|
||||||
|
#ping_fig.update_xaxes(visible=False)
|
||||||
|
ping_fig.update_traces(hoverinfo='y')
|
||||||
|
#ping_fig.update_layout(yaxis=dict(dtick=1))
|
||||||
|
|
||||||
|
online = db["online"]
|
||||||
|
online_fig = go.Figure(data=[go.Scatter(x = online["time"], y = online["count"] )], layout = layout)
|
||||||
|
#online_fig.update_xaxes(visible=False)
|
||||||
|
online_fig.update_traces(hoverinfo='y')
|
||||||
|
online_fig.update_layout(yaxis=dict(dtick=1))
|
||||||
|
|
||||||
|
gr_conf = {"displayModeBar": False, "showAxisDragHandles": False, "showAxisRangeEntryBoxes": False, "fillFrame": False}
|
||||||
|
return html.Div([
|
||||||
|
html.H2("Пинг"),
|
||||||
|
dcc.Graph(
|
||||||
|
id = 'ping',
|
||||||
|
figure = ping_fig,
|
||||||
|
config = gr_conf
|
||||||
|
),
|
||||||
|
html.H2("Онлайн"),
|
||||||
|
dcc.Graph(
|
||||||
|
id = 'online',
|
||||||
|
figure = online_fig,
|
||||||
|
config = gr_conf
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def players():
|
||||||
|
db = read('data/stat.json')
|
||||||
|
|
||||||
|
layout = go.Layout( margin=go.layout.Margin(l=0, r=0, b=0, t=0,), height = 300 )
|
||||||
|
|
||||||
|
time = db["players"]["time"]
|
||||||
|
# Конвертируем в вид ["hythe", "freedom"] и [1764, 6532]
|
||||||
|
time_nick = list(time.keys())
|
||||||
|
time_time = list(time.values())
|
||||||
|
# Сортируем по времени (3, 2, 1)
|
||||||
|
sorted = False
|
||||||
|
while not sorted:
|
||||||
|
sorted = True
|
||||||
|
for i in range(len(time_time) - 1):
|
||||||
|
# Если [5, 7] => [7, 5]
|
||||||
|
if time_time[i + 1] > time_time[i]:
|
||||||
|
sorted = False
|
||||||
|
# Меняем местами
|
||||||
|
time_time[i], time_time[i + 1] = time_time[i + 1], time_time[i]
|
||||||
|
time_nick[i], time_nick[i + 1] = time_nick[i + 1], time_nick[i]
|
||||||
|
|
||||||
|
# Конвертируем 12960 => 3:36
|
||||||
|
conv_time = []
|
||||||
|
for i in range(len(time_time)):
|
||||||
|
seconds = time_time[i]
|
||||||
|
hours = seconds // 3600
|
||||||
|
minutes = (seconds-hours*3600) // 60
|
||||||
|
|
||||||
|
conv_time.append(f"Время: {hours} час. {minutes} мин.")
|
||||||
|
|
||||||
|
time_fig = go.Figure(data=[go.Bar(x = time_nick, y = time_time, hovertemplate=conv_time, name="",
|
||||||
|
text=conv_time, textposition='inside', insidetextfont=dict(size=16, color='white')
|
||||||
|
)], layout = layout )
|
||||||
|
time_fig.update_yaxes(visible=False)
|
||||||
|
#time_fig.update_xaxes(visible=False)
|
||||||
|
time_fig.update_xaxes({"tickfont": {"size": 18} })
|
||||||
|
time_fig.update_traces(hoverinfo='y')
|
||||||
|
|
||||||
|
last = db["players"]["last"]
|
||||||
|
# Конвертируем в вид ["hythe"] и [1434657.567523]
|
||||||
|
last_nick = list(last.keys())
|
||||||
|
last_time = list(last.values())
|
||||||
|
# Сортируем по времени (3, 2, 1)
|
||||||
|
# Сортируем по времени
|
||||||
|
sorted = False
|
||||||
|
while not sorted:
|
||||||
|
sorted = True
|
||||||
|
for i in range(len(last_time) - 1):
|
||||||
|
# Если [5, 7] => [7, 5]
|
||||||
|
if float(last_time[i + 1]) > float(last_time[i]):
|
||||||
|
sorted = False
|
||||||
|
# Меняем местами
|
||||||
|
last_time[i], last_time[i + 1] = last_time[i + 1], last_time[i]
|
||||||
|
last_nick[i], last_nick[i + 1] = last_nick[i + 1], last_nick[i]
|
||||||
|
|
||||||
|
# Конвертируем 1709475944.625857 => '2012-09-13 02:22:50'
|
||||||
|
for i in range(len(last_time)):
|
||||||
|
last_time[i] = strftime('%Y-%m-%d %H:%M:%S', localtime( float(last_time[i]) ))
|
||||||
|
|
||||||
|
last_fig = go.Figure(data= [go.Table(header={"values": ["Игрок","Время захода"], "font": {"size": 18}, "height": 40},
|
||||||
|
cells={"values": [last_nick, last_time], "font_size": 18, "height": 30},
|
||||||
|
) ], layout = layout )
|
||||||
|
|
||||||
|
# Подгоняем высоту таблицы: 40 пикселей заголовка + кол.элементов * 30
|
||||||
|
last_fig.layout.update({'height': 40 + len(last_nick)*30 })
|
||||||
|
|
||||||
|
gr_conf = {"displayModeBar": False, "showAxisDragHandles": False, "showAxisRangeEntryBoxes": False, "fillFrame": False}
|
||||||
|
return html.Div([
|
||||||
|
html.H2("Топ по времени"),
|
||||||
|
dcc.Graph(
|
||||||
|
id = 'ping',
|
||||||
|
figure = time_fig,
|
||||||
|
config = gr_conf
|
||||||
|
),
|
||||||
|
html.H2("Последний заход"),
|
||||||
|
dcc.Graph(
|
||||||
|
id = 'ping',
|
||||||
|
figure = last_fig,
|
||||||
|
config = gr_conf
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def av_stats():
|
||||||
|
raw = sorted( next(os.walk('data/'), (None, None, []))[2] )
|
||||||
|
# Убираем .json
|
||||||
|
dates = []
|
||||||
|
for i in raw:
|
||||||
|
if i != "stat.json":
|
||||||
|
dates.append(i[:i.find(".json")])
|
||||||
|
|
||||||
|
return dates
|
||||||
|
|
||||||
|
@callback(
|
||||||
|
Output('out', 'children'),
|
||||||
|
Output('date', 'options'),
|
||||||
|
Input('date', 'value'),
|
||||||
|
Input('select', 'value'),
|
||||||
|
)
|
||||||
|
def main(date, select):
|
||||||
|
if select == "Сервер":
|
||||||
|
return server(date), av_stats()
|
||||||
|
elif select == "Игроки":
|
||||||
|
return players(), av_stats()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
app.run(debug=False)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
except:
|
||||||
|
pass
|
Loading…
Reference in New Issue
Block a user