Compare commits

..

No commits in common. "a9dad5ab8fce2ef2dc32db29af7921e89ad764b6" and "5f8b70a2ee14e97be5b53a7c802a0458105bf72b" have entirely different histories.

26 changed files with 343 additions and 787 deletions

View File

@ -36,7 +36,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Log history (.1.log, .2.log, ...) - [x] Log history (.1.log, .2.log, ...)
- [x] Console: - [x] Console:
- [x] Tabulation - [x] Tabulation
- [x] _~~(By design)~~_ Static text (bug) - [ ] _(Deferred)_ Static text (bug)
- [x] Events System - [x] Events System
- [x] Call events - [x] Call events
- [x] Create custom events - [x] Create custom events
@ -56,7 +56,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Configure FastAPI logger - [x] Configure FastAPI logger
- [ ] Sync with event system - [ ] Sync with event system
- [ ] Add methods... - [ ] Add methods...
- [ ] [Documentation](./docs/) - [ ] [Documentation](docs/en/readme.md)
## Installation ## Installation

View File

@ -1,15 +1,15 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Greetings from KuiToi Server!", "hello": "Hello from KuiToi-Server!",
"config_path": "Use {} to configure.", "config_path": "Use {} for config.",
"init_ok": "Initialization complete.", "init_ok": "Initializing ready.",
"start": "Server started!", "start": "Server started!",
"stop": "Server stopped!", "stop": "Goodbye!",
"": "Server auth", "": "Server auth",
"auth_need_key": "A BeamMP key is required to start the server!", "auth_need_key": "BEAM key needed for starting the server!",
"auth_empty_key": "The BeamMP key is empty!", "auth_empty_key": "Key is empty!",
"auth_cannot_open_browser": "Failed to open browser: {}", "auth_cannot_open_browser": "Cannot open browser: {}",
"auth_use_link": "Use this link: {}", "auth_use_link": "Use this link: {}",
"": "GUI phases", "": "GUI phases",
@ -17,32 +17,32 @@
"GUI_no": "No", "GUI_no": "No",
"GUI_ok": "Ok", "GUI_ok": "Ok",
"GUI_cancel": "Cancel", "GUI_cancel": "Cancel",
"GUI_need_key_message": "A BeamMP key is required to start the server!\nDo you want to open the link in a browser to obtain the key?", "GUI_need_key_message": "BEAM key needed for starting the server!\nDo you need to open the web link to obtain the key?",
"GUI_enter_key_message": "Please enter the key:", "GUI_enter_key_message": "Please type your key:",
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
"": "Web phases", "": "Web phases",
"web_start": "WebAPI started at {} (Press CTRL+C to quit)", "web_start": "WebAPI running on {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Displays help page for COMMAND.", "help_message_man": "Display the manual page for COMMAND.",
"man_for": "Help page for", "man_for": "Manual for command",
"man_message_not_found": "man: Help page not found.", "man_message_not_found": "man: Manual message not found.",
"man_command_not_found": "man: Command \"{}\" not found!", "man_command_not_found": "man: command \"{}\" not found!",
"": "Command: help", "": "Command: help",
"man_message_help": "help - Displays the names and short descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands and a brief description of each command.", "man_message_help": "help - display names and brief descriptions of available commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands along with a brief description of each command.",
"help_message_help": "Displays the names and short descriptions of commands.", "help_message_help": "Display names and brief descriptions of available commands",
"help_command": "Command", "help_command": "Command",
"help_message": "Description", "help_message": "Help message",
"help_message_not_found": "No description available.", "help_message_not_found": "No help message found",
"": "Command: stop", "": "Command: stop",
"man_message_stop": "stop - Stops the server.\nUsage: stop", "man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
"help_message_stop": "Stops the server.", "help_message_stop": "Server shutdown.",
"": "Command: exit", "": "Command: exit",
"man_message_exit": "exit - Stops the server.\nUsage: exit", "man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
"help_message_exit": "Stops the server." "help_message_exit": "Server shutdown."
} }

View File

@ -1,4 +0,0 @@
# MultiLanguage - i18n Support
In [example.json](./example.json) you will find a copy of [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json).\
If you want to translate to a language that has not been translated before or update an existing translation, I would be happy to receive your pull requests.

View File

@ -1,37 +0,0 @@
import json
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# Инициализация плагина
with open(cfg_file, 'w') as f:
json.dump(config, f)
cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
async def start():
# Запуск процессов плагина
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
async def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")

View File

@ -1,37 +1,36 @@
import json import KuiToi # Import server object
try: beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin"
import KuiToi log = beam.log # Use logger from server
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
def my_event_handler(event_data): def on_load():
log.info(f"{event_data}") # When plugin initialization Server uses plugin.load() to load plugin.
# def load(): is really needed
log.info(beam.name)
def load(): # Events handlers
# Инициализация плагина
with open(cfg_file, 'w') as f: def on_started():
json.dump(config, f) # Simple event handler
cgf = config log.info("Server starting...")
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
def start(): # Simple event register
# Запуск процессов плагина beam.register_event("on_started", on_started)
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
def unload(): def any_func(data=None):
# Код завершающий все процессы # Custom event handler
log.info("Плагин выгружен успешно.") log.info(f"Data from any_func: {data}")
# Create custom event
beam.register_event("my_event", any_func)
# Call custom event
beam.call_event("my_event")
beam.call_event("my_event", "Some data")
# This will be an error since any_func accepts only one argument at the input
beam.call_event("my_event", "Some data", "Some data1")

View File

@ -1,92 +1,32 @@
# Plugin System # Plugins System
## Installing the Library with "Stubs" ## Install
###### (This means that it will not work without a server, but the IDE will suggest the API) ###### (Lib can't ready to use)
###### (The library is still under development)
* Using pip:\ * From pip:\
`$ pip install KuiToi` `$ pip install KuiToi`
* From source code:\ * From source:\
`git clone https://github.com/KuiToi/KuiToi-PyLib` `git clone https://github.com/KuiToi/KuiToi-PyLib`
## Example ## Example
```python ```python
try: import KuiToi
import KuiToi
except ImportError:
pass
kt = KuiToi("Example") beam = KuiToi("TestPlugin")
log = kt.log logger = beam.log
def my_event_handler(event_data): def load(): # Plugins load from here
log.info(f"{event_data}") print(beam.name)
def load(): def on_started():
# Plugin initialization logger.info("Server starting...")
ev.register_event("my_event", my_event_handler)
log.info("Plugin loaded successfully.")
beam.register_event("on_started", on_started)
def start():
# Running plugin processes
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
log.info("Plugin started successfully.")
def unload():
# Code that ends all processes
log.info("Plugin unloaded successfully.")
``` ```
* It is recommended to use `open()` after `load()`. Otherwise, use `kt.load()` - creates a file in the `plugin/<plugin_name>/<filename>` folder. * Basic Events: ['on_started', 'on_auth, 'on_stop']
* Creating your own event: `kt.register_event("my_event", my_event_function)` * Create new event : `beam.register_event("my_event", my_event_function)`
* Calling an event: `kt.call_event("my_event")` * Call event: `beam.call_event("my_event")`
* Calling an event with data: `kt.call_event("my_event", data, data2=data2)` * Call event with some data: `beam.call_event("my_event", data, data2)`
* Basic events: _Will write later_ * Calls _**can't support**_ like this: `beam.call_event("my_event", data=data)`
## Async Functions
Async support is available.
```python
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# Plugin initialization
ev.register_event("my_event", my_event_handler)
log.info("Plugin loaded successfully.")
async def start():
# Running plugin processes
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
log.info("Plugin started successfully.")
async def unload():
# Code that ends all processes
log.info("Plugin unloaded successfully.")
```
A more extensive example can also be found in [async_example.py](./async_example.py).
* Creating your own event: `kt.register_event("my_event", my_event_function)` (register_event checks for function)
* Calling an async event: `kt.call_async_event("my_event")`
* Calling an async event with data: `kt.call_async_event("my_event", data, data2=data2)`
* Basic async events: _Will write later_

View File

@ -1,9 +1,9 @@
# Documentation for KuiToi Server # Documentation for KuiToi Server
### The documentation is not perfect yet, but it will be one day #### The documentation has not been perfected yet, but one day it will definitely happen
1. Setup and Start server - [here](setup) 1. Setup and Start server - [here](setup)
2. Plugins and Events system - [here](plugins) 2. Plugins and Events system - [here](plugins)
3. MultiLanguage - [here](./multilanguage) 3. MultiLanguage - [here](./multilanguage)
4. KuiToi WebAPI - [here](./web) 4. RESP API - [here](./web)
5. Something new... 5. Something new

View File

@ -1,32 +1,18 @@
# Greetings from KuiToi Server # Hello from KuiToi Server
## Well, let's begin ## Start
###### _(Here are the commands for Linux)_ * Need **Python 3.10.x** to start!
* After cloning use this:
* **Python 3.10.x** is required to run the server! It won't work on Python 3.11...
* You can check the version of your Python installation with the following command:
```bash ```bash
python3 --version # Python 3.10.6 $ python3 --version # Python 3.10.6
``` $ python3 main.py --help # Show help message
* Clone the repository and navigate to it. $ python3 main.py # Start server
* Install everything that's needed.
* Then, using my "script", remove all unnecessary files and move to the core source code.
```bash
git clone -b Stable https://github.com/kuitoi/KuiToi-Server.git && cd KuiToi-Server
pip install -r requirements.txt
mv ./src/ $HOME/ktsrc/ && rm -rf ./* && mv $HOME/ktsrc/* . && rm -rf $HOME/ktsrc
```
* Here's how to view information about the server and start it:
```bash
python3 main.py --help # Displays all available commands
python3 main.py # Starts the server
``` ```
## Configuration ## Setup
* After starting the server, a `kuitoi.yaml` file will be created. * After starting server creating `kuitoi.yaml`; Default:
* By default, it looks like this:
```yaml ```yaml
!!python/object:modules.ConfigProvider.config_provider.Config !!python/object:modules.ConfigProvider.config_provider.Config
Auth: Auth:
@ -37,39 +23,11 @@ Game:
max_cars: 1 max_cars: 1
players: 8 players: 8
Server: Server:
debug: false debug: true
description: Welcome to KuiToi Server! description: This server uses KuiToi!
language: en
name: KuiToi-Server name: KuiToi-Server
server_ip: 0.0.0.0 server_ip: 0.0.0.0
server_port: 30813 server_port: 30814
WebAPI:
enabled: false
secret_key: <random_key>
server_ip: 127.0.0.1
server_port: 8433
``` ```
### Auth * Server can't start without BEAM Auth.key
* If you set `private: false` and do not set a `key`, the server will request a BeamMP key and will not start without it.
* By entering a BeamMP key, the server will appear in the launcher list.
* You can get a key here: [https://beammp.com/k/keys ↗](https://beammp.com/k/keys)
### Game
* `map` specifies only the name of the map. That is, open the mod with the map in `map.zip/levels` - the name of the map will be there, and that's what you need to insert.
* `max_cars` - the maximum number of cars per player
* `players` - the maximum number of players
### Server
* `debug` - should debug messages be displayed (for experienced users only; slightly affects performance)
* `description` - server description for the BeamMP launcher
* `language` - the language in which the server will run (currently available: en, ru)
* `name` - server name for the BeamMP launcher
* `server_ip` - the IP address to be used by the server (for experienced users only; defaults to 0.0.0.0)
* `server_port` - the port on which the server will run
### WebAPI
##### _Docs are not ready yet_

View File

@ -1,15 +1,3 @@
Here's the translation of the readme.txt content: # Web RESP API for the Server
# WebAPI for the server In development...
## Available endpoints
* `/stop`:
* Required parameters:
* `secret_key` - The key specified in the server configuration
* `/event.get`
* The endpoint is not yet ready
* Required parameters:
* `secret_key` - The key specified in the server configuration

View File

@ -1,4 +0,0 @@
# MultiLanguage - Поддержка i18n
В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json)\
Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам.

View File

@ -1,37 +0,0 @@
import json
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# Инициализация плагина
with open(cfg_file, 'w') as f:
json.dump(config, f)
cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
async def start():
# Запуск процессов плагина
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
async def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")

View File

@ -1,37 +1,36 @@
import json import KuiToi # Import server object
try: beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin"
import KuiToi log = beam.log # Use logger from server
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
def my_event_handler(event_data): def on_load():
log.info(f"{event_data}") # When plugin initialization Server uses plugin.load() to load plugin.
# def load(): is really needed
log.info(beam.name)
def load(): # Events handlers
# Инициализация плагина
with open(cfg_file, 'w') as f: def on_started():
json.dump(config, f) # Simple event handler
cgf = config log.info("Server starting...")
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
def start(): # Simple event register
# Запуск процессов плагина beam.register_event("on_started", on_started)
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
def unload(): def any_func(data=None):
# Код завершающий все процессы # Custom event handler
log.info("Плагин выгружен успешно.") log.info(f"Data from any_func: {data}")
# Create custom event
beam.register_event("my_event", any_func)
# Call custom event
beam.call_event("my_event")
beam.call_event("my_event", "Some data")
# This will be an error since any_func accepts only one argument at the input
beam.call_event("my_event", "Some data", "Some data1")

View File

@ -12,81 +12,24 @@
## Пример ## Пример
```python ```python
try: import KuiToi
import KuiToi
except ImportError:
pass
kt = KuiToi("Example") beam = KuiToi("TestPlugin")
log = kt.log logger = beam.log
def my_event_handler(event_data): def load(): # Plugins load from here
log.info(f"{event_data}") print(beam.name)
def load(): def on_started():
# Инициализация плагина logger.info("Server starting...")
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
beam.register_event("on_started", on_started)
def start():
# Запуск процессов плагина
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")
``` ```
* Рекомендуется использовать `open()` после `load()`, иначе стоит использовать `kt.load()` - Создаёт файл в папке `plugin/<plugin_name>/<filename>` Так же более обширный пример можно найти в [example.py](./example.py)
* Создание своего ивента : `kt.register_event("my_event", my_event_function)` -
* Вызов ивента: `kt.call_event("my_event")`
* Вызов ивента с данными: `kt.call_event("my_event", data, data2=data2)`
* Базовые ивенты: _Позже напишу_
## Async функции * Базовые ивенты: ['on_started', 'on_auth, 'on_stop']
* Создание своего ивента : `beam.register_event("my_event", my_event_function)`
Поддержка async есть * Вызов ивента: `beam.call_event("my_event")`
* Вызов ивента с данными: `beam.call_event("my_event", data, data2)`
```python * Вызовы с указанием переменой _**не поддерживаются**_: `beam.call_event("my_event", data=data)`
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# Инициализация плагина
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
async def start():
# Запуск процессов плагина
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
async def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")
```
Так же более обширный пример можно найти в [async_example.py](./async_example.py)
* Создание своего ивента: `kt.register_event("my_event", my_event_function)` (в register_event стоит проверка на функцию)
* Вызов async ивента: `kt.call_async_event("my_event")`
* Вызов async ивента: `kt.call_async_event("my_event", data, data2=data2)`
* Базовые async ивенты: _Позже напишу_

View File

@ -5,5 +5,5 @@
1. Настройка и Запуск сервера - [тута](./setup) 1. Настройка и Запуск сервера - [тута](./setup)
2. Плагины и Ивент система - [тута](./plugins) 2. Плагины и Ивент система - [тута](./plugins)
3. Мультиязычность - [тута](./multilanguage) 3. Мультиязычность - [тута](./multilanguage)
4. KuiToi WebAPI - [тута](./web) 4. RESP API - [тута](./web)
5. Тут будет что-то ново.... 5. Тут будет что-то ново....

View File

@ -38,39 +38,12 @@ Game:
players: 8 players: 8
Server: Server:
debug: false debug: false
description: Welcome to KuiToi Server! description: This server uses KuiToi!
language: en
name: KuiToi-Server name: KuiToi-Server
server_ip: 0.0.0.0 server_ip: 0.0.0.0
server_port: 30813 server_port: 30814
WebAPI:
enabled: false
secret_key: <random_key>
server_ip: 127.0.0.1
server_port: 8433
``` ```
### Auth
* Если поставить `private: false` и не установить `key`, то сервер запросит BeamMP ключ, без него не запустится. * Если поставить `private: false` и не установить `key`, то сервер запросит BeamMP ключ, без него не запустится.
* Введя BeamMP ключ сервер появится в списке лаунчера. * Введя BeamMP ключ сервер появится в списке лаунчера.
* Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys) * Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys)
### Game
* `map` указывается только название карты, т.е. открываем мод с картой в `map.zip/levels` - вот тут будет название карты, его мы и вставляем
* `max_cars` - Максимальное количество машин на игрока
* `players` - Максимально количество игроков
### Server
* `debug` - Нужно ли выводить debug сообщения (только для опытных пользователей, немного теряется в производительности)
* `description` - Описания сервера для лаунчера BeamMP
* `language` - С каким языком запустится сервер (Доступные на данный момент: en, ru)
* `name` - Названия сервер для лаунчера BeamMP
* `server_ip` - Какой IP адрес занять серверу (только для опытных пользователей, по умолчанию 0.0.0.0)
* `server_port` - На каком порту будет работать сервер
### WebAPI
##### _Доки не готовы_

View File

@ -1,14 +1,3 @@
# WebAPI для сервера # Web RESP API для сервера
## Доступные endpoints
* `/stop`:
* Необходимые парамеры:
* `secret_key` - Ключ, который указан в конфигурации сервера
* `/event.get`
* Точка не готова
* Необходимые парамеры:
* `secret_key` - Ключ, который указан в конфигурации сервера
В разработке

View File

@ -8,85 +8,48 @@ from core import utils
class Client: 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.down_rw = (None, None)
self.__Core = core self.log = utils.get_logger("client(None:0)")
self.__alive = True self.addr = writer.get_extra_info("sockname")
self._loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self._log = utils.get_logger("client(None:0)") self.Core = core
self._addr = writer.get_extra_info("sockname") self.cid = -1
self._cid = -1 self.key = None
self._key = None self.nick = None
self._nick = None self.roles = None
self._roles = None self.guest = True
self._guest = True self.alive = True
self._ready = False self.ready = False
self._cars = []
@property
def log(self):
return self._log
@property
def addr(self):
return self._addr
@property
def cid(self):
return self._cid
@property
def key(self):
return self._key
@property
def nick(self):
return self._nick
@property
def roles(self):
return self._roles
@property
def guest(self):
return self._guest
@property
def ready(self):
return self._ready
@property
def cars(self):
return self.cars
def _update_logger(self): def _update_logger(self):
self._log = utils.get_logger(f"{self.nick}:{self.cid}") self.log = utils.get_logger(f"{self.nick}:{self.cid})")
self.log.debug(f"Update logger") self.log.debug(f"Update logger")
def is_disconnected(self): def is_disconnected(self):
if not self.__alive: if not self.alive:
return True return True
res = self.__writer.is_closing() res = self.writer.is_closing()
if res: if res:
self.log.debug(f"Disconnected.") self.log.debug(f"Disconnected.")
self.__alive = False self.alive = False
return True return True
else: else:
self.log.debug(f"Alive.") self.log.debug(f"Alive.")
self.__alive = True self.alive = True
return False return False
async def kick(self, reason): async def kick(self, reason):
if not self.__alive: if not self.alive:
self.log.debug(f"Kick({reason}) skipped;") self.log.debug(f"Kick({reason}) skipped;")
return return
# TODO: i18n # TODO: i18n
self.log.info(f"Kicked with reason: \"{reason}\"") self.log.info(f"Kicked with reason: \"{reason}\"")
await self._tcp_send(b"K" + bytes(reason, "utf-8")) await self.tcp_send(b"K" + bytes(reason, "utf-8"))
self.__alive = False self.alive = False
async def _tcp_send(self, data, to_all=False, to_self=True, to_udp=False, writer=None): async def tcp_send(self, data, to_all=False, writer=None):
# TNetwork.cpp; Line: 383 # TNetwork.cpp; Line: 383
# BeamMP 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.
@ -95,24 +58,17 @@ class Client:
# size data # size data
if writer is None: if writer is None:
writer = self.__writer writer = self.writer
if to_all: if to_all:
code = data[:1] for client in self.Core.clients:
for client in self.__Core.clients: if not client:
if not client or (client == self and not to_self):
continue continue
if not to_udp or code in [b'W', b'Y', b'V', b'E']: await client.tcp_send(data)
if code in [b'O', b'T'] or len(data) > 1000:
# TODO: Compress data
await client._tcp_send(data)
else:
await client._tcp_send(data)
else:
# TODO: UDP send
pass
return return
if len(data) == 10:
data += b"."
header = len(data).to_bytes(4, "little", signed=True) header = len(data).to_bytes(4, "little", signed=True)
self.log.debug(f'len: {len(data)}; send: {header + data!r}') self.log.debug(f'len: {len(data)}; send: {header + data!r}')
try: try:
@ -120,11 +76,11 @@ class Client:
await writer.drain() await writer.drain()
except ConnectionError: except ConnectionError:
self.log.debug('tcp_send: Disconnected') self.log.debug('tcp_send: Disconnected')
self.__alive = False self.alive = False
async def _recv(self): async def recv(self):
try: try:
header = await self.__reader.read(4) header = await self.reader.read(4)
int_header = 0 int_header = 0
for i in range(len(header)): for i in range(len(header)):
@ -133,7 +89,7 @@ class Client:
if int_header <= 0: if int_header <= 0:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
self.is_disconnected() self.is_disconnected()
if self.__alive: if self.alive:
self.log.debug(f"Header: {header}") self.log.debug(f"Header: {header}")
await self.kick("Invalid packet - header negative") await self.kick("Invalid packet - header negative")
return b"" return b""
@ -144,7 +100,7 @@ class Client:
f"assuming malicious intent and disconnecting the client.") f"assuming malicious intent and disconnecting the client.")
return b"" return b""
data = await self.__reader.read(100 * MB) data = await self.reader.read(100 * MB)
self.log.debug(f"header: `{header}`; int_header: `{int_header}`; data: `{data}`;") self.log.debug(f"header: `{header}`; int_header: `{int_header}`; data: `{data}`;")
if len(data) != int_header: if len(data) != int_header:
@ -157,13 +113,13 @@ class Client:
return data return data
return data return data
except ConnectionError: except ConnectionError:
self.__alive = False self.alive = False
return b"" return b""
# TODO: Speed limiter
async def _split_load(self, start, end, d_sock, filename): async def _split_load(self, start, end, d_sock, filename):
# TODO: Speed limiter
real_size = end - start real_size = end - start
writer = self._down_rw[1] if d_sock else self.__writer writer = self.down_rw[1] if d_sock else self.writer
who = 'dwn' if d_sock else 'srv' who = 'dwn' if d_sock else 'srv'
if config.Server["debug"]: if config.Server["debug"]:
self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}") self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}")
@ -176,20 +132,20 @@ class Client:
await writer.drain() await writer.drain()
self.log.debug(f"[{who}] File sent.") self.log.debug(f"[{who}] File sent.")
except ConnectionError: except ConnectionError:
self.__alive = False self.alive = False
self.log.debug(f"[{who}] Disconnected.") self.log.debug(f"[{who}] Disconnected.")
return real_size return real_size
async def _sync_resources(self): async def sync_resources(self):
while self.__alive: while self.alive:
data = await self._recv() data = await self.recv()
self.log.debug(f"data: {data!r}") self.log.debug(f"data: {data!r}")
if data.startswith(b"f"): if data.startswith(b"f"):
file = data[1:].decode("utf-8") file = data[1:].decode("utf-8")
# TODO: i18n # TODO: i18n
self.log.info(f"Requested mode: {file!r}") self.log.info(f"Requested mode: {file!r}")
size = -1 size = -1
for mod in self.__Core.mods_list: for mod in self.Core.mods_list:
if type(mod) == int: if type(mod) == int:
continue continue
if mod.get('path') == file: if mod.get('path') == file:
@ -198,12 +154,12 @@ class Client:
break break
self.log.debug(f"Mode size: {size}") self.log.debug(f"Mode size: {size}")
if size == -1: if size == -1:
await self._tcp_send(b"CO") await self.tcp_send(b"CO")
await self.kick(f"Not allowed mod: " + file) await self.kick(f"Not allowed mod: " + file)
return return
await self._tcp_send(b"AG") await self.tcp_send(b"AG")
t = 0 t = 0
while not self._down_rw[0]: while not self.down_rw[0]:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
t += 1 t += 1
if t > 50: if t > 50:
@ -221,14 +177,14 @@ class Client:
lost = size - sent lost = size - sent
self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}") self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}")
if not ok: if not ok:
self.__alive = False self.alive = False
# TODO: i18n # TODO: i18n
self.log.error(f"Error while sending.") self.log.error(f"Error while sending.")
return return
elif data.startswith(b"SR"): elif data.startswith(b"SR"):
path_list = '' path_list = ''
size_list = '' size_list = ''
for mod in self.__Core.mods_list: for mod in self.Core.mods_list:
if type(mod) == int: if type(mod) == int:
continue continue
path_list += f"{mod['path']};" path_list += f"{mod['path']};"
@ -236,99 +192,55 @@ class Client:
mod_list = path_list + size_list mod_list = path_list + size_list
self.log.debug(f"Mods List: {mod_list}") self.log.debug(f"Mods List: {mod_list}")
if len(mod_list) == 0: if len(mod_list) == 0:
await self._tcp_send(b"-") await self.tcp_send(b"-")
else: else:
await self._tcp_send(bytes(mod_list, "utf-8")) await self.tcp_send(bytes(mod_list, "utf-8"))
elif data == b"Done": elif data == b"Done":
await self._tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
break break
return return
async def _looper(self): async def looper(self):
await self._tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID
await self._sync_resources() await self.sync_resources()
# TODO: GlobalParser while self.alive:
while self.__alive: data = await self.recv()
data = await self._recv() if data == b"":
if not data: if not self.alive:
self.__alive = False break
break else:
await asyncio.sleep(.1)
if 89 >= data[0] >= 86: self.is_disconnected()
# TODO: Network.SendToAll continue
pass
code = data.decode()[0] code = data.decode()[0]
self.log.debug(f"Received code: {code}, data: {data}") self.log.debug(f"Received code: {code}, data: {data}")
match code: match code:
case "H": case "H":
# Client connected # Client connected
self._ready = True self.ready = True
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8"), to_all=True)
ev.call_event("player_join", self)
await ev.call_async_event("player_join", self)
bnick = bytes(self.nick, "utf-8")
await self._tcp_send(b"Sn" + bnick, to_all=True) # I don't know for what it
await self._tcp_send(b"JWelcome" + bnick + b"!", to_all=True) # Hello message
# TODO: Sync cars
# for client in self.__Core.clients:
# for car in client.cars:
# await self._tcp_send(car)
case "C": case "C":
# Chat # Chat
msg = data[2:].decode() ev.call_event("chat_receive", f"{data}")
if not msg: await self.tcp_send(data, to_all=True)
self.log.debug("Tried to send an empty event, ignoring")
continue
self.log.info(f"Received message: {msg}")
# TODO: Handle chat event
ev_data = ev.call_event("chat_receive", msg)
d2 = await ev.call_async_event("chat_receive", msg)
ev_data.extend(d2)
self.log.info(f"TODO: Handle chat event; {ev_data}")
await self._tcp_send(data, to_all=True)
case "O": async def remove_me(self):
# TODO: ParseVehicle
pass
case "E":
# TODO: HandleEvent
pass
case "N":
# TODO: N
pass
case _:
pass
async def _remove_me(self):
await asyncio.sleep(0.3) await asyncio.sleep(0.3)
self.__alive = False self.alive = False
if (self.cid > 0 or self.nick is not None) and \ if (self.cid > 0 or self.nick is not None) and \
self.__Core.clients_by_nick.get(self.nick): self.Core.clients_by_nick.get(self.nick):
# if self.ready: # if self.ready:
# await self.tcp_send(b"", to_all=True) # I'm disconnected. # await self.tcp_send(b"", to_all=True) # I'm disconnected.
self.log.debug(f"Removing client {self.nick}:{self.cid}") self.log.debug(f"Removing client {self.nick}:{self.cid}")
# TODO: i18n # TODO: i18n
self.log.info("Disconnected") self.log.info("Disconnected")
self.__Core.clients[self.cid] = None self.Core.clients[self.cid] = None
self.__Core.clients_by_id.pop(self.cid) self.Core.clients_by_id.pop(self.cid)
self.__Core.clients_by_nick.pop(self.nick) self.Core.clients_by_nick.pop(self.nick)
else: else:
self.log.debug(f"Removing client; Closing connection...") self.log.debug(f"Removing client; Closing connection...")
try: if not self.writer.is_closing():
if not self.__writer.is_closing(): self.writer.close()
self.__writer.close() _, down_w = self.down_rw
except Exception as e: if down_w and not down_w.is_closing():
self.log.debug(f"Error while closing writer: {e}") down_w.close()
try:
_, down_w = self._down_rw
if down_w and not down_w.is_closing():
down_w.close()
except Exception as e:
self.log.debug(f"Error while closing download writer: {e}")

View File

@ -8,50 +8,26 @@ from core import Core, utils
class Client: 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.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()
self.__Core = core self.Core = core
self._cid: int = -1 self.cid: int = -1
self._key: str = None self.key: str = None
self._nick: str = None self.nick: str = None
self._roles: str = None self.roles: str = None
self._guest = True self.guest = True
self.__alive = True self.alive = True
self._ready = False self.ready = False
@property
def log(self):
return self._log
@property
def addr(self):
return self._addr
@property
def cid(self):
return self._cid
@property
def key(self):
return self._key
@property
def nick(self):
return self._nick
@property
def roles(self):
return self._roles
@property
def guest(self):
return self._guest
@property
def ready(self):
return self._ready
def is_disconnected(self) -> bool: ... def is_disconnected(self) -> bool: ...
async def kick(self, reason: str) -> None: ... async def kick(self, reason: str) -> None: ...
async def _tcp_send(self, data: bytes, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ... async def tcp_send(self, data: bytes, to_all:bool = False, writer: StreamWriter = None) -> 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 _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ... async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ...
async def _looper(self) -> None: ... async def looper(self) -> None: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...
async def _remove_me(self) -> None: ... async def remove_me(self) -> None: ...

View File

@ -43,11 +43,7 @@ class Core:
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BeamMP_version = "3.2.0" self.BeamMP_version = "3.2.0"
ev.register_event("get_player", self.get_client) def get_client(self, cid=None, nick=None):
def get_client(self, cid=None, nick=None, from_ev=None):
if from_ev is not None:
return self.get_client(*from_ev['args'], **from_ev['kwargs'])
if cid is not None: if cid is not None:
return self.clients_by_id.get(cid) return self.clients_by_id.get(cid)
if nick: if nick:
@ -65,7 +61,7 @@ class Core:
break break
await asyncio.sleep(random.randint(3, 9) * 0.01) await asyncio.sleep(random.randint(3, 9) * 0.01)
if not self.clients[cid]: if not self.clients[cid]:
client._cid = cid client.cid = cid
self.clients_by_nick.update({client.nick: client}) self.clients_by_nick.update({client.nick: client})
self.log.debug(f"Inserting client: {client.nick}:{client.cid}") self.log.debug(f"Inserting client: {client.nick}:{client.cid}")
self.clients_by_id.update({client.cid: client}) self.clients_by_id.update({client.cid: client})
@ -85,7 +81,7 @@ class Core:
for client in self.clients: for client in self.clients:
if not client: if not client:
continue continue
out += f"{client._nick}" out += f"{client.nick}"
if need_cid: if need_cid:
out += f":{client.cid}" out += f":{client.cid}"
out += "," out += ","
@ -104,7 +100,7 @@ class Core:
if not client.ready: if not client.ready:
client.is_disconnected() client.is_disconnected()
continue continue
await client._tcp_send(bytes(ca, "utf-8")) await client.tcp_send(bytes(ca, "utf-8"))
@staticmethod @staticmethod
def start_web(): def start_web():
@ -256,33 +252,30 @@ class Core:
tasks.append(asyncio.create_task(task())) tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
await ev.call_async_event("_plugins_start") ev.call_event("_plugins_start")
self.run = True self.run = True
self.log.info(i18n.start) self.log.info(i18n.start)
ev.call_event("server_started") ev.call_event("server_started")
await ev.call_async_event("server_started")
await t # Wait end. await t # Wait end.
except KeyboardInterrupt:
pass
except Exception as e: except Exception as e:
self.log.error(f"Exception: {e}") self.log.error(f"Exception: {e}")
self.log.exception(e) self.log.exception(e)
except KeyboardInterrupt:
pass
finally: finally:
self.run = False
self.tcp.stop() self.tcp.stop()
# self.udp.stop() # self.udp.stop()
await self.stop() self.run = False
def start(self): def start(self):
asyncio.run(self.main()) asyncio.run(self.main())
async def stop(self): def stop(self):
ev.call_event("server_stopped") ev.call_event("server_stopped")
await ev.call_async_event("server_stopped") ev.call_event("_plugins_unload")
await ev.call_async_event("_plugins_unload")
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:
asyncio.run(self.web_stop()) asyncio.run(self.web_stop())
# exit(0) exit(0)

View File

@ -45,4 +45,4 @@ class Core:
async def heartbeat(self, test=False) -> None: ... async def heartbeat(self, test=False) -> None: ...
async def main(self) -> None: ... async def main(self) -> None: ...
def start(self) -> None: ... def start(self) -> None: ...
async def stop(self) -> None: ... def stop(self) -> None: ...

View File

@ -25,36 +25,36 @@ class TCPServer:
client = self.Core.create_client(reader, writer) client = self.Core.create_client(reader, writer)
# TODO: i18n # TODO: i18n
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"Version: {data}") self.log.debug(f"Version: {data}")
if data.decode("utf-8") != f"VC{self.Core.client_major_version}": if data.decode("utf-8") != f"VC{self.Core.client_major_version}":
# TODO: i18n # TODO: i18n
await client.kick("Outdated Version.") await client.kick("Outdated Version.")
return False, client return False, client
else: else:
await client._tcp_send(b"S") # Accepted client version await client.tcp_send(b"S") # Accepted client version
data = await client._recv() data = await client.recv()
self.log.debug(f"Key: {data}") self.log.debug(f"Key: {data}")
if len(data) > 50: if len(data) > 50:
# TODO: i18n # TODO: i18n
await client.kick("Invalid Key (too long)!") await client.kick("Invalid Key (too long)!")
return False, client return False, client
client._key = data.decode("utf-8") client.key = data.decode("utf-8")
ev.call_event("auth_sent_key", client) ev.call_event("auth_sent_key", client)
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
url = 'https://auth.beammp.com/pkToUser' url = 'https://auth.beammp.com/pkToUser'
async with session.post(url, data={'key': client._key}) as response: async with session.post(url, data={'key': client.key}) as response:
res = await response.json() res = await response.json()
self.log.debug(f"res: {res}") self.log.debug(f"res: {res}")
if res.get("error"): if res.get("error"):
# TODO: i18n # TODO: i18n
await client.kick('Invalid key! Please restart your game.') await client.kick('Invalid key! Please restart your game.')
return False, client return False, client
client._nick = res["username"] client.nick = res["username"]
client._roles = res["roles"] client.roles = res["roles"]
client._guest = res["guest"] client.guest = res["guest"]
# noinspection PyProtectedMember # noinspection PyProtectedMember
client._update_logger() client._update_logger()
except Exception as e: except Exception as e:
@ -89,7 +89,7 @@ class TCPServer:
cid = (await reader.read(1))[0] cid = (await reader.read(1))[0]
client = self.Core.get_client(cid=cid) client = self.Core.get_client(cid=cid)
if client: if client:
client._down_rw = (reader, writer) client.down_rw = (reader, writer)
self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!") self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!")
else: else:
writer.close() writer.close()
@ -102,7 +102,7 @@ class TCPServer:
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.looper()
return result, client return result, client
case "D": case "D":
await self.set_down_rw(reader, writer) await self.set_down_rw(reader, writer)
@ -128,7 +128,7 @@ class TCPServer:
# await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION) # await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
_, cl = await self.handle_code(code, reader, writer) _, cl = await self.handle_code(code, reader, writer)
if cl: if cl:
await cl._remove_me() await cl.remove_me()
del cl del cl
break break
except Exception as e: except Exception as e:
@ -152,9 +152,7 @@ class TCPServer:
# TODO: i18n # TODO: i18n
self.log.error("Cannot bind port") self.log.error("Cannot bind port")
raise e raise e
except KeyboardInterrupt: except BaseException as e:
pass
except Exception as e:
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
raise e raise e
finally: finally:

View File

@ -14,13 +14,17 @@ parser.add_argument('-v', '--version', action="store_true", help='Print version
parser.add_argument('--config', help='Patch to config file.', nargs='?', default=None, type=str) parser.add_argument('--config', help='Patch to config file.', nargs='?', default=None, type=str)
parser.add_argument('--language', help='Setting localisation.', nargs='?', default=None, type=str) parser.add_argument('--language', help='Setting localisation.', nargs='?', default=None, type=str)
run = True
def main(): def main():
from core import Core from core import Core
core = Core()
try: try:
Core().start() core.start()
except KeyboardInterrupt: except KeyboardInterrupt:
pass core.run = False
core.stop()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -13,80 +13,51 @@ class EventsSystem:
# TODO: default events # TODO: default events
self.log = get_logger("EventsSystem") self.log = get_logger("EventsSystem")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.as_tasks = []
self.__events = { self.__events = {
"server_started": [],
"auth_sent_key": [], # Only sync
"auth_ok": [], # Only sync
"player_join": [],
"chat_receive": [],
"server_stopped": [],
}
self.__async_events = {
"server_started": [], "server_started": [],
"_plugins_start": [], "_plugins_start": [],
"_plugins_unload": [], "auth_sent_key": [],
"player_join": [], "auth_ok": [],
"chat_receive": [], "chat_receive": [],
"server_stopped": [] "_plugins_unload": [],
"server_stopped": [],
} }
def builtins_hook(self): def builtins_hook(self):
self.log.debug("used builtins_hook") self.log.debug("used builtins_hook")
builtins.ev = self builtins.ev = self
def register_event(self, event_name, event_func, async_event=False): def register_event(self, event_name, event_func):
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):
# TODO: i18n # TODO: i18n
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 `KuiToi.add_event({event_name}', function)` instead. Skipping it...")
return return
if async_event or inspect.iscoroutinefunction(event_func): if event_name not in self.__events:
if event_name not in self.__async_events: self.__events.update({str(event_name): [event_func]})
self.__async_events.update({str(event_name): [event_func]})
else:
self.__async_events[event_name].append(event_func)
else: else:
if event_name not in self.__events: self.__events[event_name].append(event_func)
self.__events.update({str(event_name): [event_func]})
else:
self.__events[event_name].append(event_func)
async def call_async_event(self, event_name, *args, **kwargs):
self.log.debug(f"Calling async event: '{event_name}'")
funcs_data = []
if event_name in self.__async_events.keys():
for func in self.__async_events[event_name]:
try:
event_data = {"event_name": event_name, "args": args, "kwargs": kwargs}
data = await func(event_data)
funcs_data.append(data)
except Exception as e:
# TODO: i18n
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
self.log.exception(e)
else:
# TODO: i18n
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_event()?. Just skipping it...")
return funcs_data
def call_event(self, event_name, *args, **kwargs): def call_event(self, event_name, *args, **kwargs):
self.log.debug(f"Calling sync event: '{event_name}'") self.log.debug(f"Using event '{event_name}'")
funcs_data = [] funcs_data = []
if event_name in self.__events.keys(): if event_name in self.__events.keys():
for func in self.__events[event_name]: for func in self.__events[event_name]:
try: try:
event_data = {"event_name": event_name, "args": args, "kwargs": kwargs} event_data = {"event_name": event_name, "args": args, "kwargs": kwargs}
funcs_data.append(func(event_data)) if inspect.iscoroutinefunction(func):
d = self.loop.run_until_complete(func(event_data))
else:
d = func(event_data)
funcs_data.append(d)
except Exception as e: except Exception as e:
# TODO: i18n # TODO: i18n
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"') self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
self.log.exception(e) self.log.exception(e)
else: else:
# TODO: i18n # TODO: i18n
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_async_event()?. Just skipping it...") self.log.warning(f"Event {event_name} does not exist. Just skipping it...")
return funcs_data return funcs_data

View File

@ -1,11 +1,6 @@
from typing import Any
class EventsSystem: class EventsSystem:
@staticmethod @staticmethod
def register_event(event_name, event_func): ... def register_event(event_name, event_func): ...
@staticmethod @staticmethod
async def call_async_event(event_name, *args, **kwargs) -> list[Any]: ... def call_event(event_name, *data, **kwargs): ...
@staticmethod class ev(EventsSystem): ...
def call_event(event_name, *data, **kwargs) -> list[Any]: ...
class ev(EventsSystem): ...

View File

@ -1,15 +1,15 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Greetings from KuiToi Server!", "hello": "Hello from KuiToi-Server!",
"config_path": "Use {} to configure.", "config_path": "Use {} for config.",
"init_ok": "Initialization complete.", "init_ok": "Initializing ready.",
"start": "Server started!", "start": "Server started!",
"stop": "Server stopped!", "stop": "Goodbye!",
"": "Server auth", "": "Server auth",
"auth_need_key": "A BeamMP key is required to start the server!", "auth_need_key": "BEAM key needed for starting the server!",
"auth_empty_key": "The BeamMP key is empty!", "auth_empty_key": "Key is empty!",
"auth_cannot_open_browser": "Failed to open browser: {}", "auth_cannot_open_browser": "Cannot open browser: {}",
"auth_use_link": "Use this link: {}", "auth_use_link": "Use this link: {}",
"": "GUI phases", "": "GUI phases",
@ -17,32 +17,32 @@
"GUI_no": "No", "GUI_no": "No",
"GUI_ok": "Ok", "GUI_ok": "Ok",
"GUI_cancel": "Cancel", "GUI_cancel": "Cancel",
"GUI_need_key_message": "A BeamMP key is required to start the server!\nDo you want to open the link in a browser to obtain the key?", "GUI_need_key_message": "BEAM key needed for starting the server!\nDo you need to open the web link to obtain the key?",
"GUI_enter_key_message": "Please enter the key:", "GUI_enter_key_message": "Please type your key:",
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
"": "Web phases", "": "Web phases",
"web_start": "WebAPI started at {} (Press CTRL+C to quit)", "web_start": "WebAPI running on {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Displays help page for COMMAND.", "help_message_man": "Display the manual page for COMMAND.",
"man_for": "Help page for", "man_for": "Manual for command",
"man_message_not_found": "man: Help page not found.", "man_message_not_found": "man: Manual message not found.",
"man_command_not_found": "man: Command \"{}\" not found!", "man_command_not_found": "man: command \"{}\" not found!",
"": "Command: help", "": "Command: help",
"man_message_help": "help - Displays the names and short descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands and a brief description of each command.", "man_message_help": "help - display names and brief descriptions of available commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands along with a brief description of each command.",
"help_message_help": "Displays the names and short descriptions of commands.", "help_message_help": "Display names and brief descriptions of available commands",
"help_command": "Command", "help_command": "Command",
"help_message": "Description", "help_message": "Help message",
"help_message_not_found": "No description available.", "help_message_not_found": "No help message found",
"": "Command: stop", "": "Command: stop",
"man_message_stop": "stop - Stops the server.\nUsage: stop", "man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
"help_message_stop": "Stops the server.", "help_message_stop": "Server shutdown.",
"": "Command: exit", "": "Command: exit",
"man_message_exit": "exit - Stops the server.\nUsage: exit", "man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
"help_message_exit": "Stops the server." "help_message_exit": "Server shutdown."
} }

View File

@ -89,53 +89,53 @@ class MultiLanguage:
else: else:
# noinspection PyDictDuplicateKeys # noinspection PyDictDuplicateKeys
self.__data = { self.__data = {
"": "Basic phases", "": "Basic phases",
"hello": "Greetings from KuiToi Server!", "hello": "Hello from KuiToi-Server!",
"config_path": "Use {} to configure.", "config_path": "Use {} for config.",
"init_ok": "Initialization complete.", "init_ok": "Initializing ready.",
"start": "Server started!", "start": "Server started!",
"stop": "Server stopped!", "stop": "Goodbye!",
"": "Server auth", "": "Server auth",
"auth_need_key": "A BeamMP key is required to start the server!", "auth_need_key": "BEAM key needed for starting the server!",
"auth_empty_key": "The BeamMP key is empty!", "auth_empty_key": "Key is empty!",
"auth_cannot_open_browser": "Failed to open browser: {}", "auth_cannot_open_browser": "Cannot open browser: {}",
"auth_use_link": "Use this link: {}", "auth_use_link": "Use this link: {}",
"": "GUI phases", "": "GUI phases",
"GUI_yes": "Yes", "GUI_yes": "Yes",
"GUI_no": "No", "GUI_no": "No",
"GUI_ok": "Ok", "GUI_ok": "Ok",
"GUI_cancel": "Cancel", "GUI_cancel": "Cancel",
"GUI_need_key_message": "A BeamMP key is required to start the server!\nDo you want to open the link in a browser to obtain the key?", "GUI_need_key_message": "BEAM key needed for starting the server!\nDo you need to open the web link to obtain the key?",
"GUI_enter_key_message": "Please enter the key:", "GUI_enter_key_message": "Please type your key:",
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
"": "Web phases", "": "Web phases",
"web_start": "WebAPI started at {} (Press CTRL+C to quit)", "web_start": "WebAPI running on {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Displays help page for COMMAND.", "help_message_man": "Display the manual page for COMMAND.",
"man_for": "Help page for", "man_for": "Manual for command",
"man_message_not_found": "man: Help page not found.", "man_message_not_found": "man: Manual message not found.",
"man_command_not_found": "man: Command \"{}\" not found!", "man_command_not_found": "man: command \"{}\" not found!",
"": "Command: help", "": "Command: help",
"man_message_help": "help - Displays the names and short descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands and a brief description of each command.", "man_message_help": "help - display names and brief descriptions of available commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands along with a brief description of each command.",
"help_message_help": "Displays the names and short descriptions of commands.", "help_message_help": "Display names and brief descriptions of available commands",
"help_command": "Command", "help_command": "Command",
"help_message": "Description", "help_message": "Help message",
"help_message_not_found": "No description available.", "help_message_not_found": "No help message found",
"": "Command: stop", "": "Command: stop",
"man_message_stop": "stop - Stops the server.\nUsage: stop", "man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
"help_message_stop": "Stops the server.", "help_message_stop": "Server shutdown.",
"": "Command: exit", "": "Command: exit",
"man_message_exit": "exit - Stops the server.\nUsage: exit", "man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
"help_message_exit": "Stops the server." "help_message_exit": "Server shutdown."
} }
self.__i18n = i18n(self.__data) self.__i18n = i18n(self.__data)
def open_file(self): def open_file(self):