Compare commits

..

No commits in common. "6e46af4c1305f101397d85f4d65bb1457cf9fbf9" and "3f688df30ba950b877097c30733b0914f8c88e23" have entirely different histories.

16 changed files with 118 additions and 184 deletions

1
.gitignore vendored
View File

@ -137,4 +137,3 @@ dmypy.json
/src/plugins /src/plugins
/test/ /test/
*test.py *test.py
logs/

View File

@ -7,48 +7,43 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs ## TODOs
- [ ] Server core - [ ] Server core
- [x] BeamMP System - [x] BEAMP System
- [x] Private access (Without key, Direct connect) - [x] Private access without key (Direct connect)
- [x] Public access (With key, listing in Launcher) - [x] Server authentication (For public access)
- [X] Player authentication - [X] Player authentication
- [ ] TCP Server part: - [ ] TCP Server part:
- [x] Handle code - [x] Handle code
- [x] Understanding BeamMP header - [x] Understanding beamp 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 _(Code: Zp)_ - [ ] Players synchronizations
- [ ] Ping _(Code: p)_ - [ ] Ping
- [ ] 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 (bug) - [ ] _(Deferred)_ Static text
- [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 BeamMP compatibility) - [ ] Load Lua plugins (Original BEAMP 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] Configure FastAPI logger - [x] Custom logger
- [ ] Sync with event system - [ ] Sync with event system
- [ ] Add methods...
- [ ] [Documentation](docs/en/readme.md) - [ ] [Documentation](docs/en/readme.md)
## Installation ## Installation

View File

@ -7,8 +7,8 @@
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",
"": "Server auth", "": "Server auth",
"auth_need_key": "Нужен BeamMP ключ для запуска!", "auth_need_key": "Нужен BEAMP ключ для запуска!",
"auth_empty_key": "BeamMP ключ пустой!", "auth_empty_key": "BEAMP ключ пустой!",
"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": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?", "GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
"GUI_enter_key_message": "Пожалуйста введите ключ:", "GUI_enter_key_message": "Пожалуйста введите ключ:",
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}", "GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",

View File

@ -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`, то сервер запросит BeamMP ключ, без него не запустится. * Если поставить `private: false` и не установить `key`, то сервер запросит BEAMP ключ, без него не запустится.
* Введя BeamMP ключ сервер появится в списке лаунчера. * Введя BEAMP ключ сервер появится в списке лаунчера.
* Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys) * Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys)

View File

@ -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 BeamMP Server system...") log.debug("Initializing BEAMP 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='BeamMP Server Key', title='BEAMP 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='BeamMP Server Key', title='BEAMP 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='BeamMP Server Key', title='BEAMP 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()

View File

@ -23,7 +23,6 @@ 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()
@ -62,7 +61,7 @@ class Client:
async def tcp_send(self, data): async def tcp_send(self, data):
# TNetwork.cpp; Line: 383 # TNetwork.cpp; Line: 383
# BeamMP TCP protocol sends a header of 4 bytes, followed by the data. # BEAMP TCP protocol sends a header of 4 bytes, followed by the data.
# [][][][][][]...[] # [][][][][][]...[]
# ^------^^---...-^ # ^------^^---...-^
# size data # size data
@ -109,31 +108,18 @@ class Client:
return data return data
async def sync_resources(self): async def sync_resources(self):
while True: await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8"))
data = await self.recv() data = await self.recv()
if data.startswith(b"f"): if data.startswith(b"SR"):
# TODO: SendFile await self.tcp_send(b"-") # Cannot handle mods for now.
pass data = await self.recv()
elif data.startswith(b"SR"): if data == b"Done":
# TODO: Create mods list await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
self.log.debug("Sending Mod Info") await self.last_handle()
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 looper(self): async def last_handle(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"":
@ -161,9 +147,8 @@ 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_by_id = {} self.clients_counter = 0
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"]
@ -175,42 +160,33 @@ class Core:
self.web_stop = None self.web_stop = None
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BeamMP_version = "3.2.0" self.BEAMP_version = "3.2.0"
def get_client(self, sock=None, cid=None, nick=None): def get_client(self, sock=None, cid=None):
if cid: if cid:
return self.clients_by_id.get(cid) return self.clients.get(cid)
if nick:
return self.clients_by_nick.get(nick)
if sock: if sock:
return self.clients_by_nick.get(sock.getsockname()) return self.clients.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_by_nick.update({client.nick: client}) self.clients.update({client.cid: client, 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)
cid = 1 self.clients_counter += 1
for client in self.clients: client.id = self.clients_counter
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: {client.cid};") self.log.debug(f"Create client: {client.cid}; clients_counter: {self.clients_counter}")
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 client in self.clients: for cl in self.clients.values():
d = client.is_disconnected() d = await cl.is_disconnected()
if d: if d:
self.log.debug(f"Client ID: {client.cid} died...") self.log.debug(f"Client ID: {cl.id} died...")
@staticmethod @staticmethod
def start_web(): def start_web():
@ -248,7 +224,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.BeamMP_version, "private": config.Auth['private'], "version": self.BEAMP_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}
@ -361,6 +337,5 @@ class Core:
def stop(self): def stop(self):
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
if config.WebAPI["enabled"]: asyncio.run(self.web_stop())
asyncio.run(self.web_stop())
exit(0) exit(0)

View File

@ -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, List, Dict, Tuple from typing import Callable
from core import utils from core import utils
from .tcp_server import TCPServer from .tcp_server import TCPServer
@ -19,7 +19,6 @@ 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()
@ -35,7 +34,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 looper(self) -> None: ... async def last_handle(self) -> bytes: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...
@ -45,9 +44,7 @@ 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: List[Client]= [] self.clients = dict()
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 = []
@ -58,7 +55,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.BeamMP_version = "3.2.0" self.BEAMP_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: ...

View File

@ -25,7 +25,10 @@ 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 data.decode("utf-8") != f"VC{self.Core.client_major_version}": if len(data) > 50:
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:
@ -54,9 +57,8 @@ 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.')
for _client in self.Core.clients: # TODO: Password party
if _client.nick == client.nick and _client.guest == client.guest: # await client.tcp_send(b"S") # Ask client key (How?)
await client.kick('Stale Client (replaced by new client)')
ev.call_event("on_auth", client) ev.call_event("on_auth", client)
@ -68,29 +70,22 @@ class TCPServer:
return True, client return True, client
async def set_down_rw(self, reader, writer): async def handle_download(self, writer):
try: # TODO: HandleDownload
cid = (await reader.read(1)).decode() # FIXME: wtf? 1 byte? self.log.debug(f"Client: \"IP: {0!r}; ID: {0}\" - HandleDownload!")
self.log.debug(f"Client: \"ID: {cid}\" - HandleDownload!") return False
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.looper() await client.sync_resources()
# await client.kick("Authentication success! Server not ready.")
return True return True
return False return False
case "D": case "D":
return await self.set_down_rw(reader, writer) return await self.handle_download(writer)
case "P": case "P":
writer.write(b"P") writer.write(b"P")
await writer.drain() await writer.drain()
@ -120,7 +115,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=int(config.Game["players"] * 1.3)) backlog=config.Game["players"] + 1)
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

View File

@ -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 set_down_rw(self, reader: StreamReader, writer: StreamWriter) -> bool: ... async def handle_download(self, 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: ...

View File

@ -1,37 +1,21 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.utils.py # File core.utils.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.1 # Version 1.0
# 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_dir = "./logs/" log_format_access = '[%(asctime)s | %(name)-14s | %(levelname)-5s] %(client_addr)s - "%(request_line)s" %(status_code)s'
log_file = log_dir + "server.log" log_file = "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 not os.path.exists(log_dir): # if os.path.exists(log_file):
os.mkdir(log_dir) # os.remove(log_file)
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))

View File

@ -14,7 +14,6 @@ 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
@ -187,12 +186,8 @@ class Console:
session = PromptSession(history=FileHistory('./.cmdhistory')) session = PromptSession(history=FileHistory('./.cmdhistory'))
while True: while True:
try: try:
with patch_stdout(): cmd_in = await session.prompt_async(self.__prompt_in,
cmd_in = await session.prompt_async( completer=self.completer, auto_suggest=AutoSuggestFromHistory())
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 == "":
@ -215,3 +210,13 @@ 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())

View File

@ -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 `KuiToi.add_event({event_name}', function)` instead. Skipping it...") f"Use `BEAMP.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]})

View File

@ -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("BeamMP: Name is required") raise Exception("BEAMP: 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 KuiToi\n", "") code = f.read().replace("import BEAMP\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})

View File

@ -7,6 +7,7 @@ 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
@ -20,6 +21,30 @@ 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:
@ -53,8 +78,7 @@ async def _method(method, secret_key: str = None):
async def _stop(): async def _stop():
await asyncio.sleep(1) await asyncio.sleep(1)
if uvserver is not None: uvserver.should_exit = True
uvserver.should_exit = True
data_run[0] = False data_run[0] = False

View File

@ -2,17 +2,10 @@ import asyncio
import sys import sys
import click import click
import uvicorn.server as uvs from uvicorn.server import Server, logger
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
@ -49,7 +42,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()
@ -88,40 +81,7 @@ async def on_shutdown(self) -> None:
def hack_fastapi(): def hack_fastapi():
uvs.Server.shutdown = ev_shutdown Server.shutdown = ev_shutdown
uvs.Server._log_started_message = ev_log_started_message 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)

View File

@ -7,8 +7,8 @@
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",
"": "Server auth", "": "Server auth",
"auth_need_key": "Нужен BeamMP ключ для запуска!", "auth_need_key": "Нужен BEAMP ключ для запуска!",
"auth_empty_key": "BeamMP ключ пустой!", "auth_empty_key": "BEAMP ключ пустой!",
"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": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?", "GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
"GUI_enter_key_message": "Пожалуйста введите ключ:", "GUI_enter_key_message": "Пожалуйста введите ключ:",
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}", "GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",