mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 16:25:36 +00:00
Compare commits
16 Commits
3f688df30b
...
6e46af4c13
Author | SHA1 | Date | |
---|---|---|---|
6e46af4c13 | |||
d21798aaf1 | |||
22105b2030 | |||
19c121f208 | |||
85c379bd9e | |||
a15eb316bb | |||
cecd6f13d6 | |||
df171aaa70 | |||
5a1cb8a133 | |||
d44cff1116 | |||
bc6cf60099 | |||
fc886ef415 | |||
bd7b988b01 | |||
4f5a6edc48 | |||
541849642c | |||
f364f29d79 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -137,3 +137,4 @@ dmypy.json
|
|||||||
/src/plugins
|
/src/plugins
|
||||||
/test/
|
/test/
|
||||||
*test.py
|
*test.py
|
||||||
|
logs/
|
27
README.md
27
README.md
@ -7,43 +7,48 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
|||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
- [ ] Server core
|
- [ ] Server core
|
||||||
- [x] BEAMP System
|
- [x] BeamMP System
|
||||||
- [x] Private access without key (Direct connect)
|
- [x] Private access (Without key, Direct connect)
|
||||||
- [x] Server authentication (For public access)
|
- [x] Public access (With key, listing in Launcher)
|
||||||
- [X] Player authentication
|
- [X] Player authentication
|
||||||
- [ ] TCP Server part:
|
- [ ] TCP Server part:
|
||||||
- [x] Handle code
|
- [x] Handle code
|
||||||
- [x] Understanding beamp header
|
- [x] Understanding BeamMP header
|
||||||
- [ ] Upload mods
|
- [ ] Upload mods
|
||||||
- [x] Connecting to the world
|
- [x] Connecting to the world
|
||||||
- [x] Chat
|
- [x] Chat
|
||||||
|
- [ ] Player counter _(Code: Ss)_
|
||||||
|
- [ ] Car state synchronizations _(Codes: We, Vi)_
|
||||||
- [ ] "ABG:" (compressed data)
|
- [ ] "ABG:" (compressed data)
|
||||||
- [x] Decompress data
|
- [x] Decompress data
|
||||||
- [ ] Vehicle data
|
- [ ] Vehicle data
|
||||||
- [ ] Players synchronizations
|
|
||||||
- [ ] UDP Server part:
|
- [ ] UDP Server part:
|
||||||
- [ ] Players synchronizations
|
- [ ] Players synchronizations _(Code: Zp)_
|
||||||
- [ ] Ping
|
- [ ] Ping _(Code: p)_
|
||||||
- [ ] Player counter
|
|
||||||
- [x] Additional:
|
- [x] Additional:
|
||||||
|
- [x] Logger
|
||||||
|
- [x] Just logging
|
||||||
|
- [x] Log in file
|
||||||
|
- [x] Log history (.1.log, .2.log, ...)
|
||||||
- [x] Console:
|
- [x] Console:
|
||||||
- [x] Tabulation
|
- [x] Tabulation
|
||||||
- [ ] _(Deferred)_ Static text
|
- [ ] _(Deferred)_ Static text (bug)
|
||||||
- [x] Events System
|
- [x] Events System
|
||||||
- [x] Call events
|
- [x] Call events
|
||||||
- [x] Create custom events
|
- [x] Create custom events
|
||||||
- [ ] Return from events
|
- [ ] Return from events
|
||||||
- [x] Plugins support
|
- [x] Plugins support
|
||||||
- [x] Load Python plugins
|
- [x] Load Python plugins
|
||||||
- [ ] Load Lua plugins (Original BEAMP compatibility)
|
- [ ] Load Lua plugins (Original BeamMP compatibility)
|
||||||
- [x] MultiLanguage (i18n support)
|
- [x] MultiLanguage (i18n support)
|
||||||
- [x] Core
|
- [x] Core
|
||||||
- [x] Console
|
- [x] Console
|
||||||
- [x] WebAPI
|
- [x] WebAPI
|
||||||
- [x] HTTP API Server (fastapi)
|
- [x] HTTP API Server (fastapi)
|
||||||
- [x] Stop and Start with core
|
- [x] Stop and Start with core
|
||||||
- [x] Custom logger
|
- [x] Configure FastAPI logger
|
||||||
- [ ] Sync with event system
|
- [ ] Sync with event system
|
||||||
|
- [ ] Add methods...
|
||||||
- [ ] [Documentation](docs/en/readme.md)
|
- [ ] [Documentation](docs/en/readme.md)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"stop": "Сервер остановлен!",
|
"stop": "Сервер остановлен!",
|
||||||
|
|
||||||
"": "Server auth",
|
"": "Server auth",
|
||||||
"auth_need_key": "Нужен BEAMP ключ для запуска!",
|
"auth_need_key": "Нужен BeamMP ключ для запуска!",
|
||||||
"auth_empty_key": "BEAMP ключ пустой!",
|
"auth_empty_key": "BeamMP ключ пустой!",
|
||||||
"auth_cannot_open_browser": "Не получилось открыть браузер: {}",
|
"auth_cannot_open_browser": "Не получилось открыть браузер: {}",
|
||||||
"auth_use_link": "Используй эту ссылку: {}",
|
"auth_use_link": "Используй эту ссылку: {}",
|
||||||
|
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"GUI_no": "Нет",
|
"GUI_no": "Нет",
|
||||||
"GUI_ok": "Окей",
|
"GUI_ok": "Окей",
|
||||||
"GUI_cancel": "Отмена",
|
"GUI_cancel": "Отмена",
|
||||||
"GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
|
"GUI_need_key_message": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
|
||||||
"GUI_enter_key_message": "Пожалуйста введите ключ:",
|
"GUI_enter_key_message": "Пожалуйста введите ключ:",
|
||||||
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",
|
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ Server:
|
|||||||
server_ip: 0.0.0.0
|
server_ip: 0.0.0.0
|
||||||
server_port: 30814
|
server_port: 30814
|
||||||
```
|
```
|
||||||
* Если поставить `private: false` и не установить `key`, то сервер запросит BEAMP ключ, без него не запустится.
|
* Если поставить `private: false` и не установить `key`, то сервер запросит BeamMP ключ, без него не запустится.
|
||||||
* Введя BEAMP ключ сервер появится в списке лаунчера.
|
* Введя BeamMP ключ сервер появится в списке лаунчера.
|
||||||
* Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys)
|
* Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys)
|
||||||
|
|
||||||
|
@ -67,13 +67,13 @@ ev.builtins_hook()
|
|||||||
log.info(i18n.hello)
|
log.info(i18n.hello)
|
||||||
log.info(i18n.config_path.format(config_path))
|
log.info(i18n.config_path.format(config_path))
|
||||||
|
|
||||||
log.debug("Initializing BEAMP Server system...")
|
log.debug("Initializing BeamMP Server system...")
|
||||||
# Key handler..
|
# Key handler..
|
||||||
if not config.Auth['private'] and not config.Auth['key']:
|
if not config.Auth['private'] and not config.Auth['key']:
|
||||||
log.warn(i18n.auth_need_key)
|
log.warn(i18n.auth_need_key)
|
||||||
url = "https://beammp.com/k/keys"
|
url = "https://beammp.com/k/keys"
|
||||||
if shortcuts.yes_no_dialog(
|
if shortcuts.yes_no_dialog(
|
||||||
title='BEAMP Server Key',
|
title='BeamMP Server Key',
|
||||||
text=i18n.GUI_need_key_message,
|
text=i18n.GUI_need_key_message,
|
||||||
yes_text=i18n.GUI_yes,
|
yes_text=i18n.GUI_yes,
|
||||||
no_text=i18n.GUI_no).run():
|
no_text=i18n.GUI_no).run():
|
||||||
@ -84,12 +84,12 @@ if not config.Auth['private'] and not config.Auth['key']:
|
|||||||
log.error(i18n.auth_cannot_open_browser.format(e))
|
log.error(i18n.auth_cannot_open_browser.format(e))
|
||||||
log.info(i18n.auth_use_link.format(url))
|
log.info(i18n.auth_use_link.format(url))
|
||||||
shortcuts.message_dialog(
|
shortcuts.message_dialog(
|
||||||
title='BEAMP Server Key',
|
title='BeamMP Server Key',
|
||||||
text=i18n.GUI_cannot_open_browser.format(url),
|
text=i18n.GUI_cannot_open_browser.format(url),
|
||||||
ok_text=i18n.GUI_ok).run()
|
ok_text=i18n.GUI_ok).run()
|
||||||
|
|
||||||
config.Auth['key'] = shortcuts.input_dialog(
|
config.Auth['key'] = shortcuts.input_dialog(
|
||||||
title='BEAMP Server Key',
|
title='BeamMP Server Key',
|
||||||
text=i18n.GUI_enter_key_message,
|
text=i18n.GUI_enter_key_message,
|
||||||
ok_text=i18n.GUI_ok,
|
ok_text=i18n.GUI_ok,
|
||||||
cancel_text=i18n.GUI_cancel).run()
|
cancel_text=i18n.GUI_cancel).run()
|
||||||
|
@ -23,6 +23,7 @@ class Client:
|
|||||||
def __init__(self, reader, writer, core):
|
def __init__(self, reader, writer, core):
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
self.writer = writer
|
self.writer = writer
|
||||||
|
self.down_rw = (None, None)
|
||||||
self.log = utils.get_logger("client(None:0)")
|
self.log = utils.get_logger("client(None:0)")
|
||||||
self.addr = writer.get_extra_info("sockname")
|
self.addr = writer.get_extra_info("sockname")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
@ -61,7 +62,7 @@ class Client:
|
|||||||
async def tcp_send(self, data):
|
async def tcp_send(self, data):
|
||||||
|
|
||||||
# TNetwork.cpp; Line: 383
|
# TNetwork.cpp; Line: 383
|
||||||
# BEAMP TCP protocol sends a header of 4 bytes, followed by the data.
|
# BeamMP TCP protocol sends a header of 4 bytes, followed by the data.
|
||||||
# [][][][][][]...[]
|
# [][][][][][]...[]
|
||||||
# ^------^^---...-^
|
# ^------^^---...-^
|
||||||
# size data
|
# size data
|
||||||
@ -108,18 +109,31 @@ class Client:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def sync_resources(self):
|
async def sync_resources(self):
|
||||||
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8"))
|
while True:
|
||||||
data = await self.recv()
|
data = await self.recv()
|
||||||
if data.startswith(b"SR"):
|
if data.startswith(b"f"):
|
||||||
await self.tcp_send(b"-") # Cannot handle mods for now.
|
# TODO: SendFile
|
||||||
data = await self.recv()
|
pass
|
||||||
if data == b"Done":
|
elif data.startswith(b"SR"):
|
||||||
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
|
# TODO: Create mods list
|
||||||
await self.last_handle()
|
self.log.debug("Sending Mod Info")
|
||||||
|
mods = []
|
||||||
|
mod_list = b''
|
||||||
|
# * code *
|
||||||
|
if len(mods) == 0:
|
||||||
|
await self.tcp_send(b"-")
|
||||||
|
else:
|
||||||
|
await self.tcp_send(mod_list)
|
||||||
|
data = await self.recv()
|
||||||
|
if data == b"Done":
|
||||||
|
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
|
||||||
|
break
|
||||||
|
|
||||||
async def last_handle(self):
|
async def looper(self):
|
||||||
# self.is_disconnected()
|
# self.is_disconnected()
|
||||||
self.log.debug(f"Alive: {self.alive}")
|
self.log.debug(f"Alive: {self.alive}")
|
||||||
|
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8"))
|
||||||
|
await self.sync_resources()
|
||||||
while self.alive:
|
while self.alive:
|
||||||
data = await self.recv()
|
data = await self.recv()
|
||||||
if data == b"":
|
if data == b"":
|
||||||
@ -147,8 +161,9 @@ class Core:
|
|||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.run = False
|
self.run = False
|
||||||
self.direct = False
|
self.direct = False
|
||||||
self.clients = {}
|
self.clients = []
|
||||||
self.clients_counter = 0
|
self.clients_by_id = {}
|
||||||
|
self.clients_by_nick = {}
|
||||||
self.mods_dir = "./mods"
|
self.mods_dir = "./mods"
|
||||||
self.mods_list = [0, ]
|
self.mods_list = [0, ]
|
||||||
self.server_ip = config.Server["server_ip"]
|
self.server_ip = config.Server["server_ip"]
|
||||||
@ -160,33 +175,42 @@ class Core:
|
|||||||
self.web_stop = None
|
self.web_stop = None
|
||||||
|
|
||||||
self.client_major_version = "2.0"
|
self.client_major_version = "2.0"
|
||||||
self.BEAMP_version = "3.2.0"
|
self.BeamMP_version = "3.2.0"
|
||||||
|
|
||||||
def get_client(self, sock=None, cid=None):
|
def get_client(self, sock=None, cid=None, nick=None):
|
||||||
if cid:
|
if cid:
|
||||||
return self.clients.get(cid)
|
return self.clients_by_id.get(cid)
|
||||||
|
if nick:
|
||||||
|
return self.clients_by_nick.get(nick)
|
||||||
if sock:
|
if sock:
|
||||||
return self.clients.get(sock.getsockname())
|
return self.clients_by_nick.get(sock.getsockname())
|
||||||
|
|
||||||
def insert_client(self, client):
|
def insert_client(self, client):
|
||||||
self.log.debug(f"Inserting client: {client.cid}")
|
self.log.debug(f"Inserting client: {client.cid}")
|
||||||
self.clients.update({client.cid: client, client.nick: client})
|
self.clients_by_nick.update({client.nick: client})
|
||||||
|
self.clients_by_id.update({client.cid: client})
|
||||||
|
self.clients[client.cid] = client
|
||||||
|
|
||||||
def create_client(self, *args, **kwargs):
|
def create_client(self, *args, **kwargs):
|
||||||
client = Client(*args, **kwargs)
|
client = Client(*args, **kwargs)
|
||||||
self.clients_counter += 1
|
cid = 1
|
||||||
client.id = self.clients_counter
|
for client in self.clients:
|
||||||
|
if client.cid == cid:
|
||||||
|
cid += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
client.cid = cid
|
||||||
client._update_logger()
|
client._update_logger()
|
||||||
self.log.debug(f"Create client: {client.cid}; clients_counter: {self.clients_counter}")
|
self.log.debug(f"Create client; client.cid: {client.cid};")
|
||||||
return client
|
return client
|
||||||
|
|
||||||
async def check_alive(self):
|
async def check_alive(self):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
self.log.debug(f"Checking if clients is alive")
|
self.log.debug(f"Checking if clients is alive")
|
||||||
for cl in self.clients.values():
|
for client in self.clients:
|
||||||
d = await cl.is_disconnected()
|
d = client.is_disconnected()
|
||||||
if d:
|
if d:
|
||||||
self.log.debug(f"Client ID: {cl.id} died...")
|
self.log.debug(f"Client ID: {client.cid} died...")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start_web():
|
def start_web():
|
||||||
@ -224,7 +248,7 @@ class Core:
|
|||||||
while self.run:
|
while self.run:
|
||||||
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
|
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
|
||||||
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
|
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
|
||||||
"private": config.Auth['private'], "version": self.BEAMP_version,
|
"private": config.Auth['private'], "version": self.BeamMP_version,
|
||||||
"clientversion": self.client_major_version,
|
"clientversion": self.client_major_version,
|
||||||
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
|
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
|
||||||
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
|
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
|
||||||
@ -337,5 +361,6 @@ class Core:
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self.run = False
|
self.run = False
|
||||||
self.log.info(i18n.stop)
|
self.log.info(i18n.stop)
|
||||||
asyncio.run(self.web_stop())
|
if config.WebAPI["enabled"]:
|
||||||
|
asyncio.run(self.web_stop())
|
||||||
exit(0)
|
exit(0)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from asyncio import StreamWriter, StreamReader
|
from asyncio import StreamWriter, StreamReader
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable
|
from typing import Callable, List, Dict, Tuple
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from .tcp_server import TCPServer
|
from .tcp_server import TCPServer
|
||||||
@ -19,6 +19,7 @@ class Client:
|
|||||||
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
|
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
self.writer = writer
|
self.writer = writer
|
||||||
|
self.down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
|
||||||
self.log = utils.get_logger("client(id: )")
|
self.log = utils.get_logger("client(id: )")
|
||||||
self.addr = writer.get_extra_info("sockname")
|
self.addr = writer.get_extra_info("sockname")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
@ -34,7 +35,7 @@ class Client:
|
|||||||
async def tcp_send(self, data: bytes) -> None: ...
|
async def tcp_send(self, data: bytes) -> None: ...
|
||||||
async def sync_resources(self) -> None: ...
|
async def sync_resources(self) -> None: ...
|
||||||
async def recv(self) -> bytes: ...
|
async def recv(self) -> bytes: ...
|
||||||
async def last_handle(self) -> bytes: ...
|
async def looper(self) -> None: ...
|
||||||
def _update_logger(self) -> None: ...
|
def _update_logger(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +45,9 @@ class Core:
|
|||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.run = False
|
self.run = False
|
||||||
self.direct = False
|
self.direct = False
|
||||||
self.clients = dict()
|
self.clients: List[Client]= []
|
||||||
|
self.clients_by_id: Dict[{int: Client}]= {}
|
||||||
|
self.clients_by_nick: Dict[{str: Client}] = {}
|
||||||
self.clients_counter: int = 0
|
self.clients_counter: int = 0
|
||||||
self.mods_dir: str = "mods"
|
self.mods_dir: str = "mods"
|
||||||
self.mods_list: list = []
|
self.mods_list: list = []
|
||||||
@ -55,7 +58,7 @@ class Core:
|
|||||||
self.web_thread: Thread = None
|
self.web_thread: Thread = None
|
||||||
self.web_stop: Callable = lambda: None
|
self.web_stop: Callable = lambda: None
|
||||||
self.client_major_version = "2.0"
|
self.client_major_version = "2.0"
|
||||||
self.BEAMP_version = "3.2.0"
|
self.BeamMP_version = "3.2.0"
|
||||||
def insert_client(self, client: Client) -> None: ...
|
def insert_client(self, client: Client) -> None: ...
|
||||||
def create_client(self, *args, **kwargs) -> Client: ...
|
def create_client(self, *args, **kwargs) -> Client: ...
|
||||||
async def check_alive(self) -> None: ...
|
async def check_alive(self) -> None: ...
|
||||||
|
@ -25,10 +25,7 @@ class TCPServer:
|
|||||||
self.log.info(f"Identifying new ClientConnection...")
|
self.log.info(f"Identifying new ClientConnection...")
|
||||||
data = await client.recv()
|
data = await client.recv()
|
||||||
self.log.debug(f"recv1 data: {data}")
|
self.log.debug(f"recv1 data: {data}")
|
||||||
if len(data) > 50:
|
if data.decode("utf-8") != f"VC{self.Core.client_major_version}":
|
||||||
await client.kick("Too long data")
|
|
||||||
return False, None
|
|
||||||
if "VC2.0" not in data.decode("utf-8"):
|
|
||||||
await client.kick("Outdated Version.")
|
await client.kick("Outdated Version.")
|
||||||
return False, None
|
return False, None
|
||||||
else:
|
else:
|
||||||
@ -57,8 +54,9 @@ class TCPServer:
|
|||||||
self.log.error(f"Auth error: {e}")
|
self.log.error(f"Auth error: {e}")
|
||||||
await client.kick('Invalid authentication data! Try to connect in 5 minutes.')
|
await client.kick('Invalid authentication data! Try to connect in 5 minutes.')
|
||||||
|
|
||||||
# TODO: Password party
|
for _client in self.Core.clients:
|
||||||
# await client.tcp_send(b"S") # Ask client key (How?)
|
if _client.nick == client.nick and _client.guest == client.guest:
|
||||||
|
await client.kick('Stale Client (replaced by new client)')
|
||||||
|
|
||||||
ev.call_event("on_auth", client)
|
ev.call_event("on_auth", client)
|
||||||
|
|
||||||
@ -70,22 +68,29 @@ class TCPServer:
|
|||||||
|
|
||||||
return True, client
|
return True, client
|
||||||
|
|
||||||
async def handle_download(self, writer):
|
async def set_down_rw(self, reader, writer):
|
||||||
# TODO: HandleDownload
|
try:
|
||||||
self.log.debug(f"Client: \"IP: {0!r}; ID: {0}\" - HandleDownload!")
|
cid = (await reader.read(1)).decode() # FIXME: wtf? 1 byte?
|
||||||
return False
|
self.log.debug(f"Client: \"ID: {cid}\" - HandleDownload!")
|
||||||
|
if not cid.isdigit():
|
||||||
|
return False
|
||||||
|
for _client in self.Core.clients:
|
||||||
|
if _client.cid == cid:
|
||||||
|
_client.down_rw = (reader, writer)
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
return False
|
||||||
|
|
||||||
async def handle_code(self, code, reader, writer):
|
async def handle_code(self, code, reader, writer):
|
||||||
match code:
|
match code:
|
||||||
case "C":
|
case "C":
|
||||||
result, client = await self.auth_client(reader, writer)
|
result, client = await self.auth_client(reader, writer)
|
||||||
if result:
|
if result:
|
||||||
await client.sync_resources()
|
await client.looper()
|
||||||
# await client.kick("Authentication success! Server not ready.")
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
case "D":
|
case "D":
|
||||||
return await self.handle_download(writer)
|
return await self.set_down_rw(reader, writer)
|
||||||
case "P":
|
case "P":
|
||||||
writer.write(b"P")
|
writer.write(b"P")
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
@ -115,7 +120,7 @@ class TCPServer:
|
|||||||
self.log.debug("Starting TCP server.")
|
self.log.debug("Starting TCP server.")
|
||||||
try:
|
try:
|
||||||
server = await asyncio.start_server(self.handle_client, self.host, self.port,
|
server = await asyncio.start_server(self.handle_client, self.host, self.port,
|
||||||
backlog=config.Game["players"] + 1)
|
backlog=int(config.Game["players"] * 1.3))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.log.error(f"Error: {e}")
|
self.log.error(f"Error: {e}")
|
||||||
self.Core.run = False
|
self.Core.run = False
|
||||||
|
@ -20,7 +20,7 @@ class TCPServer:
|
|||||||
self.port = port
|
self.port = port
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ...
|
async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ...
|
||||||
async def handle_download(self, writer: StreamWriter) -> bool: ...
|
async def set_down_rw(self, reader: StreamReader, writer: StreamWriter) -> bool: ...
|
||||||
async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> bool: ...
|
async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> bool: ...
|
||||||
async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ...
|
async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ...
|
||||||
async def start(self) -> None: ...
|
async def start(self) -> None: ...
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.utils.py
|
# File core.utils.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Version 1.0
|
# Version 1.1
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
|
||||||
log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s"
|
log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s"
|
||||||
log_format_access = '[%(asctime)s | %(name)-14s | %(levelname)-5s] %(client_addr)s - "%(request_line)s" %(status_code)s'
|
log_dir = "./logs/"
|
||||||
log_file = "server.log"
|
log_file = log_dir + "server.log"
|
||||||
log_level = logging.INFO
|
log_level = logging.INFO
|
||||||
# Инициализируем логирование
|
# Инициализируем логирование
|
||||||
logging.basicConfig(level=log_level, format=log_format)
|
logging.basicConfig(level=log_level, format=log_format)
|
||||||
# Настройка логирование в файл.
|
# Настройка логирование в файл.
|
||||||
# if os.path.exists(log_file):
|
if not os.path.exists(log_dir):
|
||||||
# os.remove(log_file)
|
os.mkdir(log_dir)
|
||||||
|
if os.path.exists(log_file):
|
||||||
|
mtime = os.path.getmtime(log_file)
|
||||||
|
gz_path = log_dir + datetime.datetime.fromtimestamp(mtime).strftime('%d.%m.%Y') + "-%s.tar.gz"
|
||||||
|
index = 1
|
||||||
|
while True:
|
||||||
|
if not os.path.exists(gz_path % index):
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
with tarfile.open(gz_path % index, "w:gz") as tar:
|
||||||
|
logs_files = [log_file, "./logs/web.log", "./logs/web_access.log"]
|
||||||
|
for file in logs_files:
|
||||||
|
if os.path.exists(file):
|
||||||
|
tar.add(file, os.path.basename(file))
|
||||||
|
os.remove(file)
|
||||||
fh = logging.FileHandler(log_file, encoding='utf-8')
|
fh = logging.FileHandler(log_file, encoding='utf-8')
|
||||||
fh.setFormatter(logging.Formatter(log_format))
|
fh.setFormatter(logging.Formatter(log_format))
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
|
|||||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||||
from prompt_toolkit.completion import NestedCompleter
|
from prompt_toolkit.completion import NestedCompleter
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
|
from prompt_toolkit.patch_stdout import patch_stdout
|
||||||
|
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
|
|
||||||
@ -186,8 +187,12 @@ class Console:
|
|||||||
session = PromptSession(history=FileHistory('./.cmdhistory'))
|
session = PromptSession(history=FileHistory('./.cmdhistory'))
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cmd_in = await session.prompt_async(self.__prompt_in,
|
with patch_stdout():
|
||||||
completer=self.completer, auto_suggest=AutoSuggestFromHistory())
|
cmd_in = await session.prompt_async(
|
||||||
|
self.__prompt_in,
|
||||||
|
completer=self.completer,
|
||||||
|
auto_suggest=AutoSuggestFromHistory()
|
||||||
|
)
|
||||||
cmd_s = cmd_in.split(" ")
|
cmd_s = cmd_in.split(" ")
|
||||||
cmd = cmd_s[0]
|
cmd = cmd_s[0]
|
||||||
if cmd == "":
|
if cmd == "":
|
||||||
@ -210,13 +215,3 @@ class Console:
|
|||||||
def stop(self, *args, **kwargs):
|
def stop(self, *args, **kwargs):
|
||||||
self.__is_run = False
|
self.__is_run = False
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
# if __name__ == '__main__':
|
|
||||||
# c = Console()
|
|
||||||
# c.logger_hook()
|
|
||||||
# c.builtins_hook()
|
|
||||||
# log = logging.getLogger(name="name")
|
|
||||||
# log.info("Starting console")
|
|
||||||
# print("Starting console")
|
|
||||||
# asyncio.run(c.start())
|
|
||||||
|
@ -21,7 +21,7 @@ class EventsSystem:
|
|||||||
self.log.debug(f"register_event({event_name}, {event_func}):")
|
self.log.debug(f"register_event({event_name}, {event_func}):")
|
||||||
if not callable(event_func):
|
if not callable(event_func):
|
||||||
self.log.error(f"Cannot add event '{event_name}'. "
|
self.log.error(f"Cannot add event '{event_name}'. "
|
||||||
f"Use `BEAMP.add_event({event_name}', function)` instead. Skipping it...")
|
f"Use `KuiToi.add_event({event_name}', function)` instead. Skipping it...")
|
||||||
return
|
return
|
||||||
if event_name not in self.__events:
|
if event_name not in self.__events:
|
||||||
self.__events.update({str(event_name): [event_func]})
|
self.__events.update({str(event_name): [event_func]})
|
||||||
|
@ -8,7 +8,7 @@ class KuiToi:
|
|||||||
|
|
||||||
def __init__(self, name=None):
|
def __init__(self, name=None):
|
||||||
if name is None:
|
if name is None:
|
||||||
raise Exception("BEAMP: Name is required")
|
raise Exception("BeamMP: Name is required")
|
||||||
self.log = get_logger(f"PluginsLoader | {name}")
|
self.log = get_logger(f"PluginsLoader | {name}")
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class PluginsLoader:
|
|||||||
plugin.print = print
|
plugin.print = print
|
||||||
file = os.path.join(self.__plugins_dir, file)
|
file = os.path.join(self.__plugins_dir, file)
|
||||||
with open(f'{file}', 'r') as f:
|
with open(f'{file}', 'r') as f:
|
||||||
code = f.read().replace("import BEAMP\n", "")
|
code = f.read().replace("import KuiToi\n", "")
|
||||||
exec(code, plugin.__dict__)
|
exec(code, plugin.__dict__)
|
||||||
plugin.load()
|
plugin.load()
|
||||||
self.__plugins.update({file[:-3]: plugin})
|
self.__plugins.update({file[:-3]: plugin})
|
||||||
|
@ -7,7 +7,6 @@ from fastapi.exceptions import RequestValidationError
|
|||||||
from starlette import status
|
from starlette import status
|
||||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
from uvicorn.config import LOGGING_CONFIG
|
|
||||||
|
|
||||||
import core.utils
|
import core.utils
|
||||||
from . import utils
|
from . import utils
|
||||||
@ -21,30 +20,6 @@ uvserver = None
|
|||||||
data_pool = []
|
data_pool = []
|
||||||
data_run = [True]
|
data_run = [True]
|
||||||
|
|
||||||
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
|
|
||||||
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format_access
|
|
||||||
LOGGING_CONFIG["formatters"].update({
|
|
||||||
"file_default": {
|
|
||||||
"fmt": core.utils.log_format
|
|
||||||
},
|
|
||||||
"file_access": {
|
|
||||||
"fmt": core.utils.log_format_access
|
|
||||||
}
|
|
||||||
})
|
|
||||||
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
|
|
||||||
LOGGING_CONFIG["handlers"].update({
|
|
||||||
"file_default": {
|
|
||||||
"class": "logging.handlers.RotatingFileHandler",
|
|
||||||
"filename": "webserver.log"
|
|
||||||
},
|
|
||||||
"file_access": {
|
|
||||||
"class": "logging.handlers.RotatingFileHandler",
|
|
||||||
"filename": "webserver.log"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
|
|
||||||
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
|
|
||||||
|
|
||||||
|
|
||||||
def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None):
|
def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None):
|
||||||
if 200 >= code <= 300:
|
if 200 >= code <= 300:
|
||||||
@ -78,7 +53,8 @@ async def _method(method, secret_key: str = None):
|
|||||||
|
|
||||||
async def _stop():
|
async def _stop():
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
uvserver.should_exit = True
|
if uvserver is not None:
|
||||||
|
uvserver.should_exit = True
|
||||||
data_run[0] = False
|
data_run[0] = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,10 +2,17 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from uvicorn.server import Server, logger
|
import uvicorn.server as uvs
|
||||||
|
from uvicorn.config import LOGGING_CONFIG
|
||||||
|
|
||||||
from uvicorn.lifespan import on
|
from uvicorn.lifespan import on
|
||||||
|
|
||||||
|
import core.utils
|
||||||
|
|
||||||
|
# logger = core.utils.get_logger("uvicorn")
|
||||||
|
# uvs.logger = logger
|
||||||
|
logger = uvs.logger
|
||||||
|
|
||||||
|
|
||||||
def ev_log_started_message(self, listeners) -> None:
|
def ev_log_started_message(self, listeners) -> None:
|
||||||
cfg = self.config
|
cfg = self.config
|
||||||
@ -42,7 +49,7 @@ async def ev_shutdown(self, sockets=None) -> None:
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown)
|
await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded",len(self.server_state.tasks))
|
logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded", len(self.server_state.tasks))
|
||||||
for t in self.server_state.tasks:
|
for t in self.server_state.tasks:
|
||||||
if sys.version_info < (3, 9):
|
if sys.version_info < (3, 9):
|
||||||
t.cancel()
|
t.cancel()
|
||||||
@ -81,7 +88,40 @@ async def on_shutdown(self) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def hack_fastapi():
|
def hack_fastapi():
|
||||||
Server.shutdown = ev_shutdown
|
uvs.Server.shutdown = ev_shutdown
|
||||||
Server._log_started_message = ev_log_started_message
|
uvs.Server._log_started_message = ev_log_started_message
|
||||||
on.LifespanOn.startup = on_startup
|
on.LifespanOn.startup = on_startup
|
||||||
on.LifespanOn.shutdown = on_shutdown
|
on.LifespanOn.shutdown = on_shutdown
|
||||||
|
|
||||||
|
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
|
||||||
|
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format
|
||||||
|
LOGGING_CONFIG["formatters"].update({
|
||||||
|
"file_default": {
|
||||||
|
"()": "logging.Formatter",
|
||||||
|
"fmt": core.utils.log_format
|
||||||
|
},
|
||||||
|
"file_access": {
|
||||||
|
"()": "logging.Formatter",
|
||||||
|
"fmt": core.utils.log_format
|
||||||
|
}
|
||||||
|
})
|
||||||
|
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
|
||||||
|
LOGGING_CONFIG["handlers"].update({
|
||||||
|
"file_default": {
|
||||||
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
|
"filename": "./logs/web.log",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"formatter": "file_default"
|
||||||
|
},
|
||||||
|
"file_access": {
|
||||||
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
|
"filename": "./logs/web_access.log",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"formatter": "file_access"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
|
||||||
|
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
|
||||||
|
print(LOGGING_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"stop": "Сервер остановлен!",
|
"stop": "Сервер остановлен!",
|
||||||
|
|
||||||
"": "Server auth",
|
"": "Server auth",
|
||||||
"auth_need_key": "Нужен BEAMP ключ для запуска!",
|
"auth_need_key": "Нужен BeamMP ключ для запуска!",
|
||||||
"auth_empty_key": "BEAMP ключ пустой!",
|
"auth_empty_key": "BeamMP ключ пустой!",
|
||||||
"auth_cannot_open_browser": "Не получилось открыть браузер: {}",
|
"auth_cannot_open_browser": "Не получилось открыть браузер: {}",
|
||||||
"auth_use_link": "Используй эту ссылку: {}",
|
"auth_use_link": "Используй эту ссылку: {}",
|
||||||
|
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"GUI_no": "Нет",
|
"GUI_no": "Нет",
|
||||||
"GUI_ok": "Окей",
|
"GUI_ok": "Окей",
|
||||||
"GUI_cancel": "Отмена",
|
"GUI_cancel": "Отмена",
|
||||||
"GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
|
"GUI_need_key_message": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
|
||||||
"GUI_enter_key_message": "Пожалуйста введите ключ:",
|
"GUI_enter_key_message": "Пожалуйста введите ключ:",
|
||||||
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",
|
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user