mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 16:25:36 +00:00
Compare commits
13 Commits
5f8b70a2ee
...
a9dad5ab8f
Author | SHA1 | Date | |
---|---|---|---|
a9dad5ab8f | |||
aa5725e8a5 | |||
939723acdd | |||
90beaf1302 | |||
ee366a2d23 | |||
d665021479 | |||
13ff3207b2 | |||
50b479c396 | |||
62fa4c6f25 | |||
f0f8da962e | |||
840d8fd685 | |||
4629fbc43a | |||
e9919459af |
@ -36,7 +36,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] Log history (.1.log, .2.log, ...)
|
||||
- [x] Console:
|
||||
- [x] Tabulation
|
||||
- [ ] _(Deferred)_ Static text (bug)
|
||||
- [x] _~~(By design)~~_ Static text (bug)
|
||||
- [x] Events System
|
||||
- [x] Call events
|
||||
- [x] Create custom events
|
||||
@ -56,7 +56,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] Configure FastAPI logger
|
||||
- [ ] Sync with event system
|
||||
- [ ] Add methods...
|
||||
- [ ] [Documentation](docs/en/readme.md)
|
||||
- [ ] [Documentation](./docs/)
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
{
|
||||
"": "Basic phases",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} for config.",
|
||||
"init_ok": "Initializing ready.",
|
||||
"hello": "Greetings from KuiToi Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization complete.",
|
||||
"start": "Server started!",
|
||||
"stop": "Goodbye!",
|
||||
"stop": "Server stopped!",
|
||||
|
||||
"": "Server auth",
|
||||
"auth_need_key": "BEAM key needed for starting the server!",
|
||||
"auth_empty_key": "Key is empty!",
|
||||
"auth_cannot_open_browser": "Cannot open browser: {}",
|
||||
"auth_need_key": "A BeamMP key is required to start the server!",
|
||||
"auth_empty_key": "The BeamMP key is empty!",
|
||||
"auth_cannot_open_browser": "Failed to open browser: {}",
|
||||
"auth_use_link": "Use this link: {}",
|
||||
|
||||
"": "GUI phases",
|
||||
@ -17,32 +17,32 @@
|
||||
"GUI_no": "No",
|
||||
"GUI_ok": "Ok",
|
||||
"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_enter_key_message": "Please type your key:",
|
||||
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
|
||||
"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 enter the key:",
|
||||
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
|
||||
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI running on {} (Press CTRL+C to quit)",
|
||||
"web_start": "WebAPI started at {} (Press CTRL+C to quit)",
|
||||
|
||||
"": "Command: man",
|
||||
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Display the manual page for COMMAND.",
|
||||
"man_for": "Manual for command",
|
||||
"man_message_not_found": "man: Manual message not found.",
|
||||
"man_command_not_found": "man: command \"{}\" not found!",
|
||||
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Displays help page for COMMAND.",
|
||||
"man_for": "Help page for",
|
||||
"man_message_not_found": "man: Help page not found.",
|
||||
"man_command_not_found": "man: Command \"{}\" not found!",
|
||||
|
||||
"": "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.",
|
||||
"help_message_help": "Display names and brief descriptions of available commands",
|
||||
"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": "Displays the names and short descriptions of commands.",
|
||||
"help_command": "Command",
|
||||
"help_message": "Help message",
|
||||
"help_message_not_found": "No help message found",
|
||||
"help_message": "Description",
|
||||
"help_message_not_found": "No description available.",
|
||||
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_stop": "Server shutdown.",
|
||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||
"help_message_stop": "Stops the server.",
|
||||
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_exit": "Server shutdown."
|
||||
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
||||
"help_message_exit": "Stops the server."
|
||||
}
|
@ -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.
|
37
docs/en/plugins/async_example.py
Normal file
37
docs/en/plugins/async_example.py
Normal 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("Плагин выгружен успешно.")
|
@ -1,36 +1,37 @@
|
||||
import KuiToi # Import server object
|
||||
import json
|
||||
|
||||
beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin"
|
||||
log = beam.log # Use logger from server
|
||||
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"
|
||||
|
||||
|
||||
def on_load():
|
||||
# When plugin initialization Server uses plugin.load() to load plugin.
|
||||
# def load(): is really needed
|
||||
log.info(beam.name)
|
||||
def my_event_handler(event_data):
|
||||
log.info(f"{event_data}")
|
||||
|
||||
|
||||
# Events handlers
|
||||
|
||||
def on_started():
|
||||
# Simple event handler
|
||||
log.info("Server starting...")
|
||||
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("Плагин загружен успешно.")
|
||||
|
||||
|
||||
# Simple event register
|
||||
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 any_func(data=None):
|
||||
# Custom event handler
|
||||
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")
|
||||
def unload():
|
||||
# Код завершающий все процессы
|
||||
log.info("Плагин выгружен успешно.")
|
||||
|
@ -1,32 +1,92 @@
|
||||
# Plugins System
|
||||
# Plugin System
|
||||
|
||||
## Install
|
||||
###### (Lib can't ready to use)
|
||||
## Installing the Library with "Stubs"
|
||||
###### (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`
|
||||
* From source:\
|
||||
* From source code:\
|
||||
`git clone https://github.com/KuiToi/KuiToi-PyLib`
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
import KuiToi
|
||||
try:
|
||||
import KuiToi
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
beam = KuiToi("TestPlugin")
|
||||
logger = beam.log
|
||||
kt = KuiToi("Example")
|
||||
log = kt.log
|
||||
|
||||
def load(): # Plugins load from here
|
||||
print(beam.name)
|
||||
def my_event_handler(event_data):
|
||||
log.info(f"{event_data}")
|
||||
|
||||
def on_started():
|
||||
logger.info("Server starting...")
|
||||
def load():
|
||||
# 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']
|
||||
* Create new event : `beam.register_event("my_event", my_event_function)`
|
||||
* Call event: `beam.call_event("my_event")`
|
||||
* Call event with some data: `beam.call_event("my_event", data, data2)`
|
||||
* Calls _**can't support**_ like this: `beam.call_event("my_event", data=data)`
|
||||
* It is recommended to use `open()` after `load()`. Otherwise, use `kt.load()` - creates a file in the `plugin/<plugin_name>/<filename>` folder.
|
||||
* Creating your own event: `kt.register_event("my_event", my_event_function)`
|
||||
* Calling an event: `kt.call_event("my_event")`
|
||||
* Calling an event with data: `kt.call_event("my_event", data, data2=data2)`
|
||||
* 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_
|
@ -1,9 +1,9 @@
|
||||
# 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)
|
||||
2. Plugins and Events system - [here](plugins)
|
||||
3. MultiLanguage - [here](./multilanguage)
|
||||
4. RESP API - [here](./web)
|
||||
5. Something new
|
||||
4. KuiToi WebAPI - [here](./web)
|
||||
5. Something new...
|
||||
|
@ -1,18 +1,32 @@
|
||||
# Hello from KuiToi Server
|
||||
# Greetings from KuiToi Server
|
||||
|
||||
## Start
|
||||
## Well, let's begin
|
||||
|
||||
* Need **Python 3.10.x** to start!
|
||||
* After cloning use this:
|
||||
###### _(Here are the commands for Linux)_
|
||||
|
||||
* **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
|
||||
$ python3 --version # Python 3.10.6
|
||||
$ python3 main.py --help # Show help message
|
||||
$ python3 main.py # Start server
|
||||
python3 --version # Python 3.10.6
|
||||
```
|
||||
* 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
|
||||
!!python/object:modules.ConfigProvider.config_provider.Config
|
||||
Auth:
|
||||
@ -23,11 +37,39 @@ Game:
|
||||
max_cars: 1
|
||||
players: 8
|
||||
Server:
|
||||
debug: true
|
||||
description: This server uses KuiToi!
|
||||
debug: false
|
||||
description: Welcome to KuiToi Server!
|
||||
language: en
|
||||
name: KuiToi-Server
|
||||
server_ip: 0.0.0.0
|
||||
server_port: 30814
|
||||
```
|
||||
* Server can't start without BEAM Auth.key
|
||||
server_port: 30813
|
||||
WebAPI:
|
||||
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_
|
@ -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
|
||||
|
@ -0,0 +1,4 @@
|
||||
# MultiLanguage - Поддержка i18n
|
||||
|
||||
В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json)\
|
||||
Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам.
|
37
docs/ru/plugins/async_example.py
Normal file
37
docs/ru/plugins/async_example.py
Normal 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("Плагин выгружен успешно.")
|
@ -1,36 +1,37 @@
|
||||
import KuiToi # Import server object
|
||||
import json
|
||||
|
||||
beam = KuiToi("TestPlugin") # Init plugin with name "TestPlugin"
|
||||
log = beam.log # Use logger from server
|
||||
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"
|
||||
|
||||
|
||||
def on_load():
|
||||
# When plugin initialization Server uses plugin.load() to load plugin.
|
||||
# def load(): is really needed
|
||||
log.info(beam.name)
|
||||
def my_event_handler(event_data):
|
||||
log.info(f"{event_data}")
|
||||
|
||||
|
||||
# Events handlers
|
||||
|
||||
def on_started():
|
||||
# Simple event handler
|
||||
log.info("Server starting...")
|
||||
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("Плагин загружен успешно.")
|
||||
|
||||
|
||||
# Simple event register
|
||||
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 any_func(data=None):
|
||||
# Custom event handler
|
||||
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")
|
||||
def unload():
|
||||
# Код завершающий все процессы
|
||||
log.info("Плагин выгружен успешно.")
|
||||
|
@ -12,24 +12,81 @@
|
||||
## Пример
|
||||
|
||||
```python
|
||||
import KuiToi
|
||||
try:
|
||||
import KuiToi
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
beam = KuiToi("TestPlugin")
|
||||
logger = beam.log
|
||||
kt = KuiToi("Example")
|
||||
log = kt.log
|
||||
|
||||
def load(): # Plugins load from here
|
||||
print(beam.name)
|
||||
def my_event_handler(event_data):
|
||||
log.info(f"{event_data}")
|
||||
|
||||
def on_started():
|
||||
logger.info("Server starting...")
|
||||
def load():
|
||||
# Инициализация плагина
|
||||
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']
|
||||
* Создание своего ивента : `beam.register_event("my_event", my_event_function)`
|
||||
* Вызов ивента: `beam.call_event("my_event")`
|
||||
* Вызов ивента с данными: `beam.call_event("my_event", data, data2)`
|
||||
* Вызовы с указанием переменой _**не поддерживаются**_: `beam.call_event("my_event", data=data)`
|
||||
## Async функции
|
||||
|
||||
Поддержка async есть
|
||||
|
||||
```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 ивенты: _Позже напишу_
|
||||
|
@ -5,5 +5,5 @@
|
||||
1. Настройка и Запуск сервера - [тута](./setup)
|
||||
2. Плагины и Ивент система - [тута](./plugins)
|
||||
3. Мультиязычность - [тута](./multilanguage)
|
||||
4. RESP API - [тута](./web)
|
||||
4. KuiToi WebAPI - [тута](./web)
|
||||
5. Тут будет что-то ново....
|
||||
|
@ -38,12 +38,39 @@ Game:
|
||||
players: 8
|
||||
Server:
|
||||
debug: false
|
||||
description: This server uses KuiToi!
|
||||
description: Welcome to KuiToi Server!
|
||||
language: en
|
||||
name: KuiToi-Server
|
||||
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 ключ, без него не запустится.
|
||||
* Введя BeamMP ключ сервер появится в списке лаунчера.
|
||||
* Взять ключ можно тут: [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
|
||||
##### _Доки не готовы_
|
||||
|
||||
|
@ -1,3 +1,14 @@
|
||||
# Web RESP API для сервера
|
||||
# WebAPI для сервера
|
||||
|
||||
## Доступные endpoints
|
||||
|
||||
* `/stop`:
|
||||
* Необходимые парамеры:
|
||||
* `secret_key` - Ключ, который указан в конфигурации сервера
|
||||
|
||||
|
||||
* `/event.get`
|
||||
* Точка не готова
|
||||
* Необходимые парамеры:
|
||||
* `secret_key` - Ключ, который указан в конфигурации сервера
|
||||
|
||||
В разработке
|
@ -8,48 +8,85 @@ from core import utils
|
||||
class Client:
|
||||
|
||||
def __init__(self, reader, writer, core):
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.down_rw = (None, None)
|
||||
self.log = utils.get_logger("client(None:0)")
|
||||
self.addr = writer.get_extra_info("sockname")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.Core = core
|
||||
self.cid = -1
|
||||
self.key = None
|
||||
self.nick = None
|
||||
self.roles = None
|
||||
self.guest = True
|
||||
self.alive = True
|
||||
self.ready = False
|
||||
self.__reader = reader
|
||||
self.__writer = writer
|
||||
self._down_rw = (None, None)
|
||||
self.__Core = core
|
||||
self.__alive = True
|
||||
self._loop = asyncio.get_event_loop()
|
||||
self._log = utils.get_logger("client(None:0)")
|
||||
self._addr = writer.get_extra_info("sockname")
|
||||
self._cid = -1
|
||||
self._key = None
|
||||
self._nick = None
|
||||
self._roles = None
|
||||
self._guest = True
|
||||
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):
|
||||
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")
|
||||
|
||||
def is_disconnected(self):
|
||||
if not self.alive:
|
||||
if not self.__alive:
|
||||
return True
|
||||
res = self.writer.is_closing()
|
||||
res = self.__writer.is_closing()
|
||||
if res:
|
||||
self.log.debug(f"Disconnected.")
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
return True
|
||||
else:
|
||||
self.log.debug(f"Alive.")
|
||||
self.alive = True
|
||||
self.__alive = True
|
||||
return False
|
||||
|
||||
async def kick(self, reason):
|
||||
if not self.alive:
|
||||
if not self.__alive:
|
||||
self.log.debug(f"Kick({reason}) skipped;")
|
||||
return
|
||||
# TODO: i18n
|
||||
self.log.info(f"Kicked with reason: \"{reason}\"")
|
||||
await self.tcp_send(b"K" + bytes(reason, "utf-8"))
|
||||
self.alive = False
|
||||
await self._tcp_send(b"K" + bytes(reason, "utf-8"))
|
||||
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
|
||||
# BeamMP TCP protocol sends a header of 4 bytes, followed by the data.
|
||||
@ -58,17 +95,24 @@ class Client:
|
||||
# size data
|
||||
|
||||
if writer is None:
|
||||
writer = self.writer
|
||||
writer = self.__writer
|
||||
|
||||
if to_all:
|
||||
for client in self.Core.clients:
|
||||
if not client:
|
||||
code = data[:1]
|
||||
for client in self.__Core.clients:
|
||||
if not client or (client == self and not to_self):
|
||||
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
|
||||
|
||||
if len(data) == 10:
|
||||
data += b"."
|
||||
header = len(data).to_bytes(4, "little", signed=True)
|
||||
self.log.debug(f'len: {len(data)}; send: {header + data!r}')
|
||||
try:
|
||||
@ -76,11 +120,11 @@ class Client:
|
||||
await writer.drain()
|
||||
except ConnectionError:
|
||||
self.log.debug('tcp_send: Disconnected')
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
|
||||
async def recv(self):
|
||||
async def _recv(self):
|
||||
try:
|
||||
header = await self.reader.read(4)
|
||||
header = await self.__reader.read(4)
|
||||
|
||||
int_header = 0
|
||||
for i in range(len(header)):
|
||||
@ -89,7 +133,7 @@ class Client:
|
||||
if int_header <= 0:
|
||||
await asyncio.sleep(0.1)
|
||||
self.is_disconnected()
|
||||
if self.alive:
|
||||
if self.__alive:
|
||||
self.log.debug(f"Header: {header}")
|
||||
await self.kick("Invalid packet - header negative")
|
||||
return b""
|
||||
@ -100,7 +144,7 @@ class Client:
|
||||
f"assuming malicious intent and disconnecting the client.")
|
||||
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}`;")
|
||||
|
||||
if len(data) != int_header:
|
||||
@ -113,13 +157,13 @@ class Client:
|
||||
return data
|
||||
return data
|
||||
except ConnectionError:
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
return b""
|
||||
|
||||
# TODO: Speed limiter
|
||||
async def _split_load(self, start, end, d_sock, filename):
|
||||
# TODO: Speed limiter
|
||||
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'
|
||||
if config.Server["debug"]:
|
||||
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()
|
||||
self.log.debug(f"[{who}] File sent.")
|
||||
except ConnectionError:
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
self.log.debug(f"[{who}] Disconnected.")
|
||||
return real_size
|
||||
|
||||
async def sync_resources(self):
|
||||
while self.alive:
|
||||
data = await self.recv()
|
||||
async def _sync_resources(self):
|
||||
while self.__alive:
|
||||
data = await self._recv()
|
||||
self.log.debug(f"data: {data!r}")
|
||||
if data.startswith(b"f"):
|
||||
file = data[1:].decode("utf-8")
|
||||
# TODO: i18n
|
||||
self.log.info(f"Requested mode: {file!r}")
|
||||
size = -1
|
||||
for mod in self.Core.mods_list:
|
||||
for mod in self.__Core.mods_list:
|
||||
if type(mod) == int:
|
||||
continue
|
||||
if mod.get('path') == file:
|
||||
@ -154,12 +198,12 @@ class Client:
|
||||
break
|
||||
self.log.debug(f"Mode size: {size}")
|
||||
if size == -1:
|
||||
await self.tcp_send(b"CO")
|
||||
await self._tcp_send(b"CO")
|
||||
await self.kick(f"Not allowed mod: " + file)
|
||||
return
|
||||
await self.tcp_send(b"AG")
|
||||
await self._tcp_send(b"AG")
|
||||
t = 0
|
||||
while not self.down_rw[0]:
|
||||
while not self._down_rw[0]:
|
||||
await asyncio.sleep(0.1)
|
||||
t += 1
|
||||
if t > 50:
|
||||
@ -177,14 +221,14 @@ class Client:
|
||||
lost = size - sent
|
||||
self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}")
|
||||
if not ok:
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
# TODO: i18n
|
||||
self.log.error(f"Error while sending.")
|
||||
return
|
||||
elif data.startswith(b"SR"):
|
||||
path_list = ''
|
||||
size_list = ''
|
||||
for mod in self.Core.mods_list:
|
||||
for mod in self.__Core.mods_list:
|
||||
if type(mod) == int:
|
||||
continue
|
||||
path_list += f"{mod['path']};"
|
||||
@ -192,55 +236,99 @@ class Client:
|
||||
mod_list = path_list + size_list
|
||||
self.log.debug(f"Mods List: {mod_list}")
|
||||
if len(mod_list) == 0:
|
||||
await self.tcp_send(b"-")
|
||||
await self._tcp_send(b"-")
|
||||
else:
|
||||
await self.tcp_send(bytes(mod_list, "utf-8"))
|
||||
await self._tcp_send(bytes(mod_list, "utf-8"))
|
||||
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
|
||||
return
|
||||
|
||||
async def looper(self):
|
||||
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID
|
||||
await self.sync_resources()
|
||||
while self.alive:
|
||||
data = await self.recv()
|
||||
if data == b"":
|
||||
if not self.alive:
|
||||
break
|
||||
else:
|
||||
await asyncio.sleep(.1)
|
||||
self.is_disconnected()
|
||||
continue
|
||||
async def _looper(self):
|
||||
await self._tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID
|
||||
await self._sync_resources()
|
||||
# TODO: GlobalParser
|
||||
while self.__alive:
|
||||
data = await self._recv()
|
||||
if not data:
|
||||
self.__alive = False
|
||||
break
|
||||
|
||||
if 89 >= data[0] >= 86:
|
||||
# TODO: Network.SendToAll
|
||||
pass
|
||||
|
||||
code = data.decode()[0]
|
||||
self.log.debug(f"Received code: {code}, data: {data}")
|
||||
match code:
|
||||
case "H":
|
||||
# Client connected
|
||||
self.ready = True
|
||||
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8"), to_all=True)
|
||||
self._ready = 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":
|
||||
# Chat
|
||||
ev.call_event("chat_receive", f"{data}")
|
||||
await self.tcp_send(data, to_all=True)
|
||||
msg = data[2:].decode()
|
||||
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)
|
||||
self.alive = False
|
||||
self.__alive = False
|
||||
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:
|
||||
# await self.tcp_send(b"", to_all=True) # I'm disconnected.
|
||||
self.log.debug(f"Removing client {self.nick}:{self.cid}")
|
||||
# TODO: i18n
|
||||
self.log.info("Disconnected")
|
||||
self.Core.clients[self.cid] = None
|
||||
self.Core.clients_by_id.pop(self.cid)
|
||||
self.Core.clients_by_nick.pop(self.nick)
|
||||
self.__Core.clients[self.cid] = None
|
||||
self.__Core.clients_by_id.pop(self.cid)
|
||||
self.__Core.clients_by_nick.pop(self.nick)
|
||||
else:
|
||||
self.log.debug(f"Removing client; Closing connection...")
|
||||
if not self.writer.is_closing():
|
||||
self.writer.close()
|
||||
_, down_w = self.down_rw
|
||||
if down_w and not down_w.is_closing():
|
||||
down_w.close()
|
||||
try:
|
||||
if not self.__writer.is_closing():
|
||||
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():
|
||||
down_w.close()
|
||||
except Exception as e:
|
||||
self.log.debug(f"Error while closing download writer: {e}")
|
||||
|
@ -8,26 +8,50 @@ from core import Core, utils
|
||||
class Client:
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
|
||||
self.log = utils.get_logger("client(id: )")
|
||||
self.addr = writer.get_extra_info("sockname")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.Core = core
|
||||
self.cid: int = -1
|
||||
self.key: str = None
|
||||
self.nick: str = None
|
||||
self.roles: str = None
|
||||
self.guest = True
|
||||
self.alive = True
|
||||
self.ready = False
|
||||
self.__reader = reader
|
||||
self.__writer = writer
|
||||
self._down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
|
||||
self._log = utils.get_logger("client(id: )")
|
||||
self._addr = writer.get_extra_info("sockname")
|
||||
self._loop = asyncio.get_event_loop()
|
||||
self.__Core = core
|
||||
self._cid: int = -1
|
||||
self._key: str = None
|
||||
self._nick: str = None
|
||||
self._roles: str = None
|
||||
self._guest = True
|
||||
self.__alive = True
|
||||
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: ...
|
||||
async def kick(self, reason: str) -> None: ...
|
||||
async def tcp_send(self, data: bytes, to_all:bool = False, writer: StreamWriter = None) -> None: ...
|
||||
async def sync_resources(self) -> None: ...
|
||||
async def recv(self) -> bytes: ...
|
||||
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 _recv(self) -> bytes: ...
|
||||
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: ...
|
||||
async def remove_me(self) -> None: ...
|
||||
async def _remove_me(self) -> None: ...
|
||||
|
@ -43,7 +43,11 @@ class Core:
|
||||
self.client_major_version = "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:
|
||||
return self.clients_by_id.get(cid)
|
||||
if nick:
|
||||
@ -61,7 +65,7 @@ class Core:
|
||||
break
|
||||
await asyncio.sleep(random.randint(3, 9) * 0.01)
|
||||
if not self.clients[cid]:
|
||||
client.cid = cid
|
||||
client._cid = cid
|
||||
self.clients_by_nick.update({client.nick: client})
|
||||
self.log.debug(f"Inserting client: {client.nick}:{client.cid}")
|
||||
self.clients_by_id.update({client.cid: client})
|
||||
@ -81,7 +85,7 @@ class Core:
|
||||
for client in self.clients:
|
||||
if not client:
|
||||
continue
|
||||
out += f"{client.nick}"
|
||||
out += f"{client._nick}"
|
||||
if need_cid:
|
||||
out += f":{client.cid}"
|
||||
out += ","
|
||||
@ -100,7 +104,7 @@ class Core:
|
||||
if not client.ready:
|
||||
client.is_disconnected()
|
||||
continue
|
||||
await client.tcp_send(bytes(ca, "utf-8"))
|
||||
await client._tcp_send(bytes(ca, "utf-8"))
|
||||
|
||||
@staticmethod
|
||||
def start_web():
|
||||
@ -252,30 +256,33 @@ class Core:
|
||||
tasks.append(asyncio.create_task(task()))
|
||||
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.log.info(i18n.start)
|
||||
ev.call_event("server_started")
|
||||
await ev.call_async_event("server_started")
|
||||
await t # Wait end.
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.log.error(f"Exception: {e}")
|
||||
self.log.exception(e)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self.run = False
|
||||
self.tcp.stop()
|
||||
# self.udp.stop()
|
||||
self.run = False
|
||||
await self.stop()
|
||||
|
||||
def start(self):
|
||||
asyncio.run(self.main())
|
||||
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
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.log.info(i18n.stop)
|
||||
if config.WebAPI["enabled"]:
|
||||
asyncio.run(self.web_stop())
|
||||
exit(0)
|
||||
# exit(0)
|
||||
|
@ -45,4 +45,4 @@ class Core:
|
||||
async def heartbeat(self, test=False) -> None: ...
|
||||
async def main(self) -> None: ...
|
||||
def start(self) -> None: ...
|
||||
def stop(self) -> None: ...
|
||||
async def stop(self) -> None: ...
|
||||
|
@ -25,36 +25,36 @@ class TCPServer:
|
||||
client = self.Core.create_client(reader, writer)
|
||||
# TODO: i18n
|
||||
self.log.info(f"Identifying new ClientConnection...")
|
||||
data = await client.recv()
|
||||
data = await client._recv()
|
||||
self.log.debug(f"Version: {data}")
|
||||
if data.decode("utf-8") != f"VC{self.Core.client_major_version}":
|
||||
# TODO: i18n
|
||||
await client.kick("Outdated Version.")
|
||||
return False, client
|
||||
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}")
|
||||
if len(data) > 50:
|
||||
# TODO: i18n
|
||||
await client.kick("Invalid Key (too long)!")
|
||||
return False, client
|
||||
client.key = data.decode("utf-8")
|
||||
client._key = data.decode("utf-8")
|
||||
ev.call_event("auth_sent_key", client)
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
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()
|
||||
self.log.debug(f"res: {res}")
|
||||
if res.get("error"):
|
||||
# TODO: i18n
|
||||
await client.kick('Invalid key! Please restart your game.')
|
||||
return False, client
|
||||
client.nick = res["username"]
|
||||
client.roles = res["roles"]
|
||||
client.guest = res["guest"]
|
||||
client._nick = res["username"]
|
||||
client._roles = res["roles"]
|
||||
client._guest = res["guest"]
|
||||
# noinspection PyProtectedMember
|
||||
client._update_logger()
|
||||
except Exception as e:
|
||||
@ -89,7 +89,7 @@ class TCPServer:
|
||||
cid = (await reader.read(1))[0]
|
||||
client = self.Core.get_client(cid=cid)
|
||||
if client:
|
||||
client.down_rw = (reader, writer)
|
||||
client._down_rw = (reader, writer)
|
||||
self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!")
|
||||
else:
|
||||
writer.close()
|
||||
@ -102,7 +102,7 @@ class TCPServer:
|
||||
case "C":
|
||||
result, client = await self.auth_client(reader, writer)
|
||||
if result:
|
||||
await client.looper()
|
||||
await client._looper()
|
||||
return result, client
|
||||
case "D":
|
||||
await self.set_down_rw(reader, writer)
|
||||
@ -128,7 +128,7 @@ class TCPServer:
|
||||
# await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
|
||||
_, cl = await self.handle_code(code, reader, writer)
|
||||
if cl:
|
||||
await cl.remove_me()
|
||||
await cl._remove_me()
|
||||
del cl
|
||||
break
|
||||
except Exception as e:
|
||||
@ -152,7 +152,9 @@ class TCPServer:
|
||||
# TODO: i18n
|
||||
self.log.error("Cannot bind port")
|
||||
raise e
|
||||
except BaseException as e:
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.log.error(f"Error: {e}")
|
||||
raise e
|
||||
finally:
|
||||
|
@ -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('--language', help='Setting localisation.', nargs='?', default=None, type=str)
|
||||
|
||||
run = True
|
||||
|
||||
|
||||
def main():
|
||||
from core import Core
|
||||
core = Core()
|
||||
try:
|
||||
core.start()
|
||||
Core().start()
|
||||
except KeyboardInterrupt:
|
||||
core.run = False
|
||||
core.stop()
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -13,51 +13,80 @@ class EventsSystem:
|
||||
# TODO: default events
|
||||
self.log = get_logger("EventsSystem")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.as_tasks = []
|
||||
self.__events = {
|
||||
"server_started": [],
|
||||
"_plugins_start": [],
|
||||
"auth_sent_key": [],
|
||||
"auth_ok": [],
|
||||
"auth_sent_key": [], # Only sync
|
||||
"auth_ok": [], # Only sync
|
||||
"player_join": [],
|
||||
"chat_receive": [],
|
||||
"_plugins_unload": [],
|
||||
"server_stopped": [],
|
||||
}
|
||||
self.__async_events = {
|
||||
"server_started": [],
|
||||
"_plugins_start": [],
|
||||
"_plugins_unload": [],
|
||||
"player_join": [],
|
||||
"chat_receive": [],
|
||||
"server_stopped": []
|
||||
}
|
||||
|
||||
def builtins_hook(self):
|
||||
self.log.debug("used builtins_hook")
|
||||
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}):")
|
||||
if not callable(event_func):
|
||||
# TODO: i18n
|
||||
self.log.error(f"Cannot add event '{event_name}'. "
|
||||
f"Use `KuiToi.add_event({event_name}', function)` instead. Skipping it...")
|
||||
return
|
||||
if event_name not in self.__events:
|
||||
self.__events.update({str(event_name): [event_func]})
|
||||
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:
|
||||
self.__events[event_name].append(event_func)
|
||||
if event_name not in self.__events:
|
||||
self.__events.update({str(event_name): [event_func]})
|
||||
else:
|
||||
self.__events[event_name].append(event_func)
|
||||
|
||||
def call_event(self, event_name, *args, **kwargs):
|
||||
self.log.debug(f"Using event '{event_name}'")
|
||||
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.__events.keys():
|
||||
for func in self.__events[event_name]:
|
||||
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}
|
||||
if inspect.iscoroutinefunction(func):
|
||||
d = self.loop.run_until_complete(func(event_data))
|
||||
else:
|
||||
d = func(event_data)
|
||||
funcs_data.append(d)
|
||||
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. 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
|
||||
|
@ -1,6 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
class EventsSystem:
|
||||
@staticmethod
|
||||
def register_event(event_name, event_func): ...
|
||||
@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): ...
|
@ -1,15 +1,15 @@
|
||||
{
|
||||
"": "Basic phases",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} for config.",
|
||||
"init_ok": "Initializing ready.",
|
||||
"hello": "Greetings from KuiToi Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization complete.",
|
||||
"start": "Server started!",
|
||||
"stop": "Goodbye!",
|
||||
"stop": "Server stopped!",
|
||||
|
||||
"": "Server auth",
|
||||
"auth_need_key": "BEAM key needed for starting the server!",
|
||||
"auth_empty_key": "Key is empty!",
|
||||
"auth_cannot_open_browser": "Cannot open browser: {}",
|
||||
"auth_need_key": "A BeamMP key is required to start the server!",
|
||||
"auth_empty_key": "The BeamMP key is empty!",
|
||||
"auth_cannot_open_browser": "Failed to open browser: {}",
|
||||
"auth_use_link": "Use this link: {}",
|
||||
|
||||
"": "GUI phases",
|
||||
@ -17,32 +17,32 @@
|
||||
"GUI_no": "No",
|
||||
"GUI_ok": "Ok",
|
||||
"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_enter_key_message": "Please type your key:",
|
||||
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
|
||||
"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 enter the key:",
|
||||
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
|
||||
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI running on {} (Press CTRL+C to quit)",
|
||||
"web_start": "WebAPI started at {} (Press CTRL+C to quit)",
|
||||
|
||||
"": "Command: man",
|
||||
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Display the manual page for COMMAND.",
|
||||
"man_for": "Manual for command",
|
||||
"man_message_not_found": "man: Manual message not found.",
|
||||
"man_command_not_found": "man: command \"{}\" not found!",
|
||||
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Displays help page for COMMAND.",
|
||||
"man_for": "Help page for",
|
||||
"man_message_not_found": "man: Help page not found.",
|
||||
"man_command_not_found": "man: Command \"{}\" not found!",
|
||||
|
||||
"": "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.",
|
||||
"help_message_help": "Display names and brief descriptions of available commands",
|
||||
"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": "Displays the names and short descriptions of commands.",
|
||||
"help_command": "Command",
|
||||
"help_message": "Help message",
|
||||
"help_message_not_found": "No help message found",
|
||||
"help_message": "Description",
|
||||
"help_message_not_found": "No description available.",
|
||||
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_stop": "Server shutdown.",
|
||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||
"help_message_stop": "Stops the server.",
|
||||
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_exit": "Server shutdown."
|
||||
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
||||
"help_message_exit": "Stops the server."
|
||||
}
|
@ -89,53 +89,53 @@ class MultiLanguage:
|
||||
else:
|
||||
# noinspection PyDictDuplicateKeys
|
||||
self.__data = {
|
||||
"": "Basic phases",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} for config.",
|
||||
"init_ok": "Initializing ready.",
|
||||
"start": "Server started!",
|
||||
"stop": "Goodbye!",
|
||||
"": "Basic phases",
|
||||
"hello": "Greetings from KuiToi Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization complete.",
|
||||
"start": "Server started!",
|
||||
"stop": "Server stopped!",
|
||||
|
||||
"": "Server auth",
|
||||
"auth_need_key": "BEAM key needed for starting the server!",
|
||||
"auth_empty_key": "Key is empty!",
|
||||
"auth_cannot_open_browser": "Cannot open browser: {}",
|
||||
"auth_use_link": "Use this link: {}",
|
||||
"": "Server auth",
|
||||
"auth_need_key": "A BeamMP key is required to start the server!",
|
||||
"auth_empty_key": "The BeamMP key is empty!",
|
||||
"auth_cannot_open_browser": "Failed to open browser: {}",
|
||||
"auth_use_link": "Use this link: {}",
|
||||
|
||||
"": "GUI phases",
|
||||
"GUI_yes": "Yes",
|
||||
"GUI_no": "No",
|
||||
"GUI_ok": "Ok",
|
||||
"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_enter_key_message": "Please type your key:",
|
||||
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
|
||||
"": "GUI phases",
|
||||
"GUI_yes": "Yes",
|
||||
"GUI_no": "No",
|
||||
"GUI_ok": "Ok",
|
||||
"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_enter_key_message": "Please enter the key:",
|
||||
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
|
||||
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI running on {} (Press CTRL+C to quit)",
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI started at {} (Press CTRL+C to quit)",
|
||||
|
||||
"": "Command: man",
|
||||
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Display the manual page for COMMAND.",
|
||||
"man_for": "Manual for command",
|
||||
"man_message_not_found": "man: Manual message not found.",
|
||||
"man_command_not_found": "man: command \"{}\" not found!",
|
||||
"": "Command: man",
|
||||
"man_message_man": "man - Displays help page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Displays help page for COMMAND.",
|
||||
"man_for": "Help page for",
|
||||
"man_message_not_found": "man: Help page not found.",
|
||||
"man_command_not_found": "man: Command \"{}\" not found!",
|
||||
|
||||
"": "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.",
|
||||
"help_message_help": "Display names and brief descriptions of available commands",
|
||||
"help_command": "Command",
|
||||
"help_message": "Help message",
|
||||
"help_message_not_found": "No help message found",
|
||||
"": "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.",
|
||||
"help_message_help": "Displays the names and short descriptions of commands.",
|
||||
"help_command": "Command",
|
||||
"help_message": "Description",
|
||||
"help_message_not_found": "No description available.",
|
||||
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_stop": "Server shutdown.",
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||
"help_message_stop": "Stops the server.",
|
||||
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_exit": "Server shutdown."
|
||||
}
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
||||
"help_message_exit": "Stops the server."
|
||||
}
|
||||
self.__i18n = i18n(self.__data)
|
||||
|
||||
def open_file(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user