Compare commits

..

13 Commits

Author SHA1 Message Date
a9dad5ab8f Add codes to _looper();
Rewrite _tcp_send(to_all=True)
2023-07-15 19:00:21 +03:00
aa5725e8a5 Minor fix 2023-07-15 18:03:22 +03:00
939723acdd Add event player_join 2023-07-15 17:54:59 +03:00
90beaf1302 Refactor class Client to protected funcs and vars 2023-07-15 17:54:08 +03:00
ee366a2d23 Update TODOs 2023-07-15 17:04:12 +03:00
d665021479 i18n: Update en translate 2023-07-15 17:01:58 +03:00
13ff3207b2 Update en docs 2023-07-15 16:32:07 +03:00
50b479c396 Update docs 2023-07-15 16:21:43 +03:00
62fa4c6f25 Update docs 2023-07-15 16:20:37 +03:00
f0f8da962e Update async logic;
New except;
2023-07-15 15:37:55 +03:00
840d8fd685 New except 2023-07-15 15:36:59 +03:00
4629fbc43a Update Async logic 2023-07-15 15:36:45 +03:00
e9919459af Update Async logic 2023-07-15 15:34:24 +03:00
26 changed files with 792 additions and 348 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
- [ ] _(Deferred)_ Static text (bug) - [x] _~~(By design)~~_ 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/en/readme.md) - [ ] [Documentation](./docs/)
## Installation ## Installation

View File

@ -1,15 +1,15 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Hello from KuiToi-Server!", "hello": "Greetings from KuiToi Server!",
"config_path": "Use {} for config.", "config_path": "Use {} to configure.",
"init_ok": "Initializing ready.", "init_ok": "Initialization complete.",
"start": "Server started!", "start": "Server started!",
"stop": "Goodbye!", "stop": "Server stopped!",
"": "Server auth", "": "Server auth",
"auth_need_key": "BEAM key needed for starting the server!", "auth_need_key": "A BeamMP key is required to start the server!",
"auth_empty_key": "Key is empty!", "auth_empty_key": "The BeamMP key is empty!",
"auth_cannot_open_browser": "Cannot open browser: {}", "auth_cannot_open_browser": "Failed to 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": "BEAM key needed for starting the server!\nDo you need to open the web link to obtain the key?", "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_enter_key_message": "Please type your key:", "GUI_enter_key_message": "Please enter the key:",
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
"": "Web phases", "": "Web phases",
"web_start": "WebAPI running on {} (Press CTRL+C to quit)", "web_start": "WebAPI started at {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Display the manual page for COMMAND.", "help_message_man": "Displays help page for COMMAND.",
"man_for": "Manual for command", "man_for": "Help page for",
"man_message_not_found": "man: Manual message not found.", "man_message_not_found": "man: Help page 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 - 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.", "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.",
"help_message_help": "Display names and brief descriptions of available commands", "help_message_help": "Displays the names and short descriptions of commands.",
"help_command": "Command", "help_command": "Command",
"help_message": "Help message", "help_message": "Description",
"help_message_not_found": "No help message found", "help_message_not_found": "No description available.",
"": "Command: stop", "": "Command: stop",
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop", "man_message_stop": "stop - Stops the server.\nUsage: stop",
"help_message_stop": "Server shutdown.", "help_message_stop": "Stops the server.",
"": "Command: exit", "": "Command: exit",
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop", "man_message_exit": "exit - Stops the server.\nUsage: exit",
"help_message_exit": "Server shutdown." "help_message_exit": "Stops the server."
} }

View File

@ -0,0 +1,4 @@
# 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

@ -0,0 +1,37 @@
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,36 +1,37 @@
import KuiToi # Import server object import json
beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin" try:
log = beam.log # Use logger from server 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"
def on_load(): def my_event_handler(event_data):
# When plugin initialization Server uses plugin.load() to load plugin. log.info(f"{event_data}")
# def load(): is really needed
log.info(beam.name)
# Events handlers def load():
# Инициализация плагина
def on_started(): with open(cfg_file, 'w') as f:
# Simple event handler json.dump(config, f)
log.info("Server starting...") cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
# Simple event register def start():
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 any_func(data=None): def unload():
# Custom event handler # Код завершающий все процессы
log.info(f"Data from any_func: {data}") log.info("Плагин выгружен успешно.")
# 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,32 +1,92 @@
# Plugins System # Plugin System
## Install ## Installing the Library with "Stubs"
###### (Lib can't ready to use) ###### (This means that it will not work without a server, but the IDE will suggest the API)
###### (The library is still under development)
* From pip:\ * Using pip:\
`$ pip install KuiToi` `$ pip install KuiToi`
* From source:\ * From source code:\
`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
beam = KuiToi("TestPlugin") kt = KuiToi("Example")
logger = beam.log log = kt.log
def load(): # Plugins load from here def my_event_handler(event_data):
print(beam.name) log.info(f"{event_data}")
def on_started(): def load():
logger.info("Server starting...") # Plugin initialization
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.")
``` ```
* Basic Events: ['on_started', 'on_auth, 'on_stop'] * It is recommended to use `open()` after `load()`. Otherwise, use `kt.load()` - creates a file in the `plugin/<plugin_name>/<filename>` folder.
* Create new event : `beam.register_event("my_event", my_event_function)` * Creating your own event: `kt.register_event("my_event", my_event_function)`
* Call event: `beam.call_event("my_event")` * Calling an event: `kt.call_event("my_event")`
* Call event with some data: `beam.call_event("my_event", data, data2)` * Calling an event with data: `kt.call_event("my_event", data, data2=data2)`
* Calls _**can't support**_ like this: `beam.call_event("my_event", data=data)` * Basic events: _Will write later_
## 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 has not been perfected yet, but one day it will definitely happen ### The documentation is not perfect yet, but it will be one day
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. RESP API - [here](./web) 4. KuiToi WebAPI - [here](./web)
5. Something new 5. Something new...

View File

@ -1,18 +1,32 @@
# Hello from KuiToi Server # Greetings from KuiToi Server
## Start ## Well, let's begin
* Need **Python 3.10.x** to start! ###### _(Here are the commands for Linux)_
* 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 ```
$ python3 main.py # Start server * Clone the repository and navigate to it.
* 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
``` ```
## Setup ## Configuration
* After starting server creating `kuitoi.yaml`; Default: * After starting the server, a `kuitoi.yaml` file will be created.
* By default, it looks like this:
```yaml ```yaml
!!python/object:modules.ConfigProvider.config_provider.Config !!python/object:modules.ConfigProvider.config_provider.Config
Auth: Auth:
@ -23,11 +37,39 @@ Game:
max_cars: 1 max_cars: 1
players: 8 players: 8
Server: Server:
debug: true debug: false
description: This server uses KuiToi! description: Welcome to KuiToi Server!
language: en
name: KuiToi-Server name: KuiToi-Server
server_ip: 0.0.0.0 server_ip: 0.0.0.0
server_port: 30814 server_port: 30813
``` WebAPI:
* Server can't start without BEAM Auth.key enabled: false
secret_key: <random_key>
server_ip: 127.0.0.1
server_port: 8433
```
### Auth
* 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,3 +1,15 @@
# Web RESP API for the Server Here's the translation of the readme.txt content:
In development... # WebAPI for the server
## 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

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

View File

@ -0,0 +1,37 @@
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,36 +1,37 @@
import KuiToi # Import server object import json
beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin" try:
log = beam.log # Use logger from server 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"
def on_load(): def my_event_handler(event_data):
# When plugin initialization Server uses plugin.load() to load plugin. log.info(f"{event_data}")
# def load(): is really needed
log.info(beam.name)
# Events handlers def load():
# Инициализация плагина
def on_started(): with open(cfg_file, 'w') as f:
# Simple event handler json.dump(config, f)
log.info("Server starting...") cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
# Simple event register def start():
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 any_func(data=None): def unload():
# Custom event handler # Код завершающий все процессы
log.info(f"Data from any_func: {data}") log.info("Плагин выгружен успешно.")
# 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,24 +12,81 @@
## Пример ## Пример
```python ```python
try:
import KuiToi import KuiToi
except ImportError:
pass
beam = KuiToi("TestPlugin") kt = KuiToi("Example")
logger = beam.log log = kt.log
def load(): # Plugins load from here def my_event_handler(event_data):
print(beam.name) log.info(f"{event_data}")
def on_started(): def load():
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("Плагин выгружен успешно.")
``` ```
Так же более обширный пример можно найти в [example.py](./example.py) * Рекомендуется использовать `open()` после `load()`, иначе стоит использовать `kt.load()` - Создаёт файл в папке `plugin/<plugin_name>/<filename>`
* Создание своего ивента : `kt.register_event("my_event", my_event_function)` -
* Вызов ивента: `kt.call_event("my_event")`
* Вызов ивента с данными: `kt.call_event("my_event", data, data2=data2)`
* Базовые ивенты: _Позже напишу_
* Базовые ивенты: ['on_started', 'on_auth, 'on_stop'] ## Async функции
* Создание своего ивента : `beam.register_event("my_event", my_event_function)`
* Вызов ивента: `beam.call_event("my_event")` Поддержка async есть
* Вызов ивента с данными: `beam.call_event("my_event", data, data2)`
* Вызовы с указанием переменой _**не поддерживаются**_: `beam.call_event("my_event", data=data)` ```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():
# Инициализация плагина
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. RESP API - [тута](./web) 4. KuiToi WebAPI - [тута](./web)
5. Тут будет что-то ново.... 5. Тут будет что-то ново....

View File

@ -38,12 +38,39 @@ Game:
players: 8 players: 8
Server: Server:
debug: false debug: false
description: This server uses KuiToi! description: Welcome to KuiToi Server!
language: en
name: KuiToi-Server name: KuiToi-Server
server_ip: 0.0.0.0 server_ip: 0.0.0.0
server_port: 30814 server_port: 30813
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,3 +1,14 @@
# Web RESP API для сервера # WebAPI для сервера
## Доступные endpoints
* `/stop`:
* Необходимые парамеры:
* `secret_key` - Ключ, который указан в конфигурации сервера
* `/event.get`
* Точка не готова
* Необходимые парамеры:
* `secret_key` - Ключ, который указан в конфигурации сервера
В разработке

View File

@ -8,48 +8,85 @@ 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.log = utils.get_logger("client(None:0)") self.__Core = core
self.addr = writer.get_extra_info("sockname") self.__alive = True
self.loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self.Core = core self._log = utils.get_logger("client(None:0)")
self.cid = -1 self._addr = writer.get_extra_info("sockname")
self.key = None self._cid = -1
self.nick = None self._key = None
self.roles = None self._nick = None
self.guest = True self._roles = None
self.alive = True self._guest = 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, writer=None): async def _tcp_send(self, data, to_all=False, to_self=True, to_udp=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.
@ -58,17 +95,24 @@ 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:
for client in self.Core.clients: code = data[:1]
if not client: for client in self.__Core.clients:
if not client or (client == self and not to_self):
continue continue
await client.tcp_send(data) if not to_udp or code in [b'W', b'Y', b'V', b'E']:
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:
@ -76,11 +120,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)):
@ -89,7 +133,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""
@ -100,7 +144,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:
@ -113,13 +157,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}")
@ -132,20 +176,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:
@ -154,12 +198,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:
@ -177,14 +221,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']};"
@ -192,55 +236,99 @@ 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()
while self.alive: # TODO: GlobalParser
data = await self.recv() while self.__alive:
if data == b"": data = await self._recv()
if not self.alive: if not data:
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
ev.call_event("chat_receive", f"{data}") msg = data[2:].decode()
await self.tcp_send(data, to_all=True) if not msg:
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)
async def remove_me(self): case "O":
# 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...")
if not self.writer.is_closing(): try:
self.writer.close() if not self.__writer.is_closing():
_, down_w = self.down_rw self.__writer.close()
except Exception as e:
self.log.debug(f"Error while closing writer: {e}")
try:
_, down_w = self._down_rw
if down_w and not down_w.is_closing(): if down_w and not down_w.is_closing():
down_w.close() down_w.close()
except Exception as e:
self.log.debug(f"Error while closing download writer: {e}")

View File

@ -8,26 +8,50 @@ 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, writer: StreamWriter = None) -> 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 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,7 +43,11 @@ 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"
def get_client(self, cid=None, nick=None): ev.register_event("get_player", self.get_client)
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:
@ -61,7 +65,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})
@ -81,7 +85,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 += ","
@ -100,7 +104,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():
@ -252,30 +256,33 @@ 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)
ev.call_event("_plugins_start") await ev.call_async_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()
self.run = False await self.stop()
def start(self): def start(self):
asyncio.run(self.main()) asyncio.run(self.main())
def stop(self): async def stop(self):
ev.call_event("server_stopped") ev.call_event("server_stopped")
ev.call_event("_plugins_unload") await ev.call_async_event("server_stopped")
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: ...
def stop(self) -> None: ... async 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,7 +152,9 @@ class TCPServer:
# TODO: i18n # TODO: i18n
self.log.error("Cannot bind port") self.log.error("Cannot bind port")
raise e raise e
except BaseException as e: except KeyboardInterrupt:
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,17 +14,13 @@ 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:
core.run = False pass
core.stop()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -13,51 +13,80 @@ 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": [], "server_started": [],
"_plugins_start": [], "auth_sent_key": [], # Only sync
"auth_sent_key": [], "auth_ok": [], # Only sync
"auth_ok": [], "player_join": [],
"chat_receive": [], "chat_receive": [],
"_plugins_unload": [],
"server_stopped": [], "server_stopped": [],
} }
self.__async_events = {
"server_started": [],
"_plugins_start": [],
"_plugins_unload": [],
"player_join": [],
"chat_receive": [],
"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): def register_event(self, event_name, event_func, async_event=False):
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.__async_events:
self.__async_events.update({str(event_name): [event_func]})
else:
self.__async_events[event_name].append(event_func)
else:
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]})
else: else:
self.__events[event_name].append(event_func) self.__events[event_name].append(event_func)
def call_event(self, event_name, *args, **kwargs): async def call_async_event(self, event_name, *args, **kwargs):
self.log.debug(f"Using event '{event_name}'") self.log.debug(f"Calling async event: '{event_name}'")
funcs_data = [] funcs_data = []
if event_name in self.__async_events.keys():
if event_name in self.__events.keys(): for func in self.__async_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}
if inspect.iscoroutinefunction(func): data = await func(event_data)
d = self.loop.run_until_complete(func(event_data)) funcs_data.append(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. Just skipping it...") 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):
self.log.debug(f"Calling sync event: '{event_name}'")
funcs_data = []
if event_name in self.__events.keys():
for func in self.__events[event_name]:
try:
event_data = {"event_name": event_name, "args": args, "kwargs": kwargs}
funcs_data.append(func(event_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_async_event()?. Just skipping it...")
return funcs_data return funcs_data

View File

@ -1,6 +1,11 @@
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
def call_event(event_name, *data, **kwargs): ... async def call_async_event(event_name, *args, **kwargs) -> list[Any]: ...
@staticmethod
def call_event(event_name, *data, **kwargs) -> list[Any]: ...
class ev(EventsSystem): ... class ev(EventsSystem): ...

View File

@ -1,15 +1,15 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Hello from KuiToi-Server!", "hello": "Greetings from KuiToi Server!",
"config_path": "Use {} for config.", "config_path": "Use {} to configure.",
"init_ok": "Initializing ready.", "init_ok": "Initialization complete.",
"start": "Server started!", "start": "Server started!",
"stop": "Goodbye!", "stop": "Server stopped!",
"": "Server auth", "": "Server auth",
"auth_need_key": "BEAM key needed for starting the server!", "auth_need_key": "A BeamMP key is required to start the server!",
"auth_empty_key": "Key is empty!", "auth_empty_key": "The BeamMP key is empty!",
"auth_cannot_open_browser": "Cannot open browser: {}", "auth_cannot_open_browser": "Failed to 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": "BEAM key needed for starting the server!\nDo you need to open the web link to obtain the key?", "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_enter_key_message": "Please type your key:", "GUI_enter_key_message": "Please enter the key:",
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
"": "Web phases", "": "Web phases",
"web_start": "WebAPI running on {} (Press CTRL+C to quit)", "web_start": "WebAPI started at {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Display the manual page for COMMAND.", "help_message_man": "Displays help page for COMMAND.",
"man_for": "Manual for command", "man_for": "Help page for",
"man_message_not_found": "man: Manual message not found.", "man_message_not_found": "man: Help page 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 - 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.", "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.",
"help_message_help": "Display names and brief descriptions of available commands", "help_message_help": "Displays the names and short descriptions of commands.",
"help_command": "Command", "help_command": "Command",
"help_message": "Help message", "help_message": "Description",
"help_message_not_found": "No help message found", "help_message_not_found": "No description available.",
"": "Command: stop", "": "Command: stop",
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop", "man_message_stop": "stop - Stops the server.\nUsage: stop",
"help_message_stop": "Server shutdown.", "help_message_stop": "Stops the server.",
"": "Command: exit", "": "Command: exit",
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop", "man_message_exit": "exit - Stops the server.\nUsage: exit",
"help_message_exit": "Server shutdown." "help_message_exit": "Stops the server."
} }

View File

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