mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2026-06-22 08:31:28 +00:00
Compare commits
27 Commits
0.4.4-beta
...
59c113ade5
| Author | SHA1 | Date | |
|---|---|---|---|
| 59c113ade5 | |||
| a06c48dca5 | |||
| 42e4a8e05e | |||
| 074589da47 | |||
| 50c12f4b5c | |||
| f145048cc1 | |||
| 68bf7d0d00 | |||
| e9815cdfcf | |||
| acb2b45626 | |||
| eb71fda356 | |||
| cc400d5a12 | |||
| 67b3bd26bb | |||
| 77250561fb | |||
| 70dfb9b40c | |||
| 3118c74990 | |||
| cb6adde7c2 | |||
| 422dd35a8f | |||
| 2c2bd1cb4a | |||
| 7eba3d5877 | |||
| abbd64184e | |||
| e5dd63579b | |||
| ef286b7e03 | |||
| 3a42fa13e7 | |||
| cdec0b9949 | |||
| de91d075b4 | |||
| a7c02e0b52 | |||
| 6dd3de63a9 |
@@ -140,3 +140,6 @@ dmypy.json
|
|||||||
logs/
|
logs/
|
||||||
*.yml
|
*.yml
|
||||||
*.toml
|
*.toml
|
||||||
|
|
||||||
|
/win-ver_info.txt
|
||||||
|
/output/
|
||||||
@@ -75,14 +75,15 @@ I didn't like writing plugins in Lua after using Python; it was very inconvenien
|
|||||||
- [ ] Sync with event system
|
- [ ] Sync with event system
|
||||||
- [ ] Add methods...
|
- [ ] Add methods...
|
||||||
- [ ] RCON System:
|
- [ ] RCON System:
|
||||||
- [ ] Serving
|
- [x] Serving
|
||||||
- [ ] Client
|
- [ ] Handle commands
|
||||||
|
- [x] Client
|
||||||
- [x] AES encryption
|
- [x] AES encryption
|
||||||
- [ ] KuiToi System
|
- [ ] KuiToi System
|
||||||
- [ ] Servers counter
|
- [ ] Servers counter
|
||||||
- [ ] Players counter
|
- [ ] Players counter
|
||||||
- [ ] Etc.
|
- [ ] Etc.
|
||||||
- [ ] [Documentation](./docs/)
|
- [ ] [Documentation](./docs)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# MultiLanguage - i18n支持
|
# MultiLanguage - i18n支持
|
||||||
|
|
||||||
在 [example.json](./example.json) 中是 [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json) 的副本。如果你想将其翻译成以前未翻译过的语言,或者更新现有的翻译,我将很高兴接受你的拉取请求。
|
在 [example.json](./example.json) 中是 [src/modules/i18n/files/ru.json](../../../src/translates/ru.json) 的副本。如果你想将其翻译成以前未翻译过的语言,或者更新现有的翻译,我将很高兴接受你的拉取请求。
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# MultiLanguage - i18n Support
|
# 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).\
|
In [example.json](./example.json) you will find a copy of [src/modules/i18n/files/ru.json](../../../src/translates/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.
|
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.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# MultiLanguage - Поддержка i18n
|
# MultiLanguage - Поддержка i18n
|
||||||
|
|
||||||
В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json)\
|
В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/translates/ru.json)\
|
||||||
Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам.
|
Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам.
|
||||||
|
|||||||
+46
-12
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.tcp_server.py
|
# File core.tcp_server.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -32,6 +32,7 @@ class Client:
|
|||||||
self.nick = None
|
self.nick = None
|
||||||
self.roles = None
|
self.roles = None
|
||||||
self._guest = True
|
self._guest = True
|
||||||
|
self._synced = False
|
||||||
self._ready = False
|
self._ready = False
|
||||||
self._identifiers = []
|
self._identifiers = []
|
||||||
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
|
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
|
||||||
@@ -44,6 +45,10 @@ class Client:
|
|||||||
def _writer(self):
|
def _writer(self):
|
||||||
return self.__writer
|
return self.__writer
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive(self):
|
||||||
|
return self.__alive
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def log(self):
|
def log(self):
|
||||||
return self._log
|
return self._log
|
||||||
@@ -68,6 +73,10 @@ class Client:
|
|||||||
def guest(self):
|
def guest(self):
|
||||||
return self._guest
|
return self._guest
|
||||||
|
|
||||||
|
@property
|
||||||
|
def synced(self):
|
||||||
|
return self._synced
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ready(self):
|
def ready(self):
|
||||||
return self._ready
|
return self._ready
|
||||||
@@ -185,8 +194,8 @@ class Client:
|
|||||||
await writer.drain()
|
await writer.drain()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except ConnectionError:
|
except Exception as e:
|
||||||
self.log.debug('[TCP] Disconnected')
|
self.log.debug(f'[TCP] Disconnected: {e}')
|
||||||
self.__alive = False
|
self.__alive = False
|
||||||
await self._remove_me()
|
await self._remove_me()
|
||||||
return False
|
return False
|
||||||
@@ -194,7 +203,13 @@ class Client:
|
|||||||
async def _recv(self, one=False):
|
async def _recv(self, one=False):
|
||||||
while self.__alive:
|
while self.__alive:
|
||||||
try:
|
try:
|
||||||
header = await self.__reader.read(4)
|
header = b""
|
||||||
|
while len(header) < 4 and self.__alive:
|
||||||
|
h = await self.__reader.read(4)
|
||||||
|
if not h:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header += h
|
||||||
|
|
||||||
int_header = int.from_bytes(header, byteorder='little', signed=True)
|
int_header = int.from_bytes(header, byteorder='little', signed=True)
|
||||||
|
|
||||||
@@ -219,9 +234,14 @@ class Client:
|
|||||||
self.__packets_queue.append(None)
|
self.__packets_queue.append(None)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = await self.__reader.read(int_header)
|
data = b""
|
||||||
|
while len(data) < int_header and self.__alive:
|
||||||
|
buffer = await self.__reader.read(int_header - len(data))
|
||||||
|
if not buffer:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
data += buffer
|
||||||
|
|
||||||
# self.log.debug(f"int_header: {int_header}; data: `{data}`;")
|
|
||||||
abg = b"ABG:"
|
abg = b"ABG:"
|
||||||
if len(data) > len(abg) and data.startswith(abg):
|
if len(data) > len(abg) and data.startswith(abg):
|
||||||
data = zlib.decompress(data[len(abg):])
|
data = zlib.decompress(data[len(abg):])
|
||||||
@@ -250,7 +270,7 @@ class Client:
|
|||||||
try:
|
try:
|
||||||
writer.write(data)
|
writer.write(data)
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
self.log.debug(f"[{who}] Sent {len(data)} bytes.")
|
# self.log.debug(f"[{who}] Sent {len(data)} bytes.")
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
self.__alive = False
|
self.__alive = False
|
||||||
self.log.debug(f"[{who}] Disconnected.")
|
self.log.debug(f"[{who}] Disconnected.")
|
||||||
@@ -269,6 +289,9 @@ class Client:
|
|||||||
async def _sync_resources(self):
|
async def _sync_resources(self):
|
||||||
while self.__alive:
|
while self.__alive:
|
||||||
data = await self._recv(True)
|
data = await self._recv(True)
|
||||||
|
if data is None:
|
||||||
|
await self._remove_me()
|
||||||
|
break
|
||||||
if data.startswith(b"f"):
|
if data.startswith(b"f"):
|
||||||
file = data[1:].decode(config.enc)
|
file = data[1:].decode(config.enc)
|
||||||
self.log.info(i18n.client_mod_request.format(repr(file)))
|
self.log.info(i18n.client_mod_request.format(repr(file)))
|
||||||
@@ -307,7 +330,7 @@ class Client:
|
|||||||
self._split_load(half_size, size, True, file, speed)
|
self._split_load(half_size, size, True, file, speed)
|
||||||
]
|
]
|
||||||
sl0, sl1 = await asyncio.gather(*uploads)
|
sl0, sl1 = await asyncio.gather(*uploads)
|
||||||
tr = time.monotonic() - t
|
tr = (time.monotonic() - t) or 0.0001
|
||||||
if self.__Core.lock_upload:
|
if self.__Core.lock_upload:
|
||||||
self.__Core.lock_upload = False
|
self.__Core.lock_upload = False
|
||||||
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
|
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
|
||||||
@@ -347,7 +370,7 @@ class Client:
|
|||||||
id_sep = s.find('-')
|
id_sep = s.find('-')
|
||||||
if id_sep == -1:
|
if id_sep == -1:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}'")
|
f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}', {s}")
|
||||||
return -1, -1
|
return -1, -1
|
||||||
cid = s[:id_sep]
|
cid = s[:id_sep]
|
||||||
vid = s[id_sep + 1:]
|
vid = s[id_sep + 1:]
|
||||||
@@ -497,6 +520,12 @@ class Client:
|
|||||||
else:
|
else:
|
||||||
self.log.debug(f"Invalid car: car_id={car_id}")
|
self.log.debug(f"Invalid car: car_id={car_id}")
|
||||||
|
|
||||||
|
async def reset_car(self, car_id, x, y, z, rot=None):
|
||||||
|
# TODO: reset_car
|
||||||
|
self.log.debug(f"Resetting car from plugin")
|
||||||
|
if rot is None:
|
||||||
|
rot = {"y": 0, "w": 0, "x": 0, "z": 0}
|
||||||
|
|
||||||
async def _reset_car(self, raw_data):
|
async def _reset_car(self, raw_data):
|
||||||
cid, car_id = self._get_cid_vid(raw_data)
|
cid, car_id = self._get_cid_vid(raw_data)
|
||||||
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
||||||
@@ -538,7 +567,7 @@ class Client:
|
|||||||
|
|
||||||
case "t": # Broken details
|
case "t": # Broken details
|
||||||
self.log.debug(f"Something changed/broken: {raw_data}")
|
self.log.debug(f"Something changed/broken: {raw_data}")
|
||||||
cid, car_id = self._get_cid_vid(raw_data[5:])
|
cid, car_id = self._get_cid_vid(raw_data)
|
||||||
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
||||||
data = raw_data[raw_data.find("{"):]
|
data = raw_data[raw_data.find("{"):]
|
||||||
ev.call_event("onCarChanged", car_id=car_id, data=data)
|
ev.call_event("onCarChanged", car_id=car_id, data=data)
|
||||||
@@ -547,7 +576,7 @@ class Client:
|
|||||||
|
|
||||||
case "m": # Move focus car
|
case "m": # Move focus car
|
||||||
self.log.debug(f"Move focus to: {raw_data}")
|
self.log.debug(f"Move focus to: {raw_data}")
|
||||||
cid, car_id = self._get_cid_vid(raw_data[5:])
|
cid, car_id = self._get_cid_vid(raw_data[3:])
|
||||||
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
||||||
self._focus_car = car_id
|
self._focus_car = car_id
|
||||||
data = raw_data[raw_data.find("{"):]
|
data = raw_data[raw_data.find("{"):]
|
||||||
@@ -593,6 +622,8 @@ class Client:
|
|||||||
ev_data_list.extend(d2)
|
ev_data_list.extend(d2)
|
||||||
need_send = True
|
need_send = True
|
||||||
for ev_data in ev_data_list:
|
for ev_data in ev_data_list:
|
||||||
|
if ev_data is None:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
message = ev_data["message"]
|
message = ev_data["message"]
|
||||||
to_all = ev_data.get("to_all")
|
to_all = ev_data.get("to_all")
|
||||||
@@ -610,7 +641,9 @@ class Client:
|
|||||||
self.log.info(f"{message}" if to_all else f"{self.nick}: {msg}")
|
self.log.info(f"{message}" if to_all else f"{self.nick}: {msg}")
|
||||||
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
|
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
|
||||||
need_send = False
|
need_send = False
|
||||||
except KeyError | AttributeError:
|
except KeyError:
|
||||||
|
self.log.error(i18n.client_event_invalid_data.format(ev_data))
|
||||||
|
except AttributeError:
|
||||||
self.log.error(i18n.client_event_invalid_data.format(ev_data))
|
self.log.error(i18n.client_event_invalid_data.format(ev_data))
|
||||||
if need_send:
|
if need_send:
|
||||||
if config.Options['log_chat']:
|
if config.Options['log_chat']:
|
||||||
@@ -678,6 +711,7 @@ class Client:
|
|||||||
tasks = self.__tasks
|
tasks = self.__tasks
|
||||||
recv = asyncio.create_task(self._recv())
|
recv = asyncio.create_task(self._recv())
|
||||||
tasks.append(recv)
|
tasks.append(recv)
|
||||||
|
self._synced = True
|
||||||
while self.__alive:
|
while self.__alive:
|
||||||
if len(self.__packets_queue) > 0:
|
if len(self.__packets_queue) > 0:
|
||||||
for index, packet in enumerate(self.__packets_queue):
|
for index, packet in enumerate(self.__packets_queue):
|
||||||
|
|||||||
+6
-1
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.tcp_server.py
|
# File core.tcp_server.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -32,6 +32,7 @@ class Client:
|
|||||||
self.roles: str = None
|
self.roles: str = None
|
||||||
self._guest = True
|
self._guest = True
|
||||||
self.__alive = True
|
self.__alive = True
|
||||||
|
self._synced = False
|
||||||
self._ready = False
|
self._ready = False
|
||||||
self._focus_car = -1
|
self._focus_car = -1
|
||||||
self._identifiers = []
|
self._identifiers = []
|
||||||
@@ -42,6 +43,8 @@ class Client:
|
|||||||
@property
|
@property
|
||||||
def _writer(self) -> StreamWriter: ...
|
def _writer(self) -> StreamWriter: ...
|
||||||
@property
|
@property
|
||||||
|
def alive(self) -> bool: ...
|
||||||
|
@property
|
||||||
def log(self) -> Logger: ...
|
def log(self) -> Logger: ...
|
||||||
@property
|
@property
|
||||||
def addr(self) -> Tuple[str, int]: ...
|
def addr(self) -> Tuple[str, int]: ...
|
||||||
@@ -53,6 +56,8 @@ class Client:
|
|||||||
@property
|
@property
|
||||||
def guest(self) -> bool: ...
|
def guest(self) -> bool: ...
|
||||||
@property
|
@property
|
||||||
|
def synced(self) -> bool: ...
|
||||||
|
@property
|
||||||
def ready(self) -> bool: ...
|
def ready(self) -> bool: ...
|
||||||
@property
|
@property
|
||||||
def identifiers(self) -> list: ...
|
def identifiers(self) -> list: ...
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.__init__.py
|
# File core.__init__.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Version 1.4
|
# Version 1.5
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
|
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
__title__ = 'KuiToi-Server'
|
__title__ = 'KuiToi-Server'
|
||||||
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
||||||
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
||||||
__version__ = '0.4.3'
|
__version__ = '0.4.5'
|
||||||
__build__ = 2125 # Я это считаю лог файлами
|
__build__ = 2303 # Я это считаю лог файлами
|
||||||
__author__ = 'SantaSpeen'
|
__author__ = 'SantaSpeen'
|
||||||
__author_email__ = 'admin@kuitoi.su'
|
__author_email__ = 'admin@kuitoi.su'
|
||||||
__license__ = "FPA"
|
__license__ = "FPA"
|
||||||
@@ -30,7 +30,7 @@ from modules import ConfigProvider, EventsSystem
|
|||||||
from modules import Console
|
from modules import Console
|
||||||
from modules import MultiLanguage
|
from modules import MultiLanguage
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
if args.version:
|
if args.version:
|
||||||
print(f"{__title__}:\n\tVersion: {__version__}\n\tBuild: {__build__}")
|
print(f"{__title__}:\n\tVersion: {__version__}\n\tBuild: {__build__}")
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|||||||
+33
-4
@@ -1,18 +1,20 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.core.py
|
# File core.core.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Version: 0.4.3
|
# Version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from core import utils
|
from core import utils, __version__
|
||||||
from core.Client import Client
|
from core.Client import Client
|
||||||
from core.tcp_server import TCPServer
|
from core.tcp_server import TCPServer
|
||||||
from core.udp_server import UDPServer
|
from core.udp_server import UDPServer
|
||||||
@@ -26,6 +28,7 @@ class Core:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.log = utils.get_logger("core")
|
self.log = utils.get_logger("core")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
|
self.start_time = time.monotonic()
|
||||||
self.run = False
|
self.run = False
|
||||||
self.direct = False
|
self.direct = False
|
||||||
self.clients = []
|
self.clients = []
|
||||||
@@ -54,7 +57,7 @@ class Core:
|
|||||||
return None
|
return None
|
||||||
if cid is not None:
|
if cid is not None:
|
||||||
if cid == -1:
|
if cid == -1:
|
||||||
return [i for i in self.clients if i is not None]
|
return [i for i in self.clients if i is not None and i.synced]
|
||||||
return self.clients_by_id.get(cid)
|
return self.clients_by_id.get(cid)
|
||||||
if nick:
|
if nick:
|
||||||
return self.clients_by_nick.get(nick)
|
return self.clients_by_nick.get(nick)
|
||||||
@@ -111,6 +114,8 @@ class Core:
|
|||||||
if not client.ready:
|
if not client.ready:
|
||||||
client.is_disconnected()
|
client.is_disconnected()
|
||||||
continue
|
continue
|
||||||
|
if not client.alive:
|
||||||
|
await client.kick("You are not alive!")
|
||||||
await client._send(ca)
|
await client._send(ca)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error("Error in check_alive.")
|
self.log.error("Error in check_alive.")
|
||||||
@@ -217,6 +222,22 @@ class Core:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Error in heartbeat: {e}")
|
self.log.error(f"Error in heartbeat: {e}")
|
||||||
|
|
||||||
|
async def kick_cmd(self, args):
|
||||||
|
if not len(args) > 0:
|
||||||
|
return "\nUsage: kick <nick>|:<id> [reason]\nExamples:\n\tkick admin bad boy\n\tkick :0 bad boy"
|
||||||
|
reason = "kicked by console."
|
||||||
|
if len(args) > 1:
|
||||||
|
reason = " ".join(args[1:])
|
||||||
|
cl = args[0]
|
||||||
|
if cl.startswith(":") and cl[1:].isdigit():
|
||||||
|
client = self.get_client(cid=int(cl[1:]))
|
||||||
|
else:
|
||||||
|
client = self.get_client(nick=cl)
|
||||||
|
if client:
|
||||||
|
await client.kick(reason)
|
||||||
|
else:
|
||||||
|
return "Client not found."
|
||||||
|
|
||||||
async def main(self):
|
async def main(self):
|
||||||
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
||||||
self.udp = self.udp(self, self.server_ip, self.server_port)
|
self.udp = self.udp(self, self.server_ip, self.server_port)
|
||||||
@@ -224,6 +245,7 @@ class Core:
|
|||||||
"list",
|
"list",
|
||||||
lambda x: f"Players list: {self.get_clients_list(True)}"
|
lambda x: f"Players list: {self.get_clients_list(True)}"
|
||||||
)
|
)
|
||||||
|
console.add_command("kick", self.kick_cmd)
|
||||||
|
|
||||||
pl_dir = "plugins"
|
pl_dir = "plugins"
|
||||||
self.log.debug("Initializing PluginsLoaders...")
|
self.log.debug("Initializing PluginsLoaders...")
|
||||||
@@ -271,6 +293,7 @@ class Core:
|
|||||||
# self.udp.start,
|
# self.udp.start,
|
||||||
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
|
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
|
||||||
if config.RCON['enabled']:
|
if config.RCON['enabled']:
|
||||||
|
console.rcon.version = f"KuiToi {__version__}"
|
||||||
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
|
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
|
||||||
f_tasks.append(rcon.start)
|
f_tasks.append(rcon.start)
|
||||||
for task in f_tasks:
|
for task in f_tasks:
|
||||||
@@ -307,6 +330,12 @@ class Core:
|
|||||||
ev.call_event("_lua_plugins_unload")
|
ev.call_event("_lua_plugins_unload")
|
||||||
await ev.call_async_event("_plugins_unload")
|
await ev.call_async_event("_plugins_unload")
|
||||||
self.run = False
|
self.run = False
|
||||||
self.log.info(i18n.stop)
|
|
||||||
if config.WebAPI["enabled"]:
|
if config.WebAPI["enabled"]:
|
||||||
asyncio.run(self.web_stop())
|
asyncio.run(self.web_stop())
|
||||||
|
total_time = time.monotonic() - self.start_time
|
||||||
|
hours = int(total_time // 3600)
|
||||||
|
minutes = int((total_time % 3600) // 60)
|
||||||
|
seconds = math.ceil(total_time % 60)
|
||||||
|
t = f"{'' if not hours else f'{hours} hours, '}{'' if not hours else f'{minutes} min., '}{seconds} sec."
|
||||||
|
self.log.info(f"Working time: {t}")
|
||||||
|
self.log.info(i18n.stop)
|
||||||
|
|||||||
+4
-1
@@ -1,10 +1,11 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.core.pyi
|
# File core.core.pyi
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable, List, Dict
|
from typing import Callable, List, Dict
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ from .udp_server import UDPServer
|
|||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.start_time = time.monotonic()
|
||||||
self.log = utils.get_logger("core")
|
self.log = utils.get_logger("core")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.run = False
|
self.run = False
|
||||||
@@ -45,6 +47,7 @@ class Core:
|
|||||||
def start_web() -> None: ...
|
def start_web() -> None: ...
|
||||||
def stop_me(self) -> None: ...
|
def stop_me(self) -> None: ...
|
||||||
async def heartbeat(self, test=False) -> None: ...
|
async def heartbeat(self, test=False) -> None: ...
|
||||||
|
async def kick_cmd(self, args: list) -> None | str: ...
|
||||||
async def main(self) -> None: ...
|
async def main(self) -> None: ...
|
||||||
def start(self) -> None: ...
|
def start(self) -> None: ...
|
||||||
async def stop(self) -> None: ...
|
async def stop(self) -> None: ...
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.tcp_server.py
|
# File core.tcp_server.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -53,6 +53,8 @@ class TCPServer:
|
|||||||
client.roles = res["roles"]
|
client.roles = res["roles"]
|
||||||
client._guest = res["guest"]
|
client._guest = res["guest"]
|
||||||
client._identifiers = {k: v for s in res["identifiers"] for k, v in [s.split(':')]}
|
client._identifiers = {k: v for s in res["identifiers"] for k, v in [s.split(':')]}
|
||||||
|
if not client._identifiers.get("ip"):
|
||||||
|
client._identifiers["ip"] = client._addr[0]
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
client._update_logger()
|
client._update_logger()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.tcp_server.pyi
|
# File core.tcp_server.pyi
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.udp_server.py
|
# File core.udp_server.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -100,8 +100,8 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
await asyncio.sleep(0.2)
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.run = False
|
# self.run = False
|
||||||
self.Core.run = False
|
# self.Core.run = False
|
||||||
self.log.error(f"Cannot bind port or other error: {e}")
|
self.log.error(f"Cannot bind port or other error: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Error: {e}")
|
self.log.error(f"Error: {e}")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File core.udp_server.py
|
# File core.udp_server.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# File core.utils.py
|
# File core.utils.py
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
# Version 1.1
|
# Version 1.1
|
||||||
# Core version: 0.4.3
|
# Core version: 0.4.5
|
||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import datetime
|
import datetime
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class Config:
|
|||||||
"access_token": secrets.token_hex(16)}
|
"access_token": secrets.token_hex(16)}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
|
return f"{self.__class__.__name__}(Auth={self.Auth!r}, Game={self.Game!r}, Server={self.Server!r}, " \
|
||||||
|
f"RCON={self.RCON!r}, Options={self.Options!r}, WebAPI={self.WebAPI!r})"
|
||||||
|
|
||||||
|
|
||||||
class ConfigProvider:
|
class ConfigProvider:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import asyncio
|
||||||
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
import zlib
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
from cryptography.hazmat.primitives import padding
|
from cryptography.hazmat.primitives import padding
|
||||||
@@ -7,86 +10,177 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|||||||
|
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
|
|
||||||
"""
|
|
||||||
shared key: SHA256 of "password"
|
|
||||||
<header>: "\x00\x00\x00\x00" (Byte order: Little Endian) - like you use
|
|
||||||
<iv>: A set of random bytes packed in base64 (New for each message)
|
|
||||||
-> To server
|
|
||||||
<- From server
|
|
||||||
|
|
||||||
Open TCP connection /
|
|
||||||
| -> "<iv>:hello" Without header, immediately with AES encryption (shared key)
|
|
||||||
| *Decrypt and some processes*
|
|
||||||
| Fail /
|
|
||||||
| | <- ":E:Bad key" | ":E:Error Message" Without header, without AES encryption
|
|
||||||
| | tcp.close() # End
|
|
||||||
| Success /
|
|
||||||
| | <- "<iv>:hello" with header, with AES encryption
|
|
||||||
| | (Next, everywhere with header, with AES encryption)
|
|
||||||
| -> "<iv>:<header>Cs:ver"
|
|
||||||
| <- "<iv>:<header>Os:KuiToi 0.4.3 | "<iv>:<header>Os:BeamMP 3.2.0"
|
|
||||||
| # Prints server and they version
|
|
||||||
| -> "<iv>:<header>Cs:commands"
|
|
||||||
| <- "<iv>:<header>Os:stop,help,plugins" | "<iv>:<header>Os:SKIP" For an autocomplete; "SKIP" For no autocomplete;
|
|
||||||
| *Ready to handle commands*
|
|
||||||
| -> "<iv>:<header>C:help"
|
|
||||||
| <- "<iv>:<header>O:stop: very cool stop\nhelp: Yayayayoy"
|
|
||||||
| -> "<iv>:<header>C:...."
|
|
||||||
| <- "<iv>:<header>O:...."
|
|
||||||
| -> "<iv>:<header>C:exit"
|
|
||||||
| tcp.close()
|
|
||||||
|
|
||||||
Codes:
|
|
||||||
* "hello" - Hello message
|
|
||||||
* "E:error_message" - Send RCON error
|
|
||||||
* "C:command" - Receive command
|
|
||||||
* "Cs:" - Receive system command
|
|
||||||
* "O:output" - Send command output
|
|
||||||
* "Os:" - Send system output
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class RCONSystem:
|
class RCONSystem:
|
||||||
console = None
|
console = None
|
||||||
|
version = "verError"
|
||||||
|
|
||||||
def __init__(self, key, host, port):
|
def __init__(self, key, host, port):
|
||||||
self.log = get_logger("RCON")
|
self.log = get_logger("RCON")
|
||||||
self.key = key
|
self.key = hashlib.sha256(key.encode(config.enc)).digest()
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.run = False
|
||||||
|
|
||||||
def encrypt(self, message, key):
|
def _encrypt(self, message):
|
||||||
self.log.debug(f"Encrypt message: {message}")
|
self.log.debug(f"Encrypt message: {message}")
|
||||||
key = hashlib.sha256(key).digest()
|
|
||||||
iv = os.urandom(16)
|
iv = os.urandom(16)
|
||||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
||||||
encryptor = cipher.encryptor()
|
encryptor = cipher.encryptor()
|
||||||
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
||||||
padded_data = padder.update(message.encode('utf-8')) + padder.finalize()
|
padded_data = padder.update(message) + padder.finalize()
|
||||||
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
|
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
|
||||||
encoded_data = b64encode(encrypted_data)
|
encoded_data = b64encode(zlib.compress(encrypted_data, level=zlib.Z_BEST_COMPRESSION))
|
||||||
encoded_iv = b64encode(iv)
|
encoded_iv = b64encode(iv)
|
||||||
return encoded_iv + b":" + encoded_data
|
return encoded_iv + b":" + encoded_data
|
||||||
|
|
||||||
def decrypt(self, ciphertext, key):
|
def _decrypt(self, ciphertext):
|
||||||
self.log.debug(f"Dencrypt message: {ciphertext}")
|
self.log.debug(f"Decrypt message: {ciphertext}")
|
||||||
key = hashlib.sha256(key).digest()
|
encoded_iv, encoded_data = ciphertext.split(b":", 2)
|
||||||
encoded_iv, encoded_data = ciphertext.split(":")
|
|
||||||
iv = b64decode(encoded_iv)
|
iv = b64decode(encoded_iv)
|
||||||
encrypted_data = b64decode(encoded_data)
|
encrypted_data = zlib.decompress(b64decode(encoded_data))
|
||||||
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
||||||
decryptor = cipher.decryptor()
|
decryptor = cipher.decryptor()
|
||||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||||
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
|
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
|
||||||
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
|
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
|
||||||
return unpadded_data.decode('utf-8')
|
return unpadded_data
|
||||||
|
|
||||||
async def handle_client(self):
|
async def _recv(self, reader, writer) -> tuple[str, bool]:
|
||||||
pass
|
try:
|
||||||
|
header = b""
|
||||||
|
while len(header) < 4:
|
||||||
|
h = await reader.read(4 - len(header))
|
||||||
|
if not h:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
header += h
|
||||||
|
header = int.from_bytes(header, byteorder='little', signed=True)
|
||||||
|
if header <= 0:
|
||||||
|
self.log.warning("Connection closed!")
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
encrypted_data = b""
|
||||||
|
while len(encrypted_data) < header:
|
||||||
|
buffer = await reader.read(header - len(encrypted_data))
|
||||||
|
if not buffer:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
encrypted_data += buffer
|
||||||
|
try:
|
||||||
|
data, s = self._decrypt(encrypted_data), True
|
||||||
|
except binascii.Error:
|
||||||
|
data, s = encrypted_data, False
|
||||||
|
except ValueError:
|
||||||
|
data, s = encrypted_data, False
|
||||||
|
|
||||||
|
self.log.debug(f"Received: {data}, {s}")
|
||||||
|
return data.decode(config.enc), s
|
||||||
|
except ConnectionResetError:
|
||||||
|
self.log.warning("Connection reset.")
|
||||||
|
return "", False
|
||||||
|
|
||||||
|
async def _send(self, data, writer, encrypt=True, warn=True):
|
||||||
|
self.log.debug(f"Sending: \"{data}\"")
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode(config.enc)
|
||||||
|
|
||||||
|
if encrypt:
|
||||||
|
data = self._encrypt(data)
|
||||||
|
self.log.debug(f"Send encrypted: {data}")
|
||||||
|
|
||||||
|
header = len(data).to_bytes(4, "little", signed=True)
|
||||||
|
try:
|
||||||
|
writer.write(header + data)
|
||||||
|
await writer.drain()
|
||||||
|
return True
|
||||||
|
except ConnectionError:
|
||||||
|
self.log.debug("Sending error...")
|
||||||
|
if encrypt and warn:
|
||||||
|
self.log.warning("Connection closed!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def send_hello(self, writer, work):
|
||||||
|
while work[0]:
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
if not await self._send("Cs:hello", writer, warn=False):
|
||||||
|
work[0] = False
|
||||||
|
writer.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
async def while_handle(self, reader, writer):
|
||||||
|
ver, status = await self._recv(reader, writer)
|
||||||
|
if ver == "ver" and status:
|
||||||
|
await self._send(self.version, writer)
|
||||||
|
cmds, status = await self._recv(reader, writer)
|
||||||
|
if cmds == "commands" and status:
|
||||||
|
await self._send("SKIP", writer)
|
||||||
|
work = [True]
|
||||||
|
t = asyncio.create_task(self.send_hello(writer, work))
|
||||||
|
while work[0]:
|
||||||
|
data, status = await self._recv(reader, writer)
|
||||||
|
if not status:
|
||||||
|
work[0] = False
|
||||||
|
writer.close()
|
||||||
|
break
|
||||||
|
code = data[:2]
|
||||||
|
message = data[data.find(":") + 1:]
|
||||||
|
match code:
|
||||||
|
case "Cs":
|
||||||
|
match message:
|
||||||
|
case "hello":
|
||||||
|
await self._send("Os:hello", writer)
|
||||||
|
case _:
|
||||||
|
self.log.warning(f"Unknown command: {data}")
|
||||||
|
case "C:":
|
||||||
|
self.log.info(f"Called the command: {message}")
|
||||||
|
if message == "exit":
|
||||||
|
self.log.info("Connection closed.")
|
||||||
|
writer.close()
|
||||||
|
work[0] = False
|
||||||
|
break
|
||||||
|
|
||||||
|
case "Os":
|
||||||
|
match message:
|
||||||
|
case "hello":
|
||||||
|
pass
|
||||||
|
# await self._send("Cs:hello", writer)
|
||||||
|
case _:
|
||||||
|
self.log.warning(f"Unknown command: {data}")
|
||||||
|
case "O:":
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
self.log.warning(f"Unknown command: {data}")
|
||||||
|
|
||||||
|
await t
|
||||||
|
|
||||||
|
async def handle_connect(self, reader, writer):
|
||||||
|
try:
|
||||||
|
hello, status = await self._recv(reader, writer)
|
||||||
|
if hello == "hello" and status:
|
||||||
|
await self._send("hello", writer)
|
||||||
|
await self.while_handle(reader, writer)
|
||||||
|
else:
|
||||||
|
await self._send("E:Wrong password", writer, False)
|
||||||
|
writer.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error("Error while handling connection...")
|
||||||
|
self.log.exception(e)
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
self.log.info("TODO: RCON")
|
self.run = True
|
||||||
|
try:
|
||||||
async def stop(self):
|
server = await asyncio.start_server(self.handle_connect, self.host, self.port, backlog=5)
|
||||||
pass
|
self.log.info(f"RCON server started on {server.sockets[0].getsockname()!r}")
|
||||||
|
async with server:
|
||||||
|
await server.serve_forever()
|
||||||
|
except OSError as e:
|
||||||
|
self.log.error(i18n.core_bind_failed.format(e))
|
||||||
|
raise e
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(f"Error: {e}")
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.run = False
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
# Licence: FPA
|
# Licence: FPA
|
||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import builtins
|
import builtins
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from typing import AnyStr
|
from typing import AnyStr
|
||||||
|
|
||||||
@@ -14,11 +15,14 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
|
|||||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||||
from prompt_toolkit.completion import NestedCompleter
|
from prompt_toolkit.completion import NestedCompleter
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
|
try:
|
||||||
|
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
|
||||||
|
except AssertionError:
|
||||||
|
class NoConsoleScreenBufferError(Exception): ...
|
||||||
from prompt_toolkit.patch_stdout import patch_stdout
|
from prompt_toolkit.patch_stdout import patch_stdout
|
||||||
|
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
from modules.ConsoleSystem import RCON
|
from modules.ConsoleSystem.RCON import RCONSystem
|
||||||
|
|
||||||
|
|
||||||
class Console:
|
class Console:
|
||||||
@@ -46,7 +50,7 @@ class Console:
|
|||||||
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
|
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
|
||||||
custom_completer={"help": {"--raw": None}})
|
custom_completer={"help": {"--raw": None}})
|
||||||
self.completer = NestedCompleter.from_nested_dict(self.__alias)
|
self.completer = NestedCompleter.from_nested_dict(self.__alias)
|
||||||
rcon = RCON
|
rcon = RCONSystem
|
||||||
rcon.console = self
|
rcon.console = self
|
||||||
self.rcon = rcon
|
self.rcon = rcon
|
||||||
|
|
||||||
@@ -242,7 +246,11 @@ class Console:
|
|||||||
self.log(text)
|
self.log(text)
|
||||||
command_object = self.__func.get(cmd)
|
command_object = self.__func.get(cmd)
|
||||||
if command_object:
|
if command_object:
|
||||||
out = command_object['f'](cmd_s[1:])
|
func = command_object['f']
|
||||||
|
if inspect.iscoroutinefunction(func):
|
||||||
|
out = await func(cmd_s[1:])
|
||||||
|
else:
|
||||||
|
out = func(cmd_s[1:])
|
||||||
if out:
|
if out:
|
||||||
self.log(out)
|
self.log(out)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ class EventsSystem:
|
|||||||
self.log.debug("used builtins_hook")
|
self.log.debug("used builtins_hook")
|
||||||
builtins.ev = self
|
builtins.ev = self
|
||||||
|
|
||||||
|
def is_event(self, event_name):
|
||||||
|
return (event_name in self.__async_events.keys() or
|
||||||
|
event_name in self.__events.keys() or
|
||||||
|
event_name in self.__lua_events.keys())
|
||||||
|
|
||||||
def register_event(self, event_name, event_func, async_event=False, lua=None):
|
def register_event(self, event_name, event_func, async_event=False, lua=None):
|
||||||
self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', "
|
self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', "
|
||||||
f"async_event={async_event}, lua_event={lua}):")
|
f"async_event={async_event}, lua_event={lua}):")
|
||||||
@@ -112,7 +117,7 @@ class EventsSystem:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
|
self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
|
||||||
self.log.exception(e)
|
self.log.exception(e)
|
||||||
else:
|
elif not self.is_event(event_name):
|
||||||
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_event()"))
|
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_event()"))
|
||||||
|
|
||||||
return funcs_data
|
return funcs_data
|
||||||
@@ -130,13 +135,13 @@ class EventsSystem:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
|
self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
|
||||||
self.log.exception(e)
|
self.log.exception(e)
|
||||||
else:
|
elif not self.is_event(event_name):
|
||||||
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_async_event()"))
|
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_async_event()"))
|
||||||
|
|
||||||
return funcs_data
|
return funcs_data
|
||||||
|
|
||||||
def call_lua_event(self, event_name, *args):
|
def call_lua_event(self, event_name, *args):
|
||||||
self.log.debug(f"Calling lua event: '{event_name}'")
|
self.log.debug(f"Calling lua event: '{event_name} ({args})'")
|
||||||
funcs_data = []
|
funcs_data = []
|
||||||
if event_name in self.__lua_events.keys():
|
if event_name in self.__lua_events.keys():
|
||||||
for data in self.__lua_events[event_name]:
|
for data in self.__lua_events[event_name]:
|
||||||
@@ -151,7 +156,7 @@ class EventsSystem:
|
|||||||
funcs_data.append(fd)
|
funcs_data.append(fd)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}"))
|
self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}"))
|
||||||
else:
|
elif not self.is_event(event_name):
|
||||||
self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()"))
|
self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()"))
|
||||||
|
|
||||||
return funcs_data
|
return funcs_data
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class MP:
|
|||||||
|
|
||||||
def TriggerLocalEvent(self, event_name, *args):
|
def TriggerLocalEvent(self, event_name, *args):
|
||||||
self.log.debug("request TriggerLocalEvent()")
|
self.log.debug("request TriggerLocalEvent()")
|
||||||
self.log.debug(f"Calling local lua event: '{event_name}'")
|
self.log.debug(f"Calling local lua event: '{event_name}{args}'")
|
||||||
funcs_data = []
|
funcs_data = []
|
||||||
if event_name in self._local_events.keys():
|
if event_name in self._local_events.keys():
|
||||||
for func_name in self._local_events[event_name]:
|
for func_name in self._local_events[event_name]:
|
||||||
@@ -157,7 +157,7 @@ class MP:
|
|||||||
to_all = False
|
to_all = False
|
||||||
if player_id < 0:
|
if player_id < 0:
|
||||||
to_all = True
|
to_all = True
|
||||||
client = client[0]
|
client = client[0] if len(client) > 0 else None
|
||||||
if client and event_name and data:
|
if client and event_name and data:
|
||||||
t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all))
|
t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all))
|
||||||
self.tasks.append(t)
|
self.tasks.append(t)
|
||||||
@@ -168,7 +168,7 @@ class MP:
|
|||||||
return False, "Can't found event_name or data"
|
return False, "Can't found event_name or data"
|
||||||
|
|
||||||
def TriggerClientEventJson(self, player_id, event_name, data):
|
def TriggerClientEventJson(self, player_id, event_name, data):
|
||||||
self.log.debug("request TriggerClientEventJson()")
|
self.log.debug(f"request TriggerClientEventJson({player_id, event_name, data})")
|
||||||
data = self._lua.globals().Util.JsonEncode(data)
|
data = self._lua.globals().Util.JsonEncode(data)
|
||||||
self.TriggerClientEvent(player_id, event_name, data)
|
self.TriggerClientEvent(player_id, event_name, data)
|
||||||
|
|
||||||
@@ -232,8 +232,8 @@ class MP:
|
|||||||
|
|
||||||
def GetPlayers(self):
|
def GetPlayers(self):
|
||||||
self.log.debug("request GetPlayers()")
|
self.log.debug("request GetPlayers()")
|
||||||
clients = ev.call_event("_get_players", cid=-1)
|
clients = ev.call_event("_get_player", cid=-1)[0]
|
||||||
return self._lua.table_from(clients)
|
return self._lua.table_from([i.nick for i in clients])
|
||||||
|
|
||||||
def IsPlayerGuest(self, player_id) -> bool:
|
def IsPlayerGuest(self, player_id) -> bool:
|
||||||
self.log.debug("request IsPlayerGuest()")
|
self.log.debug("request IsPlayerGuest()")
|
||||||
@@ -312,12 +312,18 @@ class Util:
|
|||||||
return {k: v for k, v in new_dict.items() if v is not None}
|
return {k: v for k, v in new_dict.items() if v is not None}
|
||||||
|
|
||||||
def JsonEncode(self, table):
|
def JsonEncode(self, table):
|
||||||
self.log.debug("requesting JsonEncode()")
|
data = {}
|
||||||
if all(isinstance(k, int) for k in table.keys()):
|
try:
|
||||||
data = self._recursive_list_encode(table)
|
self.log.debug("requesting JsonEncode()")
|
||||||
else:
|
if all(isinstance(k, int) for k in table.keys()):
|
||||||
data = self._recursive_dict_encode(table)
|
data = self._recursive_list_encode(table)
|
||||||
return json.dumps(data)
|
else:
|
||||||
|
data = self._recursive_dict_encode(table)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception(e)
|
||||||
|
data = json.dumps(data)
|
||||||
|
self.log.debug(f"Encoded: {data}")
|
||||||
|
return data
|
||||||
|
|
||||||
def JsonDecode(self, string):
|
def JsonDecode(self, string):
|
||||||
self.log.debug("requesting JsonDecode()")
|
self.log.debug("requesting JsonDecode()")
|
||||||
|
|||||||
+95
-119
@@ -8,6 +8,7 @@
|
|||||||
# (c) kuitoi.su 2023
|
# (c) kuitoi.su 2023
|
||||||
import builtins
|
import builtins
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
|
||||||
from core.utils import get_logger
|
from core.utils import get_logger
|
||||||
@@ -25,135 +26,110 @@ class i18n:
|
|||||||
|
|
||||||
class MultiLanguage:
|
class MultiLanguage:
|
||||||
|
|
||||||
def __init__(self, language: str = None, files_dir="modules/i18n/files/", encoding=None):
|
def __init__(self, language: str = None, files_dir="translates/", encoding=None):
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = config.enc
|
encoding = config.enc
|
||||||
if language is None:
|
if language is None:
|
||||||
language = "en"
|
language = "en"
|
||||||
self.__data = {}
|
self.__data = {
|
||||||
|
"hello": "Hello from KuiToi-Server!",
|
||||||
|
"config_path": "Use {} to configure.",
|
||||||
|
"init_ok": "Initialization completed.",
|
||||||
|
"start": "Server started!",
|
||||||
|
"stop": "Server stopped!",
|
||||||
|
"auth_need_key": "BeamMP key is required to run!",
|
||||||
|
"auth_empty_key": "BeamMP key is empty!",
|
||||||
|
"auth_cannot_open_browser": "Failed to open browser: {}",
|
||||||
|
"auth_use_link": "Use this link: {}",
|
||||||
|
"GUI_yes": "Yes",
|
||||||
|
"GUI_no": "No",
|
||||||
|
"GUI_ok": "OK",
|
||||||
|
"GUI_cancel": "Cancel",
|
||||||
|
"GUI_need_key_message": "BeamMP key is required to run!\nDo you want to open the link in your browser to get the key?",
|
||||||
|
"GUI_enter_key_message": "Please enter the key:",
|
||||||
|
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
|
||||||
|
"web_start": "WebAPI started on {} (CTRL+C to stop)",
|
||||||
|
"core_bind_failed": "Failed to bind port. Error: {}",
|
||||||
|
"core_direct_mode": "Server started in direct connection mode.",
|
||||||
|
"core_auth_server_error": "Received invalid response from BeamMP authentication server.",
|
||||||
|
"core_auth_server_refused": "The BeamMP authentication server refused your key. Reason: {}",
|
||||||
|
"core_auth_server_refused_no_reason": "The BeamMP authentication server did not provide a reason.",
|
||||||
|
"core_auth_server_refused_direct_node": "The server is still running, but in direct connection mode.",
|
||||||
|
"core_auth_server_no_response": "Failed to authenticate the server.",
|
||||||
|
"core_mods_loaded": "Loaded {} mods. {}Mb",
|
||||||
|
"core_identifying_connection": "Processing new connection...",
|
||||||
|
"core_player_kick_outdated": "Incorrect version of BeamMP.",
|
||||||
|
"core_player_kick_bad_key": "Invalid key passed!",
|
||||||
|
"core_player_kick_invalid_key": "Invalid key! Please restart your game.",
|
||||||
|
"core_player_kick_auth_server_fail": "BeamMP authentication server failed! Please try to connect again in 5 minutes.",
|
||||||
|
"core_player_kick_stale": "Stale client. (Replaced by new connection)",
|
||||||
|
"core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.",
|
||||||
|
"core_player_kick_server_full": "Server is full.",
|
||||||
|
"core_player_set_id": "Player set ID {}",
|
||||||
|
"core_identifying_okay": "Successful login.",
|
||||||
|
"game_welcome_message": "Welcome {}!",
|
||||||
|
"client_mod_request": "Requested mod: {}",
|
||||||
|
"client_mod_sent": "Mod sent: Size: {}mb, Speed: {}Mb/s ({}sec)",
|
||||||
|
"client_mod_sent_limit": " (limit {}Mb/s)",
|
||||||
|
"client_mod_sent_error": "Error sending mod: {}",
|
||||||
|
"client_sync_time": "Sync time {}sec.",
|
||||||
|
"client_kicked": "Kicked for reason: \"{}\"",
|
||||||
|
"client_event_invalid_data": "Invalid data returned from event: {}",
|
||||||
|
"client_player_disconnected": "Left the server. Playtime: {} min",
|
||||||
|
"events_not_callable": "Unable to add event \"{}\". Use \"{}\" instead. Skipping...",
|
||||||
|
"events_not_found": "Event \"{}\" is not registered. Maybe {}? Skipping...",
|
||||||
|
"events_calling_error": "Error calling \"{}\" in function \"{}\".",
|
||||||
|
"events_lua_function_not_found": "Unable to call {}lua event - \"{}\" not found.",
|
||||||
|
"events_lua_local": "local ",
|
||||||
|
"events_lua_calling_error": "Error: \"{}\" - calling lua event \"{}\", function: \"{}\", arguments: {}",
|
||||||
|
"plugins_not_found_load": "Function \"def load():\" not found.",
|
||||||
|
"plugins_not_found_start": "Function \"def start():\" not found.",
|
||||||
|
"plugins_not_found_unload": "Function \"def unload():\" not found.",
|
||||||
|
"plugins_kt_invalid": "\"kt\" variable does not belong to the KuiToi class.",
|
||||||
|
"plugins_invalid": "Plugin \"{}\" cannot be run in KuiToi.",
|
||||||
|
"plugins_error_loading": "An error occurred while loading the plugin {}: {}",
|
||||||
|
"plugins_lua_enabled": "You have enabled Lua plugin support.",
|
||||||
|
"plugins_lua_nuances_warning": "There are some nuances when working with Kuiti. If you have a suggestion for their solution, and it is related to KuiToi, please contact the developer.",
|
||||||
|
"plugins_lua_legacy_config_create_warning": "Some BeamMP plugins require a properly configured ServerConfig.toml file to function.",
|
||||||
|
"plugins_lua_legacy_config_create": "Creating it.",
|
||||||
|
"plugins_lua_unload": "Stopping Lua plugin: {}",
|
||||||
|
"man_message_man": "man - Shows the help page for COMMAND.\nUsage: man COMMAND",
|
||||||
|
"help_message_man": "Shows the 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!",
|
||||||
|
"man_message_help": "help - Shows the names and brief descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands, with a brief description for each command.",
|
||||||
|
"help_message_help": "Shows the names and brief descriptions of commands",
|
||||||
|
"help_command": "Command",
|
||||||
|
"help_message": "Text",
|
||||||
|
"help_message_not_found": "No text found",
|
||||||
|
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||||
|
"help_message_stop": "Stops the server.",
|
||||||
|
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
||||||
|
"help_message_exit": "Stops the server."
|
||||||
|
}
|
||||||
|
self.__en_data = self.__data.copy()
|
||||||
self.__i18n = None
|
self.__i18n = None
|
||||||
self.__encoding = encoding
|
self.__encoding = encoding
|
||||||
self.language = language
|
self.language = language
|
||||||
|
if not os.path.exists(files_dir):
|
||||||
|
os.makedirs(files_dir)
|
||||||
|
if not os.path.exists(files_dir + "en.json"):
|
||||||
|
with open(files_dir + "en.json", "w") as f:
|
||||||
|
f.write(json.dumps(self.__en_data, indent=2))
|
||||||
self.files_dir = files_dir
|
self.files_dir = files_dir
|
||||||
self.log = get_logger("i18n")
|
self.log = get_logger("i18n")
|
||||||
|
self.fi = False
|
||||||
self.set_language(language)
|
self.set_language(language)
|
||||||
|
|
||||||
def set_language(self, language):
|
def set_language(self, language="en"):
|
||||||
if language is None:
|
if self.language == language and self.fi:
|
||||||
language = "en"
|
return
|
||||||
self.log.debug(f"set_language({language})")
|
|
||||||
self.language = language
|
|
||||||
if language != "en":
|
|
||||||
self.open_file()
|
|
||||||
else:
|
else:
|
||||||
# noinspection PyDictDuplicateKeys
|
self.fi = True
|
||||||
self.__data = {
|
self.log.debug(f"set_language({language})")
|
||||||
"": "Basic phases",
|
self.language = language
|
||||||
"hello": "Hello from KuiToi-Server!",
|
self.open_file()
|
||||||
"config_path": "Use {} to configure.",
|
|
||||||
"init_ok": "Initialization completed.",
|
|
||||||
"start": "Server started!",
|
|
||||||
"stop": "Server stopped!",
|
|
||||||
|
|
||||||
"": "Server auth",
|
|
||||||
"auth_need_key": "BeamMP key is required to run!",
|
|
||||||
"auth_empty_key": "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": "BeamMP key is required to run!\nDo you want to open the link in your browser to get 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 started on {} (CTRL+C to stop)",
|
|
||||||
|
|
||||||
"": "Core phrases",
|
|
||||||
"core_bind_failed": "Failed to bind port. Error: {}",
|
|
||||||
"core_direct_mode": "Server started in direct connection mode.",
|
|
||||||
"core_auth_server_error": "Received invalid response from BeamMP authentication server.",
|
|
||||||
"core_auth_server_refused": "The BeamMP authentication server refused your key. Reason: {}",
|
|
||||||
"core_auth_server_refused_no_reason": "The BeamMP authentication server did not provide a reason.",
|
|
||||||
"core_auth_server_refused_direct_node": "The server is still running, but in direct connection mode.",
|
|
||||||
"core_auth_server_no_response": "Failed to authenticate the server.",
|
|
||||||
"core_mods_loaded": "Loaded {} mods. {}Mb",
|
|
||||||
"core_identifying_connection": "Processing new connection...",
|
|
||||||
"core_player_kick_outdated": "Incorrect version of BeamMP.",
|
|
||||||
"core_player_kick_bad_key": "Invalid key passed!",
|
|
||||||
"core_player_kick_invalid_key": "Invalid key! Please restart your game.",
|
|
||||||
"core_player_kick_auth_server_fail": "BeamMP authentication server failed! Please try to connect again in 5 minutes.",
|
|
||||||
"core_player_kick_stale": "Stale client. (Replaced by new connection)",
|
|
||||||
"core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.",
|
|
||||||
"core_player_kick_server_full": "Server is full.",
|
|
||||||
"core_player_set_id": "Player set ID {}",
|
|
||||||
"core_identifying_okay": "Successful login.",
|
|
||||||
|
|
||||||
"": "In-game phrases",
|
|
||||||
"game_welcome_message": "Welcome {}!",
|
|
||||||
|
|
||||||
"": "Client class phrases",
|
|
||||||
"client_mod_request": "Requested mod: {}",
|
|
||||||
"client_mod_sent": "Mod sent: Size: {}mb, Speed: {}Mb/s ({}sec)",
|
|
||||||
"client_mod_sent_limit": " (limit {}Mb/s)",
|
|
||||||
"client_mod_sent_error": "Error sending mod: {}",
|
|
||||||
"client_sync_time": "Sync time {}sec.",
|
|
||||||
"client_kicked": "Kicked for reason: \"{}\"",
|
|
||||||
"client_event_invalid_data": "Invalid data returned from event: {}",
|
|
||||||
"client_player_disconnected": "Left the server. Playtime: {} min",
|
|
||||||
|
|
||||||
"": "Events system",
|
|
||||||
|
|
||||||
"events_not_callable": "Unable to add event \"{}\". Use \"{}\" instead. Skipping...",
|
|
||||||
"events_not_found": "Event \"{}\" is not registered. Maybe {}? Skipping...",
|
|
||||||
"events_calling_error": "Error calling \"{}\" in function \"{}\".",
|
|
||||||
"events_lua_function_not_found": "Unable to call {}lua event - \"{}\" not found.",
|
|
||||||
"events_lua_local": "local ",
|
|
||||||
"events_lua_calling_error": "Error: \"{}\" - calling lua event \"{}\", function: \"{}\", arguments: {}",
|
|
||||||
|
|
||||||
"": "Plugins loader",
|
|
||||||
|
|
||||||
"plugins_not_found_load": "Function \"def load():\" not found.",
|
|
||||||
"plugins_not_found_start": "Function \"def start():\" not found.",
|
|
||||||
"plugins_not_found_unload": "Function \"def unload():\" not found.",
|
|
||||||
"plugins_kt_invalid": "\"kt\" variable does not belong to the KuiToi class.",
|
|
||||||
"plugins_invalid": "Plugin \"{}\" cannot be run in KuiToi.",
|
|
||||||
"plugins_error_loading": "An error occurred while loading the plugin {}: {}",
|
|
||||||
|
|
||||||
"": "Lua plugins loader",
|
|
||||||
|
|
||||||
"plugins_lua_enabled": "You have enabled Lua plugin support.",
|
|
||||||
"plugins_lua_nuances_warning": "There are some nuances when working with Kuiti. If you have a suggestion for their solution, and it is related to KuiToi, please contact the developer.",
|
|
||||||
"plugins_lua_legacy_config_create_warning": "Some BeamMP plugins require a properly configured ServerConfig.toml file to function.",
|
|
||||||
"plugins_lua_legacy_config_create": "Creating it.",
|
|
||||||
"plugins_lua_unload": "Stopping Lua plugin: {}",
|
|
||||||
|
|
||||||
"": "Command: man",
|
|
||||||
"man_message_man": "man - Shows the help page for COMMAND.\nUsage: man COMMAND",
|
|
||||||
"help_message_man": "Shows the 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 - Shows the names and brief descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands, with a brief description for each command.",
|
|
||||||
"help_message_help": "Shows the names and brief descriptions of commands",
|
|
||||||
"help_command": "Command",
|
|
||||||
"help_message": "Text",
|
|
||||||
"help_message_not_found": "No text found",
|
|
||||||
|
|
||||||
"": "Command: stop",
|
|
||||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
|
||||||
"help_message_stop": "Stops the server.",
|
|
||||||
|
|
||||||
"": "Command: exit",
|
|
||||||
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
|
||||||
"help_message_exit": "Stops the server."
|
|
||||||
}
|
|
||||||
self.__i18n = i18n(self.__data)
|
self.__i18n = i18n(self.__data)
|
||||||
|
|
||||||
def open_file(self):
|
def open_file(self):
|
||||||
@@ -165,9 +141,9 @@ class MultiLanguage:
|
|||||||
return
|
return
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
f"Localisation \"{self.language}.json\" have JsonDecodeError. Using default localisation: en.")
|
f"Localisation \"{file}\" have JsonDecodeError. Using default localisation: en.")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.log.warning(f"Localisation \"{self.language}.json\" not found; Using default localisation: en.")
|
self.log.warning(f"Localisation \"{file}\" not found; Using default localisation: en.")
|
||||||
self.set_language("en")
|
self.set_language("en")
|
||||||
|
|
||||||
def builtins_hook(self) -> None:
|
def builtins_hook(self) -> None:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
# pip install pyinstaller-versionfile
|
||||||
|
# create-version-file win-metadata.yml --outfile win-ver_info.txt
|
||||||
|
Version: 0.4.5
|
||||||
|
CompanyName: KuiToi
|
||||||
|
FileDescription: KuiToi Server
|
||||||
|
InternalName: KuiToi Server
|
||||||
|
LegalCopyright: © Maxim Khomutov
|
||||||
|
OriginalFilename: KuiToi-Server.exe
|
||||||
|
ProductName: KuiToi Server
|
||||||
|
Translation:
|
||||||
|
# ru-RU
|
||||||
|
- langID: 1049
|
||||||
|
charsetID: 1251
|
||||||
|
# en-US
|
||||||
|
- langID: 1033
|
||||||
|
charsetID: 1251
|
||||||
|
# zh-CN
|
||||||
|
- langID: 2052
|
||||||
|
charsetID: 950
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"version": "auto-py-to-exe-configuration_v1",
|
||||||
|
"pyinstallerOptions": [
|
||||||
|
{
|
||||||
|
"optionDest": "noconfirm",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "filenames",
|
||||||
|
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/src/main.py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "onefile",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "console",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "icon_file",
|
||||||
|
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/win-logo.ico"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "name",
|
||||||
|
"value": "KuiToi-Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "ascii",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "clean_build",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "strip",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "noupx",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "disable_windowed_traceback",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "version_file",
|
||||||
|
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/win-ver_info.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "embed_manifest",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "uac_admin",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "uac_uiaccess",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "win_private_assemblies",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "win_no_prefer_redirects",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "bootloader_ignore_signals",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"optionDest": "argv_emulation",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonPyinstallerOptions": {
|
||||||
|
"increaseRecursionLimit": true,
|
||||||
|
"manualArguments": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user