mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2026-04-24 00:56:36 +00:00
Compare commits
39 Commits
0.4.2-beta
...
cb6adde7c2
| Author | SHA1 | Date | |
|---|---|---|---|
| cb6adde7c2 | |||
| 422dd35a8f | |||
| 2c2bd1cb4a | |||
| 7eba3d5877 | |||
| abbd64184e | |||
| e5dd63579b | |||
| ef286b7e03 | |||
| 3a42fa13e7 | |||
| cdec0b9949 | |||
| de91d075b4 | |||
| a7c02e0b52 | |||
| 6dd3de63a9 | |||
| 7e0c50a04e | |||
| f1ab07d49a | |||
| cdeacc16bf | |||
| 719e705bab | |||
| 21dd23cb55 | |||
| 9295ed2b7a | |||
| 7466f987ac | |||
| d29bb9de98 | |||
| 189bd0cc30 | |||
| 53dae25fcf | |||
| e241a7da4c | |||
| 13be12a7a1 | |||
| e348ffecc3 | |||
| 336aa31732 | |||
| d8c667ff51 | |||
| 658f9ed9c6 | |||
| c1cb8dcdba | |||
| f52c73ab76 | |||
| 6d01f0ef8d | |||
| eea03c835f | |||
| f28a783f7e | |||
| dbb27ff6d5 | |||
| e621c8dc7c | |||
| 76b568c248 | |||
| 4c7f5ac14b | |||
| 28386a0300 | |||
| 8140b3347a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -140,3 +140,6 @@ dmypy.json
|
||||
logs/
|
||||
*.yml
|
||||
*.toml
|
||||
|
||||
/win-ver_info.txt
|
||||
/output/
|
||||
46
README.md
46
README.md
@@ -4,9 +4,16 @@
|
||||
**_[Status: Beta]_** \
|
||||
BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
|
||||
Why did I decide to write my own kernel from scratch?\
|
||||
I didn't like writing plugins in Lua after using Python; it was very inconvenient, there were always some nuances, which ultimately led me to create KuiToi!
|
||||
|
||||
**Our site**: [kuitoi.su](https://kuitoi.su) (WIP)\
|
||||
**Our forum**: [forum.kuitoi.su](https://forum.kuitoi.su) (WIP)\
|
||||
**Our discord**: [KuiToi](https://discord.gg/BAcgaAmdkJ)
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] Server core
|
||||
- [x] Server core:
|
||||
- [x] BeamMP System:
|
||||
- [x] Private access (Without key, Direct connect)
|
||||
- [x] Public access (With key, listing in Launcher)
|
||||
@@ -19,25 +26,21 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] Chat
|
||||
- [x] Players online counter
|
||||
- [x] Packets handled (Recursive finding second packet)
|
||||
- [ ] Client events
|
||||
- [x] Client events
|
||||
- [x] Car synchronizations:
|
||||
- [x] State packets
|
||||
- [x] Spawn cars
|
||||
- [x] Delete cars
|
||||
- [x] Edit cars
|
||||
- [x] Reset cars
|
||||
- [x] "ABG:" (compressed data)
|
||||
- [x] "ABG": (compressed data)
|
||||
- [x] Decompress data
|
||||
- [x] Compress data
|
||||
- [x] UDP Server part:
|
||||
- [x] Ping
|
||||
- [x] Position synchronizations
|
||||
- [x] Additional:
|
||||
- [ ] KuiToi System
|
||||
- [ ] Servers counter
|
||||
- [ ] Players counter
|
||||
- [ ] Etc.
|
||||
- [x] Logger
|
||||
- [x] Logger:
|
||||
- [x] Just logging
|
||||
- [x] Log in file
|
||||
- [x] Log history (.1.log, .2.log, ...)
|
||||
@@ -45,13 +48,17 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] Tabulation
|
||||
- [x] History
|
||||
- [x] Autocomplete
|
||||
- [x] Events System
|
||||
- [x] Events System:
|
||||
- [x] Call events
|
||||
- [x] Create custom events
|
||||
- [x] Return from events
|
||||
- [x] Async support
|
||||
- [ ] Add all events
|
||||
- [x] Plugins supports
|
||||
- [x] Add all events
|
||||
- [x] MultiLanguage: (i18n support)
|
||||
- [x] Core
|
||||
- [x] Console
|
||||
- [x] WebAPI
|
||||
- [x] Plugins supports:
|
||||
- [x] Python part:
|
||||
- [x] Load Python plugins
|
||||
- [x] Async support
|
||||
@@ -62,16 +69,21 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] MP Class
|
||||
- [x] Util class
|
||||
- [x] FS class
|
||||
- [x] MultiLanguage (i18n support)
|
||||
- [ ] Core
|
||||
- [x] Console
|
||||
- [x] WebAPI
|
||||
- [ ] HTTP API Server (fastapi)
|
||||
- [ ] HTTP API Server: (fastapi)
|
||||
- [x] Stop and Start with core
|
||||
- [x] Configure FastAPI logger
|
||||
- [ ] Sync with event system
|
||||
- [ ] Add methods...
|
||||
- [ ] [Documentation](./docs/)
|
||||
- [ ] RCON System:
|
||||
- [x] Serving
|
||||
- [ ] Handle commands
|
||||
- [x] Client
|
||||
- [x] AES encryption
|
||||
- [ ] KuiToi System
|
||||
- [ ] Servers counter
|
||||
- [ ] Players counter
|
||||
- [ ] Etc.
|
||||
- [ ] [Documentation](./docs)
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ KiuToi几乎完全支持BeamMP的lua插件,所有必要的方法都已经创
|
||||
#### Cobalt Essentials V1.7.5(免费,[github ↗](https://github.com/prestonelam2003/CobaltEssentials/))
|
||||
|
||||
1. 要获取`pluginPath`,需要:`debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")`,因为路径返回值中包含`@`,这破坏了插件。
|
||||
2. `cobaltSysChar`
|
||||
|
||||
### 工作原理
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ KiuToi does not support: `MP.Set()`
|
||||
#### Cobalt Essentials V1.7.5 (Free, [github](https://github.com/prestonelam2003/CobaltEssentials/))
|
||||
|
||||
1. To obtain `pluginPath`, use: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` as the path returns with `@`, which broke the plugin.
|
||||
2. `cobaltSysChar`
|
||||
|
||||
### A Little About How it Works
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#### Cobalt Essentials V1.7.5 (Бесплатный, [github](https://github.com/prestonelam2003/CobaltEssentials/))
|
||||
|
||||
1. Для получения `pluginPath` нужно: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` так как пусть возвращается с `@`, что сломало плагин.
|
||||
2. `cobaltSysChar`
|
||||
|
||||
### Немного о принципе работы
|
||||
|
||||
|
||||
@@ -7,4 +7,6 @@ starlette~=0.27.0
|
||||
pydantic~=2.0.2
|
||||
click~=8.1.4
|
||||
lupa~=2.0
|
||||
toml~=0.10.2
|
||||
toml~=0.10.2
|
||||
colorama~=0.4.6
|
||||
cryptography~=41.0.2
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.tcp_server.py
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
@@ -44,6 +44,10 @@ class Client:
|
||||
def _writer(self):
|
||||
return self.__writer
|
||||
|
||||
@property
|
||||
def alive(self):
|
||||
return self.__alive
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
return self._log
|
||||
@@ -111,8 +115,7 @@ class Client:
|
||||
if not self.__alive:
|
||||
self.log.debug(f"{self.nick}.kick('{reason}') skipped: Not alive;")
|
||||
return
|
||||
# TODO: i18n
|
||||
self.log.info(f"Kicked with reason: \"{reason}\"")
|
||||
self.log.info(i18n.client_kicked.format(reason))
|
||||
await self._send(f"K{reason}")
|
||||
self.__alive = False
|
||||
|
||||
@@ -186,8 +189,8 @@ class Client:
|
||||
await writer.drain()
|
||||
return True
|
||||
|
||||
except ConnectionError:
|
||||
self.log.debug('[TCP] Disconnected')
|
||||
except Exception as e:
|
||||
self.log.debug(f'[TCP] Disconnected: {e}')
|
||||
self.__alive = False
|
||||
await self._remove_me()
|
||||
return False
|
||||
@@ -195,7 +198,13 @@ class Client:
|
||||
async def _recv(self, one=False):
|
||||
while self.__alive:
|
||||
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)
|
||||
|
||||
@@ -220,9 +229,14 @@ class Client:
|
||||
self.__packets_queue.append(None)
|
||||
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:"
|
||||
if len(data) > len(abg) and data.startswith(abg):
|
||||
data = zlib.decompress(data[len(abg):])
|
||||
@@ -251,7 +265,7 @@ class Client:
|
||||
try:
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
self.log.debug(f"[{who}] Sent {len(data)} bytes.")
|
||||
# self.log.debug(f"[{who}] Sent {len(data)} bytes.")
|
||||
except ConnectionError:
|
||||
self.__alive = False
|
||||
self.log.debug(f"[{who}] Disconnected.")
|
||||
@@ -270,10 +284,12 @@ class Client:
|
||||
async def _sync_resources(self):
|
||||
while self.__alive:
|
||||
data = await self._recv(True)
|
||||
if data is None:
|
||||
await self._remove_me()
|
||||
break
|
||||
if data.startswith(b"f"):
|
||||
file = data[1:].decode(config.enc)
|
||||
# TODO: i18n
|
||||
self.log.info(f"Requested mode: {file!r}")
|
||||
self.log.info(i18n.client_mod_request.format(repr(file)))
|
||||
size = -1
|
||||
for mod in self.__Core.mods_list:
|
||||
if type(mod) == int:
|
||||
@@ -309,13 +325,12 @@ class Client:
|
||||
self._split_load(half_size, size, True, file, speed)
|
||||
]
|
||||
sl0, sl1 = await asyncio.gather(*uploads)
|
||||
tr = time.monotonic() - t
|
||||
tr = (time.monotonic() - t) or 0.0001
|
||||
if self.__Core.lock_upload:
|
||||
self.__Core.lock_upload = False
|
||||
# TODO: i18n
|
||||
msg = f"Mod sent: Size {round(size / MB, 3)}mb Speed {math.ceil(size / tr / MB)}Mb/s ({int(tr)}s)"
|
||||
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
|
||||
if speed:
|
||||
msg += f" of limit {int(speed * 2)}Mb/s"
|
||||
msg += i18n.client_mod_sent_limit.format(int(speed * 2))
|
||||
self.log.info(msg)
|
||||
sent = sl0 + sl1
|
||||
ok = sent == size
|
||||
@@ -323,8 +338,7 @@ class Client:
|
||||
self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}")
|
||||
if not ok:
|
||||
self.__alive = False
|
||||
# TODO: i18n
|
||||
self.log.error(f"Error while sending: {file!r}")
|
||||
self.log.error(i18n.client_mod_sent_error.format(repr(file)))
|
||||
return
|
||||
elif data.startswith(b"SR"):
|
||||
path_list = ''
|
||||
@@ -351,7 +365,7 @@ class Client:
|
||||
id_sep = s.find('-')
|
||||
if id_sep == -1:
|
||||
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
|
||||
cid = s[:id_sep]
|
||||
vid = s[id_sep + 1:]
|
||||
@@ -385,10 +399,11 @@ class Client:
|
||||
lua_data = ev.call_lua_event("onVehicleSpawn", self.cid, car_id, car_data[car_data.find("{"):])
|
||||
if 1 in lua_data:
|
||||
allow = False
|
||||
ev_data_list = ev.call_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
|
||||
ev_data_list = ev.call_event("onCarSpawn", data=car_json, car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarSpawn", data=car_json, car_id=car_id, player=self)
|
||||
ev_data_list.extend(d2)
|
||||
for ev_data in ev_data_list:
|
||||
self.log.debug(ev_data)
|
||||
# TODO: handle event onCarSpawn
|
||||
pass
|
||||
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
|
||||
@@ -435,10 +450,11 @@ class Client:
|
||||
ev.call_lua_event("onVehicleDeleted", self.cid, car_id)
|
||||
|
||||
admin_allow = False # Delete from admin, for example...
|
||||
ev_data_list = ev.call_event("onCarDelete", car=self._cars[car_id], car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarDelete", car=self._cars[car_id], car_id=car_id, player=self)
|
||||
ev_data_list = ev.call_event("onCarDelete", data=self._cars[car_id], car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarDelete", data=self._cars[car_id], car_id=car_id, player=self)
|
||||
ev_data_list.extend(d2)
|
||||
for ev_data in ev_data_list:
|
||||
self.log.debug(ev_data)
|
||||
# TODO: handle event onCarDelete
|
||||
pass
|
||||
|
||||
@@ -474,10 +490,11 @@ class Client:
|
||||
lua_data = ev.call_lua_event("onVehicleEdited", self.cid, car_id, data[data.find("{"):])
|
||||
if 1 in lua_data:
|
||||
allow = False
|
||||
ev_data_list = ev.call_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
|
||||
ev_data_list = ev.call_event("onCarEdited", data=new_car_json, car_id=car_id, player=self)
|
||||
d2 = await ev.call_async_event("onCarEdited", data=new_car_json, car_id=car_id, player=self)
|
||||
ev_data_list.extend(d2)
|
||||
for ev_data in ev_data_list:
|
||||
self.log.debug(ev_data)
|
||||
# TODO: handle event onCarEdited
|
||||
pass
|
||||
|
||||
@@ -498,6 +515,12 @@ class Client:
|
||||
else:
|
||||
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):
|
||||
cid, car_id = self._get_cid_vid(raw_data)
|
||||
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
||||
@@ -508,8 +531,8 @@ class Client:
|
||||
car_json = json.loads(raw_data[raw_data.find("{"):])
|
||||
except Exception as e:
|
||||
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {raw_data}")
|
||||
ev.call_event("onCarReset", car=car_json, car_id=car_id, player=self)
|
||||
await ev.call_async_event("onCarReset", car=car_json, car_id=car_id, player=self)
|
||||
ev.call_event("onCarReset", data=car_json, car_id=car_id, player=self)
|
||||
await ev.call_async_event("onCarReset", data=car_json, car_id=car_id, player=self)
|
||||
self.log.debug(f"Car reset: car_id={car_id}")
|
||||
else:
|
||||
self.log.debug(f"Invalid car: car_id={car_id}")
|
||||
@@ -539,23 +562,30 @@ class Client:
|
||||
|
||||
case "t": # Broken details
|
||||
self.log.debug(f"Something changed/broken: {raw_data}")
|
||||
cid, car_id = self._get_cid_vid(raw_data)
|
||||
if car_id != -1 and cid == self.cid and self._cars[car_id]:
|
||||
data = raw_data[raw_data.find("{"):]
|
||||
ev.call_event("onCarChanged", car_id=car_id, data=data)
|
||||
await ev.call_async_event("onCarChanged", car_id=car_id, data=data)
|
||||
await self._send(raw_data, to_all=True, to_self=False)
|
||||
|
||||
case "m": # Move focus car
|
||||
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]:
|
||||
self._focus_car = car_id
|
||||
data = raw_data[raw_data.find("{"):]
|
||||
ev.call_event("onCarFocusMove", car_id=car_id, data=data)
|
||||
await ev.call_async_event("onCarFocusMove", car_id=car_id, data=data)
|
||||
await self._send(raw_data, to_all=True, to_self=True)
|
||||
|
||||
async def _connected_handler(self):
|
||||
self.log.info(f"Syncing time: {round(time.monotonic() - self._connect_time, 2)}s")
|
||||
# Client connected
|
||||
ev.call_event("onPlayerJoin", player=self)
|
||||
await ev.call_async_event("onPlayerJoin", player=self)
|
||||
|
||||
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
|
||||
await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
|
||||
await self._send(f"J{i18n.game_welcome_message.format(self.nick)}", to_all=True) # Hello message
|
||||
|
||||
for client in self.__Core.clients:
|
||||
if not client:
|
||||
@@ -565,6 +595,7 @@ class Client:
|
||||
continue
|
||||
await self._send(car['packet'])
|
||||
|
||||
self.log.info(i18n.client_sync_time.format(round(time.monotonic() - self._connect_time, 2)))
|
||||
self._ready = True
|
||||
|
||||
async def _chat_handler(self, data):
|
||||
@@ -604,7 +635,7 @@ class Client:
|
||||
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
|
||||
need_send = False
|
||||
except KeyError | AttributeError:
|
||||
self.log.error(f"Returns invalid data: {ev_data}")
|
||||
self.log.error(i18n.client_event_invalid_data.format(ev_data))
|
||||
if need_send:
|
||||
if config.Options['log_chat']:
|
||||
self.log.info(f"{self.nick}: {msg}")
|
||||
@@ -620,11 +651,13 @@ class Client:
|
||||
await self._send(data, to_all=True, to_self=False)
|
||||
return
|
||||
|
||||
_bytes = False
|
||||
try:
|
||||
data = data.decode()
|
||||
except UnicodeDecodeError:
|
||||
_bytes = True
|
||||
self.log.error(f"UnicodeDecodeError: {data}")
|
||||
return
|
||||
self.log.info("Some things are skipping...")
|
||||
|
||||
# Codes: p, Z in udp_server.py
|
||||
match data[0]: # At data[0] code
|
||||
@@ -632,15 +665,31 @@ class Client:
|
||||
await self._connected_handler()
|
||||
|
||||
case "C": # Chat handler
|
||||
if _bytes:
|
||||
return
|
||||
await self._chat_handler(data)
|
||||
|
||||
case "O": # Cars handler
|
||||
if _bytes:
|
||||
return
|
||||
await self._handle_car_codes(data)
|
||||
|
||||
case "E": # Client events handler
|
||||
# TODO: Handle events from client
|
||||
pass
|
||||
|
||||
if len(data) < 2:
|
||||
self.log.debug("Tried to send an empty event, ignoring.")
|
||||
return
|
||||
if _bytes:
|
||||
sep = data.find(b":", 2)
|
||||
self.log.warning("Bytes event!")
|
||||
else:
|
||||
sep = data.find(":", 2)
|
||||
if sep == -1:
|
||||
self.log.error(f"Received event in invalid format (missing ':'), got: {data}")
|
||||
event_name = data[2:sep]
|
||||
even_data = data[sep + 1:]
|
||||
ev.call_lua_event(event_name, even_data)
|
||||
ev.call_event(event_name, data=even_data, player=self)
|
||||
await ev.call_async_event(event_name, data=even_data, player=self)
|
||||
case "N":
|
||||
await self._send(data, to_all=True, to_self=False)
|
||||
|
||||
@@ -681,8 +730,11 @@ class Client:
|
||||
ev.call_event("onPlayerDisconnect", player=self)
|
||||
await ev.call_async_event("onPlayerDisconnect", player=self)
|
||||
|
||||
# TODO: i18n
|
||||
self.log.info(f"Disconnected, online time: {round((time.monotonic() - self._connect_time) / 60, 2)}min.")
|
||||
self.log.info(
|
||||
i18n.client_player_disconnected.format(
|
||||
round((time.monotonic() - self._connect_time) / 60, 2)
|
||||
)
|
||||
)
|
||||
self.__Core.clients[self.cid] = None
|
||||
del self.__Core.clients_by_id[self.cid]
|
||||
del self.__Core.clients_by_nick[self.nick]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.tcp_server.py
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
@@ -35,13 +35,15 @@ class Client:
|
||||
self._ready = False
|
||||
self._focus_car = -1
|
||||
self._identifiers = []
|
||||
self._cars: List[Optional[Dict[str, int]]] = []
|
||||
self._cars: List[Union[Dict[str, Union[str, bool, Dict[str, Union[str, List[int], float]]]], None]] = []
|
||||
self._snowman: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
|
||||
self._last_position = {}
|
||||
async def __gracefully_kick(self): ...
|
||||
@property
|
||||
def _writer(self) -> StreamWriter: ...
|
||||
@property
|
||||
def alive(self) -> bool: ...
|
||||
@property
|
||||
def log(self) -> Logger: ...
|
||||
@property
|
||||
def addr(self) -> Tuple[str, int]: ...
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.__init__.py
|
||||
# Written by: SantaSpeen
|
||||
# Version 1.3
|
||||
# Core version: 0.4.1
|
||||
# Version 1.4
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
|
||||
@@ -10,8 +10,8 @@
|
||||
__title__ = 'KuiToi-Server'
|
||||
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
||||
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
||||
__version__ = '0.4.1'
|
||||
__build__ = 1925 # Я это считаю лог файлами
|
||||
__version__ = '0.4.3'
|
||||
__build__ = 2125 # Я это считаю лог файлами
|
||||
__author__ = 'SantaSpeen'
|
||||
__author_email__ = 'admin@kuitoi.su'
|
||||
__license__ = "FPA"
|
||||
@@ -30,7 +30,7 @@ from modules import ConfigProvider, EventsSystem
|
||||
from modules import Console
|
||||
from modules import MultiLanguage
|
||||
|
||||
args = parser.parse_args()
|
||||
args, _ = parser.parse_known_args()
|
||||
if args.version:
|
||||
print(f"{__title__}:\n\tVersion: {__version__}\n\tBuild: {__build__}")
|
||||
exit(0)
|
||||
@@ -102,7 +102,7 @@ if not config.Auth['private'] and not config.Auth['key']:
|
||||
log.debug("Initializing console...")
|
||||
console = Console()
|
||||
console.builtins_hook()
|
||||
# console.logger_hook()
|
||||
console.logger_hook()
|
||||
console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop)
|
||||
console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit)
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.core.py
|
||||
# Written by: SantaSpeen
|
||||
# Version: 0.4.1
|
||||
# Version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
import aiohttp
|
||||
import uvicorn
|
||||
|
||||
from core import utils
|
||||
from core import utils, __version__
|
||||
from core.Client import Client
|
||||
from core.tcp_server import TCPServer
|
||||
from core.udp_server import UDPServer
|
||||
@@ -26,6 +28,7 @@ class Core:
|
||||
def __init__(self):
|
||||
self.log = utils.get_logger("core")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.start_time = time.monotonic()
|
||||
self.run = False
|
||||
self.direct = False
|
||||
self.clients = []
|
||||
@@ -111,6 +114,8 @@ class Core:
|
||||
if not client.ready:
|
||||
client.is_disconnected()
|
||||
continue
|
||||
if not client.alive:
|
||||
await client.kick("You are not alive!")
|
||||
await client._send(ca)
|
||||
except Exception as e:
|
||||
self.log.error("Error in check_alive.")
|
||||
@@ -142,8 +147,7 @@ class Core:
|
||||
async def heartbeat(self, test=False):
|
||||
if config.Auth["private"] or self.direct:
|
||||
if test:
|
||||
# TODO: i18n
|
||||
self.log.info(f"Server runnig in Direct connect mode.")
|
||||
self.log.info(i18n.core_direct_mode)
|
||||
self.direct = True
|
||||
return
|
||||
|
||||
@@ -184,31 +188,30 @@ class Core:
|
||||
if not (body.get("status") is not None and
|
||||
body.get("code") is not None and
|
||||
body.get("msg") is not None):
|
||||
self.log.error("Missing/invalid json members in backend response")
|
||||
raise KeyboardInterrupt
|
||||
self.log.error(i18n.core_auth_server_error)
|
||||
return
|
||||
|
||||
status = body.get("status")
|
||||
msg = body.get("msg")
|
||||
if status == "2000":
|
||||
if test:
|
||||
# TODO: i18n
|
||||
self.log.info(f"Authenticated! {msg}")
|
||||
self.log.debug(f"Authenticated! {msg}")
|
||||
elif status == "200":
|
||||
if test:
|
||||
self.log.info(f"Resumed authenticated session. {msg}")
|
||||
self.log.debug(f"Resumed authenticated session. {msg}")
|
||||
else:
|
||||
self.log.debug(f"Auth: data {data}")
|
||||
self.log.debug(f"Auth: code {code}, body {body}")
|
||||
self.log.error(f"Backend REFUSED the auth key. Reason: "
|
||||
f"{msg or 'Backend did not provide a reason'}")
|
||||
self.log.info(f"Server still runnig, but only in Direct connect mode.")
|
||||
|
||||
self.log.error(i18n.core_auth_server_refused.format(
|
||||
msg or i18n.core_auth_server_refused_no_reason))
|
||||
self.log.info(i18n.core_auth_server_refused_direct_node)
|
||||
self.direct = True
|
||||
else:
|
||||
self.direct = True
|
||||
if test:
|
||||
# TODO: i18n
|
||||
self.log.error("Cannot authenticate server.")
|
||||
self.log.info(f"Server still runnig, but only in Direct connect mode.")
|
||||
self.log.error(i18n.core_auth_server_no_response)
|
||||
self.log.info(i18n.core_auth_server_refused_direct_node)
|
||||
# if not config.Auth['private']:
|
||||
# raise KeyboardInterrupt
|
||||
|
||||
@@ -219,6 +222,22 @@ class Core:
|
||||
except Exception as 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):
|
||||
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
||||
self.udp = self.udp(self, self.server_ip, self.server_port)
|
||||
@@ -226,6 +245,7 @@ class Core:
|
||||
"list",
|
||||
lambda x: f"Players list: {self.get_clients_list(True)}"
|
||||
)
|
||||
console.add_command("kick", self.kick_cmd)
|
||||
|
||||
pl_dir = "plugins"
|
||||
self.log.debug("Initializing PluginsLoaders...")
|
||||
@@ -263,8 +283,7 @@ class Core:
|
||||
self.log.debug(f"mods_list: {self.mods_list}")
|
||||
len_mods = len(self.mods_list) - 1
|
||||
if len_mods > 0:
|
||||
# TODO: i18n
|
||||
self.log.info(f"Loaded {len_mods} mods: {round(self.mods_list[0] / MB, 2)}mb")
|
||||
self.log.info(i18n.core_mods_loaded.format(len_mods, round(self.mods_list[0] / MB, 2)))
|
||||
self.log.info(i18n.init_ok)
|
||||
|
||||
await self.heartbeat(True)
|
||||
@@ -273,12 +292,15 @@ class Core:
|
||||
tasks = []
|
||||
# self.udp.start,
|
||||
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
|
||||
if config.RCON['enabled']:
|
||||
console.rcon.version = f"KuiToi {__version__}"
|
||||
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
|
||||
f_tasks.append(rcon.start)
|
||||
for task in f_tasks:
|
||||
tasks.append(asyncio.create_task(task()))
|
||||
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
|
||||
|
||||
await ev.call_async_event("_plugins_start")
|
||||
# await ev.call_async_event("_lua_plugins_start")
|
||||
|
||||
self.run = True
|
||||
self.log.info(i18n.start)
|
||||
@@ -304,10 +326,16 @@ class Core:
|
||||
ev.call_event("onServerStopped")
|
||||
await ev.call_async_event("onServerStopped")
|
||||
await self.__gracefully_kick()
|
||||
if config.Options['use_lua']:
|
||||
ev.call_event("_lua_plugins_unload")
|
||||
await ev.call_async_event("_plugins_unload")
|
||||
ev.call_event("_lua_plugins_unload")
|
||||
self.run = False
|
||||
self.log.info(i18n.stop)
|
||||
if config.WebAPI["enabled"]:
|
||||
asyncio.run(self.web_stop())
|
||||
# exit(0)
|
||||
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)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.core.pyi
|
||||
# Written by: SantaSpeen
|
||||
# Version 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import Callable, List, Dict
|
||||
|
||||
@@ -16,6 +17,7 @@ from .udp_server import UDPServer
|
||||
|
||||
class Core:
|
||||
def __init__(self):
|
||||
self.start_time = time.monotonic()
|
||||
self.log = utils.get_logger("core")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.run = False
|
||||
@@ -45,6 +47,7 @@ class Core:
|
||||
def start_web() -> None: ...
|
||||
def stop_me(self) -> None: ...
|
||||
async def heartbeat(self, test=False) -> None: ...
|
||||
async def kick_cmd(self, args: list) -> None | str: ...
|
||||
async def main(self) -> None: ...
|
||||
def start(self) -> None: ...
|
||||
async def stop(self) -> None: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.tcp_server.py
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
@@ -24,13 +24,11 @@ class TCPServer:
|
||||
|
||||
async def auth_client(self, reader, writer):
|
||||
client = self.Core.create_client(reader, writer)
|
||||
# TODO: i18n
|
||||
self.log.info(f"Identifying new ClientConnection...")
|
||||
self.log.info(i18n.core_identifying_connection)
|
||||
data = await client._recv(True)
|
||||
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.")
|
||||
await client.kick(i18n.core_player_kick_outdated)
|
||||
return False, client
|
||||
else:
|
||||
await client._send(b"S") # Accepted client version
|
||||
@@ -38,8 +36,7 @@ class TCPServer:
|
||||
data = await client._recv(True)
|
||||
self.log.debug(f"Key: {data}")
|
||||
if len(data) > 50:
|
||||
# TODO: i18n
|
||||
await client.kick("Invalid Key (too long)!")
|
||||
await client.kick(i18n.core_player_kick_bad_key)
|
||||
return False, client
|
||||
client._key = data.decode("utf-8")
|
||||
ev.call_event("onPlayerSentKey", player=client)
|
||||
@@ -50,8 +47,7 @@ class TCPServer:
|
||||
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.')
|
||||
await client.kick(i18n.core_player_kick_invalid_key)
|
||||
return False, client
|
||||
client.nick = res["username"]
|
||||
client.roles = res["roles"]
|
||||
@@ -60,21 +56,18 @@ class TCPServer:
|
||||
# noinspection PyProtectedMember
|
||||
client._update_logger()
|
||||
except Exception as e:
|
||||
# TODO: i18n
|
||||
self.log.error(f"Auth error: {e}")
|
||||
await client.kick('Invalid authentication data! Try to reconnect in 5 minutes.')
|
||||
await client.kick(i18n.core_player_kick_auth_server_fail)
|
||||
return False, client
|
||||
|
||||
for _client in self.Core.clients:
|
||||
if not _client:
|
||||
continue
|
||||
if _client.nick == client.nick and _client.guest == client.guest:
|
||||
# TODO: i18n
|
||||
await client.kick('Stale Client (replaced by new client)')
|
||||
return False, client
|
||||
await _client.kick(i18n.core_player_kick_stale)
|
||||
|
||||
allow = True
|
||||
reason = "You are not allowed on the server!"
|
||||
reason = i18n.core_player_kick_no_allowed_default_reason
|
||||
|
||||
lua_data = ev.call_lua_event("onPlayerAuth", client.nick, client.roles, client.guest, client.identifiers)
|
||||
for data in lua_data:
|
||||
@@ -90,13 +83,12 @@ class TCPServer:
|
||||
ev.call_event("onPlayerAuthenticated", player=client)
|
||||
|
||||
if len(self.Core.clients_by_id) > config.Game["players"]:
|
||||
# TODO: i18n
|
||||
await client.kick("Server full!")
|
||||
await client.kick(i18n.core_player_kick_server_full)
|
||||
return False, client
|
||||
else:
|
||||
# TODO: i18n
|
||||
self.log.info("Identification success")
|
||||
self.log.info(i18n.core_identifying_okay)
|
||||
await self.Core.insert_client(client)
|
||||
client.log.info(i18n.core_player_set_id.format(client.pid))
|
||||
|
||||
return True, client
|
||||
|
||||
@@ -127,8 +119,8 @@ class TCPServer:
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
case _:
|
||||
# TODO: i18n
|
||||
self.log.error(f"Unknown code: {code}")
|
||||
self.log.info("Report about that!")
|
||||
writer.close()
|
||||
return False, None
|
||||
|
||||
@@ -148,7 +140,6 @@ class TCPServer:
|
||||
del cl
|
||||
break
|
||||
except Exception as e:
|
||||
# TODO: i18n
|
||||
self.log.error("Error while handling connection...")
|
||||
self.log.exception(e)
|
||||
traceback.print_exc()
|
||||
@@ -165,8 +156,7 @@ class TCPServer:
|
||||
async with server:
|
||||
await server.serve_forever()
|
||||
except OSError as e:
|
||||
# TODO: i18n
|
||||
self.log.error("Cannot bind port")
|
||||
self.log.error(i18n.core_bind_failed.format(e))
|
||||
raise e
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.tcp_server.pyi
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.udp_server.py
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
@@ -100,8 +100,8 @@ class UDPServer(asyncio.DatagramTransport):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
except OSError as e:
|
||||
self.run = False
|
||||
self.Core.run = False
|
||||
# self.run = False
|
||||
# self.Core.run = False
|
||||
self.log.error(f"Cannot bind port or other error: {e}")
|
||||
except Exception as e:
|
||||
self.log.error(f"Error: {e}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Developed by KuiToi Dev
|
||||
# File core.udp_server.py
|
||||
# Written by: SantaSpeen
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# File core.utils.py
|
||||
# Written by: SantaSpeen
|
||||
# Version 1.1
|
||||
# Core version: 0.4.1
|
||||
# Core version: 0.4.3
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import datetime
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import secrets
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, auth=None, game=None, server=None, options=None, web=None):
|
||||
self.Auth = auth or {"key": None, "private": True}
|
||||
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1}
|
||||
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
|
||||
"server_ip": "0.0.0.0", "server_port": 30814}
|
||||
self.Options = options or {"language": "en", "encoding": "utf8", "speed_limit": 0, "use_queue": False,
|
||||
"debug": False}
|
||||
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
|
||||
"secret_key": secrets.token_hex(16)}
|
||||
|
||||
Auth: Dict[str, object]
|
||||
Game: Dict[str, object]
|
||||
Server: Dict[str, object]
|
||||
RCON: Dict[str, object]
|
||||
Options: Dict[str, object]
|
||||
WebAPI: Dict[str, object]
|
||||
enc: str | None
|
||||
def __repr__(self):
|
||||
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
|
||||
class config (Config): ...
|
||||
|
||||
@@ -12,15 +12,17 @@ import yaml
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, auth=None, game=None, server=None, options=None, web=None):
|
||||
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
|
||||
self.Auth = auth or {"key": None, "private": True}
|
||||
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1}
|
||||
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
|
||||
"server_ip": "0.0.0.0", "server_port": 30814}
|
||||
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
|
||||
"password": secrets.token_hex(16)}
|
||||
self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False,
|
||||
"debug": False, "use_lua": False, "log_chat": True}
|
||||
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
|
||||
"secret_key": secrets.token_hex(16)}
|
||||
"access_token": secrets.token_hex(16)}
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
|
||||
|
||||
186
src/modules/ConsoleSystem/RCON.py
Normal file
186
src/modules/ConsoleSystem/RCON.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import asyncio
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import zlib
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from core import get_logger
|
||||
|
||||
|
||||
class RCONSystem:
|
||||
console = None
|
||||
version = "verError"
|
||||
|
||||
def __init__(self, key, host, port):
|
||||
self.log = get_logger("RCON")
|
||||
self.key = hashlib.sha256(key.encode(config.enc)).digest()
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.run = False
|
||||
|
||||
def _encrypt(self, message):
|
||||
self.log.debug(f"Encrypt message: {message}")
|
||||
iv = os.urandom(16)
|
||||
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
||||
encryptor = cipher.encryptor()
|
||||
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
||||
padded_data = padder.update(message) + padder.finalize()
|
||||
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
|
||||
encoded_data = b64encode(zlib.compress(encrypted_data, level=zlib.Z_BEST_COMPRESSION))
|
||||
encoded_iv = b64encode(iv)
|
||||
return encoded_iv + b":" + encoded_data
|
||||
|
||||
def _decrypt(self, ciphertext):
|
||||
self.log.debug(f"Decrypt message: {ciphertext}")
|
||||
encoded_iv, encoded_data = ciphertext.split(b":", 2)
|
||||
iv = b64decode(encoded_iv)
|
||||
encrypted_data = zlib.decompress(b64decode(encoded_data))
|
||||
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
|
||||
decryptor = cipher.decryptor()
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
|
||||
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
|
||||
return unpadded_data
|
||||
|
||||
async def _recv(self, reader, writer) -> tuple[str, bool]:
|
||||
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):
|
||||
self.run = True
|
||||
try:
|
||||
server = await asyncio.start_server(self.handle_connect, self.host, self.port, backlog=5)
|
||||
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
|
||||
@@ -1,36 +1,34 @@
|
||||
class Console(object):
|
||||
from logging import Logger
|
||||
from typing import AnyStr
|
||||
|
||||
def __init__(self,
|
||||
prompt_in: str = ">",
|
||||
prompt_out: str = "]:",
|
||||
not_found: str = "Command \"%s\" not found in alias.") -> None: ...
|
||||
from core import get_logger
|
||||
|
||||
def __getitem__(self, item): ...
|
||||
@property
|
||||
def alias(self) -> dict: ...
|
||||
def add(self, key: str, func: function) -> dict: ...
|
||||
def log(self, s: str, r='\r') -> None: ...
|
||||
def write(self, s: str, r='\r') -> None: ...
|
||||
def __lshift__(self, s: AnyStr) -> None: ...
|
||||
def logger_hook(self) -> None: ...
|
||||
def builtins_hook(self) -> None: ...
|
||||
async def start(self) -> None: ...
|
||||
|
||||
class console(object):
|
||||
class RCONSystem:
|
||||
console = None
|
||||
|
||||
def __init__(self, key, host, port):
|
||||
self.log = get_logger("RCON")
|
||||
self.key = key
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
async def start(self): ...
|
||||
async def stop(self): ...
|
||||
|
||||
class console:
|
||||
rcon: RCONSystem = RCONSystem
|
||||
|
||||
@staticmethod
|
||||
def alias() -> dict: ...
|
||||
@staticmethod
|
||||
def add_command(key: str, func: function) -> dict: ...
|
||||
|
||||
def add_command(key: str, func, man: str = None, desc: str = None, custom_completer: dict = None) -> dict: ...
|
||||
@staticmethod
|
||||
async def start() -> None: ...
|
||||
|
||||
@staticmethod
|
||||
def builtins_hook() -> None: ...
|
||||
@staticmethod
|
||||
def logger_hook() -> None: ...
|
||||
|
||||
@staticmethod
|
||||
def log(s: str) -> None: ...
|
||||
@staticmethod
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import builtins
|
||||
import inspect
|
||||
import logging
|
||||
from typing import AnyStr
|
||||
|
||||
@@ -14,9 +15,11 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
|
||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
|
||||
from prompt_toolkit.patch_stdout import patch_stdout
|
||||
|
||||
from core import get_logger
|
||||
from modules.ConsoleSystem.RCON import RCONSystem
|
||||
|
||||
|
||||
class Console:
|
||||
@@ -28,6 +31,7 @@ class Console:
|
||||
debug=False) -> None:
|
||||
self.__logger = get_logger("console")
|
||||
self.__is_run = False
|
||||
self.no_cmd = False
|
||||
self.__prompt_in = prompt_in
|
||||
self.__prompt_out = prompt_out
|
||||
self.__not_found = not_found
|
||||
@@ -43,6 +47,9 @@ class Console:
|
||||
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
|
||||
custom_completer={"help": {"--raw": None}})
|
||||
self.completer = NestedCompleter.from_nested_dict(self.__alias)
|
||||
rcon = RCONSystem
|
||||
rcon.console = self
|
||||
self.rcon = rcon
|
||||
|
||||
def __debug(self, *x):
|
||||
self.__logger.debug(f"{x}")
|
||||
@@ -122,19 +129,38 @@ class Console:
|
||||
self.__alias.update(custom_completer or {key: None})
|
||||
self.__alias["man"].update({key: None})
|
||||
self.__func.update({key: {"f": func}})
|
||||
self.__man.update({key: f'html<seagreen>{i18n.man_for} <b>{key}</b>\n{man}</seagreen>' if man else None})
|
||||
self.__man.update({key: f'html:<seagreen>{i18n.man_for} <b>{key}</b>\n{man}</seagreen>' if man else None})
|
||||
self.__desc.update({key: desc})
|
||||
self.__update_completer()
|
||||
return self.__alias.copy()
|
||||
|
||||
def _write(self, t):
|
||||
if self.no_cmd:
|
||||
print(t)
|
||||
return
|
||||
try:
|
||||
if t.startswith("html:"):
|
||||
print_formatted_text(HTML(t[5:]))
|
||||
else:
|
||||
print_formatted_text(t)
|
||||
except NoConsoleScreenBufferError:
|
||||
print("Works in non cmd mode.")
|
||||
self.no_cmd = True
|
||||
print(t)
|
||||
|
||||
def write(self, s: AnyStr):
|
||||
if s.startswith("html"):
|
||||
print_formatted_text(HTML(s[4:]))
|
||||
if isinstance(s, (list, tuple)):
|
||||
for text in s:
|
||||
self._write(text)
|
||||
else:
|
||||
print_formatted_text(s)
|
||||
self._write(s)
|
||||
|
||||
def log(self, s: AnyStr) -> None:
|
||||
self.__logger.info(f"{s}")
|
||||
if isinstance(s, (list, tuple)):
|
||||
for text in s:
|
||||
self.__logger.info(f"{text}")
|
||||
else:
|
||||
self.__logger.info(f"{s}")
|
||||
# self.write(s)
|
||||
|
||||
def __lshift__(self, s: AnyStr) -> None:
|
||||
@@ -191,23 +217,42 @@ class Console:
|
||||
while True:
|
||||
try:
|
||||
with patch_stdout():
|
||||
cmd_in = await session.prompt_async(
|
||||
self.__prompt_in,
|
||||
completer=self.completer,
|
||||
auto_suggest=AutoSuggestFromHistory()
|
||||
)
|
||||
if self.no_cmd:
|
||||
cmd_in = input(self.__prompt_in)
|
||||
else:
|
||||
try:
|
||||
cmd_in = await session.prompt_async(
|
||||
self.__prompt_in,
|
||||
completer=self.completer,
|
||||
auto_suggest=AutoSuggestFromHistory()
|
||||
)
|
||||
except NoConsoleScreenBufferError:
|
||||
print("Works in non cmd mode.")
|
||||
self.no_cmd = True
|
||||
cmd_s = cmd_in.split(" ")
|
||||
cmd = cmd_s[0]
|
||||
if cmd == "":
|
||||
continue
|
||||
else:
|
||||
found_in_lua = False
|
||||
d = ev.call_lua_event("onConsoleInput", cmd_in)
|
||||
if len(d) > 0:
|
||||
for text in d:
|
||||
if text is not None:
|
||||
found_in_lua = True
|
||||
self.log(text)
|
||||
command_object = self.__func.get(cmd)
|
||||
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:
|
||||
self.log(out)
|
||||
else:
|
||||
self.log(self.__not_found % cmd)
|
||||
if not found_in_lua:
|
||||
self.log(self.__not_found % cmd)
|
||||
except KeyboardInterrupt:
|
||||
raise KeyboardInterrupt
|
||||
except Exception as e:
|
||||
|
||||
@@ -22,19 +22,21 @@ class EventsSystem:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.as_tasks = []
|
||||
self.__events = {
|
||||
"onServerStarted": [],
|
||||
"onPlayerSentKey": [], # Only sync
|
||||
"onPlayerAuthenticated": [], # Only sync
|
||||
"onPlayerJoin": [],
|
||||
"onChatReceive": [],
|
||||
"onCarSpawn": [],
|
||||
"onCarDelete": [],
|
||||
"onCarEdited": [],
|
||||
"onCarReset": [],
|
||||
"onSentPing": [], # Only sync
|
||||
"onChangePosition": [], # Only sync
|
||||
"onPlayerDisconnect": [],
|
||||
"onServerStopped": [],
|
||||
"onServerStarted": [], # No handler
|
||||
"onPlayerSentKey": [], # Only sync, no handler
|
||||
"onPlayerAuthenticated": [], # (!) Only sync, With handler
|
||||
"onPlayerJoin": [], # (!) With handler
|
||||
"onChatReceive": [], # (!) With handler
|
||||
"onCarSpawn": [], # (!) With handler
|
||||
"onCarDelete": [], # (!) With handler (admin allow)
|
||||
"onCarEdited": [], # (!) With handler
|
||||
"onCarReset": [], # No handler
|
||||
"onCarChanged": [], # No handler
|
||||
"onCarFocusMove": [], # No handler
|
||||
"onSentPing": [], # Only sync, no handler
|
||||
"onChangePosition": [], # Only sync, no handler
|
||||
"onPlayerDisconnect": [], # No handler
|
||||
"onServerStopped": [], # No handler
|
||||
}
|
||||
self.__async_events = {
|
||||
"onServerStarted": [],
|
||||
@@ -44,6 +46,8 @@ class EventsSystem:
|
||||
"onCarDelete": [],
|
||||
"onCarEdited": [],
|
||||
"onCarReset": [],
|
||||
"onCarChanged": [],
|
||||
"onCarFocusMove": [],
|
||||
"onPlayerDisconnect": [],
|
||||
"onServerStopped": []
|
||||
}
|
||||
@@ -52,22 +56,28 @@ class EventsSystem:
|
||||
"onInit": [], # onServerStarted
|
||||
"onShutdown": [], # onServerStopped
|
||||
"onPlayerAuth": [], # onPlayerAuthenticated
|
||||
"onPlayerConnecting": [], # TODO lua onPlayerConnecting
|
||||
"onPlayerJoining": [], # TODO lua onPlayerJoining
|
||||
"onPlayerConnecting": [], # No
|
||||
"onPlayerJoining": [], # No
|
||||
"onPlayerJoin": [], # onPlayerJoin
|
||||
"onPlayerDisconnect": [], # TODO lua onPlayerDisconnect
|
||||
"onPlayerDisconnect": [], # onPlayerDisconnect
|
||||
"onChatMessage": [], # onChatReceive
|
||||
"onVehicleSpawn": [], # "onCarSpawn
|
||||
"onVehicleSpawn": [], # onCarSpawn
|
||||
"onVehicleEdited": [], # onCarEdited
|
||||
"onVehicleDeleted": [], # onCarDelete
|
||||
"onVehicleReset": [], # onCarReset
|
||||
"onFileChanged": [], # TODO lua onFileChanged
|
||||
"onConsoleInput": [], # kt.add_command
|
||||
}
|
||||
|
||||
def builtins_hook(self):
|
||||
self.log.debug("used builtins_hook")
|
||||
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):
|
||||
self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', "
|
||||
f"async_event={async_event}, lua_event={lua}):")
|
||||
@@ -80,9 +90,7 @@ class EventsSystem:
|
||||
return
|
||||
|
||||
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...")
|
||||
self.log.error(i18n.events_not_callable.format(event_name, f"kt.add_event(\"{event_name}\", function)"))
|
||||
return
|
||||
if async_event or inspect.iscoroutinefunction(event_func):
|
||||
if event_name not in self.__async_events:
|
||||
@@ -107,12 +115,10 @@ class EventsSystem:
|
||||
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.error(i18n.events_calling_error.format(event_name, func.__name__))
|
||||
self.log.exception(e)
|
||||
else:
|
||||
# TODO: i18n
|
||||
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_event()?. Just skipping it...")
|
||||
elif not self.is_event(event_name):
|
||||
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_event()"))
|
||||
|
||||
return funcs_data
|
||||
|
||||
@@ -127,12 +133,10 @@ class EventsSystem:
|
||||
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.error(i18n.events_calling_error.format(event_name, 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...")
|
||||
elif not self.is_event(event_name):
|
||||
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_async_event()"))
|
||||
|
||||
return funcs_data
|
||||
|
||||
@@ -146,21 +150,13 @@ class EventsSystem:
|
||||
try:
|
||||
func = lua.globals()[func_name]
|
||||
if not func:
|
||||
self.log.warning(f"Cannot trigger local event: '{func_name}' not found!")
|
||||
self.log.warning(i18n.events_lua_function_not_found.format("", func_name))
|
||||
continue
|
||||
fd = func(*args)
|
||||
funcs_data.append(fd)
|
||||
except Exception as e:
|
||||
# TODO: i18n
|
||||
self.log.error(f'Error: "{e}" - while calling lua event "{event_name}" with arguments: {args} - '
|
||||
f'in function: "{func_name}"')
|
||||
# self.log.exception(e)
|
||||
else:
|
||||
# TODO: i18n
|
||||
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
|
||||
f"Just skipping it...")
|
||||
self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}"))
|
||||
elif not self.is_event(event_name):
|
||||
self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()"))
|
||||
|
||||
return funcs_data
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
@@ -42,7 +43,7 @@ class EventTimer:
|
||||
self.mp.TriggerLocalEvent(self.event_name)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
# noinspection PyPep8Naming,PyProtectedMember
|
||||
class MP:
|
||||
|
||||
def __init__(self, name: str, lua: LuaRuntime):
|
||||
@@ -63,6 +64,12 @@ class MP:
|
||||
def _print(self, *args):
|
||||
args = list(args)
|
||||
for i, arg in enumerate(args):
|
||||
if isinstance(arg, str):
|
||||
try:
|
||||
text = arg.encode("CP1251").decode(config.enc).replace("\u001b", "\x1b")
|
||||
args[i] = re.sub(r'\x1b\[.*?m', '', text)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
if "LuaTable" in str(type(arg)):
|
||||
args[i] = self._lua.globals().Util.JsonEncode(arg)
|
||||
s = " ".join(map(str, args))
|
||||
@@ -110,19 +117,15 @@ class MP:
|
||||
try:
|
||||
func = self._lua.globals()[func_name]
|
||||
if not func:
|
||||
self.log.warning(f"Cannot trigger local event: '{func_name}' not found!")
|
||||
self.log.warning(i18n.events_lua_function_not_found.format(i18n.events_lua_local, func_name))
|
||||
continue
|
||||
fd = func(*args)
|
||||
funcs_data.append(fd)
|
||||
except Exception as e:
|
||||
# TODO: i18n
|
||||
self.log.error(f'Error: "{e}" - while calling lua event "{event_name}" with arguments: {args} - '
|
||||
f'in function: "{func_name}"')
|
||||
# self.log.exception(e)
|
||||
self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}"))
|
||||
|
||||
else:
|
||||
# TODO: i18n
|
||||
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
|
||||
f"Just skipping it...")
|
||||
self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()"))
|
||||
|
||||
return self._lua.table_from(funcs_data)
|
||||
|
||||
@@ -556,6 +559,7 @@ class FS:
|
||||
return os.path.join(*args)
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class LuaPluginsLoader:
|
||||
|
||||
def __init__(self, plugins_dir):
|
||||
@@ -573,13 +577,10 @@ class LuaPluginsLoader:
|
||||
|
||||
def load(self):
|
||||
self.log.debug("Loading Lua plugins...")
|
||||
# TODO: i18n
|
||||
self.log.info("You have enabled support for Lua plugins.")
|
||||
self.log.warning("There are some nuances to working with KuiToi. "
|
||||
"If you have a proposal for their solution, and it is related to KuiToi, "
|
||||
"please contact the developer.")
|
||||
self.log.warning("Some BeamMP plugins require a correctly configured ServerConfig.toml file to function.")
|
||||
self.log.info("Creating it.")
|
||||
self.log.info(i18n.plugins_lua_enabled)
|
||||
self.log.warning(i18n.plugins_lua_nuances_warning)
|
||||
self.log.warning(i18n.plugins_lua_legacy_config_create_warning)
|
||||
self.log.info(i18n.plugins_lua_legacy_config_create)
|
||||
data = {
|
||||
"info": "ServerConfig.toml is created solely for backward compatibility support. "
|
||||
"This file will be updated every time the program is launched.",
|
||||
@@ -667,6 +668,6 @@ class LuaPluginsLoader:
|
||||
self.log.debug("Unloading lua plugins")
|
||||
for name, data in self.lua_plugins.items():
|
||||
if data['ok']:
|
||||
self.log.info(f"Unloading lua plugin: {name}")
|
||||
self.log.info(i18n.plugins_lua_unload.format(name))
|
||||
for _, timer in data['lua'].globals().MP._event_timers.items():
|
||||
timer.stop()
|
||||
|
||||
@@ -91,6 +91,10 @@ class KuiToi:
|
||||
return False
|
||||
return bool(self.get_player(cid=pid, nick=nick))
|
||||
|
||||
def add_command(self, key, func, man, desc, custom_completer) -> dict:
|
||||
self.log.debug("Requests add_command")
|
||||
return console.add_command(key, func, man, desc, custom_completer)
|
||||
|
||||
|
||||
class PluginsLoader:
|
||||
|
||||
@@ -127,21 +131,21 @@ class PluginsLoader:
|
||||
try:
|
||||
is_func = inspect.isfunction
|
||||
if not is_func(plugin.load):
|
||||
self.log.error('Function "def load():" not found.')
|
||||
self.log.error(i18n.plugins_not_found_load)
|
||||
ok = False
|
||||
if not is_func(plugin.start):
|
||||
self.log.error('Function "def start():" not found.')
|
||||
self.log.error(i18n.plugins_not_found_start)
|
||||
ok = False
|
||||
if not is_func(plugin.unload):
|
||||
self.log.error('Function "def unload():" not found.')
|
||||
self.log.error(i18n.plugins_not_found_unload)
|
||||
ok = False
|
||||
if type(plugin.kt) != KuiToi:
|
||||
self.log.error(f'Attribute "kt" isn\'t KuiToi class. Plugin file: "{file_path}"')
|
||||
self.log.error(i18n.plugins_kt_invalid)
|
||||
ok = False
|
||||
except AttributeError:
|
||||
ok = False
|
||||
if not ok:
|
||||
self.log.error(f'Plugin file: "{file_path}" is not a valid KuiToi plugin.')
|
||||
self.log.error(i18n.plugins_invalid.format(file_path))
|
||||
return
|
||||
|
||||
pl_name = plugin.kt.name
|
||||
@@ -181,9 +185,8 @@ class PluginsLoader:
|
||||
self.loaded_str += f"{pl_name}:ok, "
|
||||
self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}")
|
||||
except Exception as e:
|
||||
# TODO: i18n
|
||||
self.loaded_str += f"{file}:no, "
|
||||
self.log.error(f"Error while loading plugin: {file}; Error: {e}")
|
||||
self.log.error(i18n.plugins_error_loading.format(file, f"{e}"))
|
||||
self.log.exception(e)
|
||||
|
||||
async def start(self, _):
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"": "基础阶段",
|
||||
"hello": "来自KuiToi服务器的问候!",
|
||||
"": "基本阶段",
|
||||
"hello": "来自KuiToi-Server的问候!",
|
||||
"config_path": "使用{}进行配置。",
|
||||
"init_ok": "初始化完成。",
|
||||
"start": "服务器已启动!",
|
||||
"stop": "服务器已停止!",
|
||||
|
||||
"": "服务器认证",
|
||||
"auth_need_key": "需要BeamMP密钥才能启动!",
|
||||
"auth_need_key": "需要BeamMP密钥才能运行!",
|
||||
"auth_empty_key": "BeamMP密钥为空!",
|
||||
"auth_cannot_open_browser": "无法打开浏览器:{}",
|
||||
"auth_use_link": "使用此链接:{}",
|
||||
@@ -17,32 +17,91 @@
|
||||
"GUI_no": "否",
|
||||
"GUI_ok": "确定",
|
||||
"GUI_cancel": "取消",
|
||||
"GUI_need_key_message": "需要BeamMP密钥才能启动!\n是否在浏览器中打开链接以获取密钥?",
|
||||
"GUI_need_key_message": "需要BeamMP密钥才能运行!\n您是否要在浏览器中打开链接获取密钥?",
|
||||
"GUI_enter_key_message": "请输入密钥:",
|
||||
"GUI_cannot_open_browser": "无法打开浏览器。\n请使用此链接:{}",
|
||||
|
||||
"": "Web阶段",
|
||||
"web_start": "WebAPI已启动{}(CTRL+C停止)",
|
||||
"web_start": "WebAPI已启动于{}(按CTRL+C停止)",
|
||||
|
||||
"": "核心短语",
|
||||
"core_bind_failed": "无法绑定端口。错误:{}",
|
||||
"core_direct_mode": "服务器以直接连接模式启动。",
|
||||
"core_auth_server_error": "从BeamMP认证服务器接收到无效响应。",
|
||||
"core_auth_server_refused": "BeamMP认证服务器拒绝了您的密钥。原因:{}",
|
||||
"core_auth_server_refused_no_reason": "BeamMP认证服务器没有提供原因。",
|
||||
"core_auth_server_refused_direct_node": "服务器仍在运行,但以直接连接模式运行。",
|
||||
"core_auth_server_no_response": "无法验证服务器。",
|
||||
"core_mods_loaded": "已加载{}个模组。{}Mb",
|
||||
"core_identifying_connection": "正在处理新连接...",
|
||||
"core_player_kick_outdated": "BeamMP版本不正确。",
|
||||
"core_player_kick_bad_key": "传递的密钥无效!",
|
||||
"core_player_kick_invalid_key": "无效的密钥!请重新启动游戏。",
|
||||
"core_player_kick_auth_server_fail": "BeamMP认证服务器失败!请在5分钟后再次尝试连接。",
|
||||
"core_player_kick_stale": "过时的客户端。(由新连接替换)",
|
||||
"core_player_kick_no_allowed_default_reason": "您不受欢迎。拒绝访问。",
|
||||
"core_player_kick_server_full": "服务器已满。",
|
||||
"core_player_set_id": "玩家设置ID {}",
|
||||
"core_identifying_okay": "成功登录。",
|
||||
|
||||
"": "游戏内短语",
|
||||
"game_welcome_message": "欢迎{}!",
|
||||
|
||||
"": "客户端类短语",
|
||||
"client_mod_request": "请求模组:{}",
|
||||
"client_mod_sent": "已发送模组:大小:{}mb,速度:{}Mb/s({}秒)",
|
||||
"client_mod_sent_limit": "(限制{}Mb/s)",
|
||||
"client_mod_sent_error": "发送模组时出错:{}",
|
||||
"client_sync_time": "同步时间{}秒。",
|
||||
"client_kicked": "因\"{}\"原因被踢出。",
|
||||
"client_event_invalid_data": "从事件返回的数据无效:{}",
|
||||
"client_player_disconnected": "离开服务器。游戏时间:{}分钟。",
|
||||
|
||||
"": "事件系统",
|
||||
|
||||
"events_not_callable": "无法添加事件\"{}\"。请改用\"{}\"。跳过...",
|
||||
"events_not_found": "事件\"{}\"未注册。也许{}?跳过...",
|
||||
"events_calling_error": "调用函数\"{}\"时出错。",
|
||||
"events_lua_function_not_found": "无法调用{}lua事件 - 未找到\"{}\"。",
|
||||
"events_lua_local": "本地 ",
|
||||
"events_lua_calling_error": "错误:\"{}\" - 调用lua事件\"{}\"时出错,函数:\"{}\",参数:{}",
|
||||
|
||||
"": "插件加载器",
|
||||
|
||||
"plugins_not_found_load": "未找到\"def load():\"函数。",
|
||||
"plugins_not_found_start": "未找到\"def start():\"函数。",
|
||||
"plugins_not_found_unload": "未找到\"def unload():\"函数。",
|
||||
"plugins_kt_invalid": "“kt”变量不属于KuiToi类。",
|
||||
"plugins_invalid": "无法在KuiToi中运行插件\"{}\"。",
|
||||
"plugins_error_loading": "加载插件{}时出错:{}",
|
||||
|
||||
"": "Lua插件加载器",
|
||||
|
||||
"plugins_lua_enabled": "您已启用Lua插件支持。",
|
||||
"plugins_lua_nuances_warning": "在使用KuiToi时有一些细微差别。如果您有关于解决方案的建议,并且它与KuiToi相关,请联系开发人员。",
|
||||
"plugins_lua_legacy_config_create_warning": "一些BeamMP插件需要一个正确配置的ServerConfig.toml文件才能正常运行。",
|
||||
"plugins_lua_legacy_config_create": "正在创建。",
|
||||
"plugins_lua_unload": "停止Lua插件:{}",
|
||||
|
||||
"": "命令:man",
|
||||
"man_message_man": "man - 显示COMMAND的帮助页面。\n用法:man COMMAND",
|
||||
"help_message_man": "显示COMMAND的帮助页面。",
|
||||
"man_for": "帮助页面",
|
||||
"man_message_not_found": "man:找不到帮助页面。",
|
||||
"man_command_not_found": "man:找不到\"{}\"命令!",
|
||||
"man_command_not_found": "man:找不到命令\"{}\"!",
|
||||
|
||||
"": "命令:help",
|
||||
"man_message_help": "help - 显示命令的名称和简短描述。\n用法:help [--raw]\n命令`help`列出所有可用的命令,并为每个命令提供简短描述。",
|
||||
"help_message_help": "显示命令的名称和简短描述。",
|
||||
"man_message_help": "help - 显示命令的名称和简要说明。\n用法:help [--raw]\n`help`命令显示所有可用命令的名称和简要说明。",
|
||||
"help_message_help": "显示命令的名称和简要说明。",
|
||||
"help_command": "命令",
|
||||
"help_message": "文本",
|
||||
"help_message_not_found": "无文本",
|
||||
"help_message_not_found": "未找到文本。",
|
||||
|
||||
"": "命令:stop",
|
||||
"man_message_stop": "stop - 关闭服务器。\n用法:stop",
|
||||
"help_message_stop": "关闭服务器。",
|
||||
"man_message_stop": "stop - 停止服务器。\n用法:stop",
|
||||
"help_message_stop": "停止服务器。",
|
||||
|
||||
"": "命令:exit",
|
||||
"man_message_exit": "exit - 关闭服务器。\n用法:exit",
|
||||
"help_message_exit": "关闭服务器。"
|
||||
"man_message_exit": "exit - 停止服务器。\n用法:exit",
|
||||
"help_message_exit": "停止服务器。"
|
||||
}
|
||||
@@ -1,42 +1,101 @@
|
||||
{
|
||||
"": "Basic phases",
|
||||
"hello": "Greetings from KuiToi Server!",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization complete.",
|
||||
"init_ok": "Initialization completed.",
|
||||
"start": "Server started!",
|
||||
"stop": "Server stopped!",
|
||||
|
||||
"": "Server auth",
|
||||
"auth_need_key": "A BeamMP key is required to start the server!",
|
||||
"auth_empty_key": "The BeamMP key is empty!",
|
||||
"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_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_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 at {} (Press CTRL+C to quit)",
|
||||
"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 - Displays help page for COMMAND.\nUsage: man COMMAND",
|
||||
"help_message_man": "Displays help page for COMMAND.",
|
||||
"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 - 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.",
|
||||
"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": "Description",
|
||||
"help_message_not_found": "No description available.",
|
||||
"help_message": "Text",
|
||||
"help_message_not_found": "No text found",
|
||||
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||
|
||||
@@ -24,6 +24,65 @@
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI запустился на {} (CTRL+C для выключения)",
|
||||
|
||||
"": "Core phrases",
|
||||
"core_bind_failed": "Не получилось занять порт. Ошибка: {}",
|
||||
"core_direct_mode": "Сервер запушен в режиме прямого подключения.",
|
||||
"core_auth_server_error": "Поступил не корректный ответ от сервером авторизации BeamMP.",
|
||||
"core_auth_server_refused": "Сервер авторизации BeamMP отклонил ваш ключ. Причина: {}",
|
||||
"core_auth_server_refused_no_reason": "Сервер авторизации BeamMP не сообщил причины.",
|
||||
"core_auth_server_refused_direct_node": "Сервер всё ещё работает, но в режиме прямого подключения.",
|
||||
"core_auth_server_no_response": "Не получилось авторизовать сервер.",
|
||||
"core_mods_loaded": "Загружено {} модов. {}Мб",
|
||||
"core_identifying_connection": "Обработка нового подключения...",
|
||||
"core_player_kick_outdated": "Не подходящая версия BeamMP.",
|
||||
"core_player_kick_bad_key": "Передан не правильный ключ!",
|
||||
"core_player_kick_invalid_key": "Неверный ключ! Пожалуйста, перезапустите свою игру.",
|
||||
"core_player_kick_auth_server_fail": "Сбой сервера аутентификации! Попробуйте снова подключиться через 5 минут.",
|
||||
"core_player_kick_stale": "Устаревший клиент. (Заменено новым подключением)",
|
||||
"core_player_kick_no_allowed_default_reason": "Вам не рады на этом сервере. Вход запрещён.",
|
||||
"core_player_kick_server_full": "Сервер полон.",
|
||||
"core_player_set_id": "Игрок получил ID {}",
|
||||
"core_identifying_okay": "Успешный вход.",
|
||||
|
||||
"": "In-game phrases",
|
||||
"game_welcome_message": "Добро пожаловать {}!",
|
||||
|
||||
"": "Client class phrases",
|
||||
"client_mod_request": "Запрошен мод: {}",
|
||||
"client_mod_sent": "Мод отправлен: Вес: {}мб, Скорость: {}Мб/с ({}сек)",
|
||||
"client_mod_sent_limit": " (лимит {}Мб/с)",
|
||||
"client_mod_sent_error": "Ошибка при отправке мода: {}",
|
||||
"client_sync_time": "Время синхронизации {}сек.",
|
||||
"client_kicked": "Кикнут по причине: \"{}\"",
|
||||
"client_event_invalid_data": "Из ивента вернулись не верные данные: {}",
|
||||
"client_player_disconnected": "Вышел с сервера. Время игры: {} мин",
|
||||
|
||||
"": "Events system",
|
||||
|
||||
"events_not_callable": "Невозможно добавить ивент \"{}\". Использую лучше \"{}\". Скип...",
|
||||
"events_not_found": "Ивент \"{}\" не зарегистрирован. Может {}? Скип...",
|
||||
"events_calling_error": "Ошибка во время вызова \"{}\" в функции \"{}\".",
|
||||
"events_lua_function_not_found": "Невозможно вызвать {}lua ивент - \"{}\" не найдена.",
|
||||
"events_lua_local": "локальный ",
|
||||
"events_lua_calling_error": "Ошибка: \"{}\" - во время вызова lua ивента \"{}\", функция: \"{}\" , аргументы: {}",
|
||||
|
||||
"": "Plugins loader",
|
||||
|
||||
"plugins_not_found_load": "Функция \"def load():\" не найдена.",
|
||||
"plugins_not_found_start": "Функция \"def start():\" не найдена.",
|
||||
"plugins_not_found_unload": "Функция \"def unload():\" не найдена.",
|
||||
"plugins_kt_invalid": "Переменная \"kt\" не принадлежит классу KuiToi.",
|
||||
"plugins_invalid": "Плагин: \"{}\" - не может быть запущен в KuiToi.",
|
||||
"plugins_error_loading": "Произошла ошибка при загрузке плагина {}: {}",
|
||||
|
||||
"": "Lua plugins loader",
|
||||
|
||||
"plugins_lua_enabled": "Вы включили поддержку плагинов Lua.",
|
||||
"plugins_lua_nuances_warning": "В работе с Kuiti есть некоторые нюансы. Если у вас есть предложение по их решению, и оно связано с KuiToi, пожалуйста, свяжитесь с разработчиком.",
|
||||
"plugins_lua_legacy_config_create_warning": "Для работы некоторых плагинов BeamMP требуется правильно настроенный файл ServerConfig.toml.",
|
||||
"plugins_lua_legacy_config_create": "Создаю его.",
|
||||
"plugins_lua_unload": "Останавливаю Lua плагин: {}",
|
||||
|
||||
"": "Command: man",
|
||||
"man_message_man": "man - Показывает страничку помощи для COMMAND.\nИспользование: man COMMAND",
|
||||
"help_message_man": "Показывает страничку помощи для COMMAND.",
|
||||
|
||||
@@ -1,93 +1,109 @@
|
||||
class i18n:
|
||||
# Basic phases
|
||||
hello: str = data["hello"]
|
||||
config_path: str = data["config_path"]
|
||||
init_ok: str = data["init_ok"]
|
||||
start: str = data["start"]
|
||||
stop: str = data["stop"]
|
||||
hello: str
|
||||
config_path: str
|
||||
init_ok: str
|
||||
start: str
|
||||
stop: str
|
||||
|
||||
# Server auth
|
||||
auth_need_key: str = data["auth_need_key"]
|
||||
auth_empty_key: str = data["auth_empty_key"]
|
||||
auth_cannot_open_browser: str = data["auth_cannot_open_browser"]
|
||||
auth_use_link: str = data["auth_use_link"]
|
||||
auth_need_key: str
|
||||
auth_empty_key: str
|
||||
auth_cannot_open_browser: str
|
||||
auth_use_link: str
|
||||
|
||||
# GUI phases
|
||||
GUI_yes: str = data["GUI_yes"]
|
||||
GUI_no: str = data["GUI_no"]
|
||||
GUI_ok: str = data["GUI_ok"]
|
||||
GUI_cancel: str = data["GUI_cancel"]
|
||||
GUI_need_key_message: str = data["GUI_need_key_message"]
|
||||
GUI_enter_key_message: str = data["GUI_enter_key_message"]
|
||||
GUI_cannot_open_browser: str = data["GUI_cannot_open_browser"]
|
||||
GUI_yes: str
|
||||
GUI_no: str
|
||||
GUI_ok: str
|
||||
GUI_cancel: str
|
||||
GUI_need_key_message: str
|
||||
GUI_enter_key_message: str
|
||||
GUI_cannot_open_browser: str
|
||||
|
||||
# Web phases
|
||||
web_start: str = data["web_start"]
|
||||
web_start: str
|
||||
|
||||
# Core phrases
|
||||
|
||||
core_bind_failed: str
|
||||
core_direct_mode: str
|
||||
core_auth_server_error: str
|
||||
core_auth_server_refused: str
|
||||
core_auth_server_refused_no_reason: str
|
||||
core_auth_server_refused_direct_node: str
|
||||
core_auth_server_no_response: str
|
||||
core_mods_loaded: str
|
||||
core_identifying_connection: str
|
||||
core_player_kick_outdated: str
|
||||
core_player_kick_bad_key: str
|
||||
core_player_kick_invalid_key: str
|
||||
core_player_kick_auth_server_fail: str
|
||||
core_player_kick_stale: str
|
||||
core_player_kick_no_allowed_default_reason: str
|
||||
core_player_kick_server_full: str
|
||||
core_player_set_id: str
|
||||
core_identifying_okay: str
|
||||
|
||||
# In-game phrases
|
||||
|
||||
game_welcome_message: str
|
||||
|
||||
# Client class phrases
|
||||
|
||||
client_mod_request: str
|
||||
client_mod_sent: str
|
||||
client_mod_sent_limit: str
|
||||
client_mod_sent_error: str
|
||||
client_sync_time: str
|
||||
client_kicked: str
|
||||
client_event_invalid_data: str
|
||||
client_player_disconnected: str
|
||||
|
||||
# Events system
|
||||
|
||||
events_not_callable: str
|
||||
events_not_found: str
|
||||
events_calling_error: str
|
||||
events_lua_function_not_found: str
|
||||
events_lua_local: str
|
||||
events_lua_calling_error: str
|
||||
|
||||
# Plugins loader
|
||||
|
||||
plugins_not_found_load: str
|
||||
plugins_not_found_start: str
|
||||
plugins_not_found_unload: str
|
||||
plugins_kt_invalid: str
|
||||
plugins_invalid: str
|
||||
plugins_error_loading: str
|
||||
|
||||
# Lua plugins loader
|
||||
|
||||
plugins_lua_enabled: str
|
||||
plugins_lua_nuances_warning: str
|
||||
plugins_lua_legacy_config_create_warning: str
|
||||
plugins_lua_legacy_config_create: str
|
||||
plugins_lua_unload: str
|
||||
|
||||
# Command: man
|
||||
man_message_man: str = data["man_message_man"]
|
||||
help_message_man: str = data["help_message_man"]
|
||||
man_for: str = data["man_for"]
|
||||
man_message_not_found: str = data["man_message_not_found"]
|
||||
man_command_not_found: str = data["man_command_not_found"]
|
||||
man_message_man: str
|
||||
help_message_man: str
|
||||
man_for: str
|
||||
man_message_not_found: str
|
||||
man_command_not_found: str
|
||||
|
||||
# Command: help
|
||||
man_message_help: str = data["man_message_help"]
|
||||
help_message_help: str = data["help_message_help"]
|
||||
help_command: str = data["help_command"]
|
||||
help_message: str = data["help_message"]
|
||||
help_message_not_found: str = data["help_message_not_found"]
|
||||
man_message_help: str
|
||||
help_message_help: str
|
||||
help_command: str
|
||||
help_message: str
|
||||
help_message_not_found: str
|
||||
|
||||
# Command: stop
|
||||
man_message_stop: str = data["man_message_stop"]
|
||||
help_message_stop: str = data["help_message_stop"]
|
||||
man_message_stop: str
|
||||
help_message_stop: str
|
||||
|
||||
# Command: exit
|
||||
man_message_exit: str = data["man_message_exit"]
|
||||
help_message_exit: str = data["help_message_exit"]
|
||||
|
||||
data = {
|
||||
"": "Basic phases",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} for config.",
|
||||
"init_ok": "Initializing ready.",
|
||||
"start": "Server started!",
|
||||
"stop": "Goodbye!",
|
||||
|
||||
"": "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: {}",
|
||||
|
||||
"": "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: {}",
|
||||
|
||||
"": "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: 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: stop",
|
||||
"man_message_stop": "stop - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_stop": "Server shutdown.",
|
||||
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Just shutting down the server.\nUsage: stop",
|
||||
"help_message_exit": "Server shutdown."
|
||||
}
|
||||
man_message_exit: str
|
||||
help_message_exit: str
|
||||
|
||||
@@ -14,56 +14,13 @@ from core.utils import get_logger
|
||||
|
||||
|
||||
class i18n:
|
||||
data = {}
|
||||
|
||||
def __init__(self, data):
|
||||
# Basic phases
|
||||
self.hello: str = data["hello"]
|
||||
self.config_path: str = data["config_path"]
|
||||
self.init_ok: str = data["init_ok"]
|
||||
self.start: str = data["start"]
|
||||
self.stop: str = data["stop"]
|
||||
i18n.data = data
|
||||
|
||||
# Server auth
|
||||
self.auth_need_key: str = data["auth_need_key"]
|
||||
self.auth_empty_key: str = data["auth_empty_key"]
|
||||
self.auth_cannot_open_browser: str = data["auth_cannot_open_browser"]
|
||||
self.auth_use_link: str = data["auth_use_link"]
|
||||
|
||||
# GUI phases
|
||||
self.GUI_yes: str = data["GUI_yes"]
|
||||
self.GUI_no: str = data["GUI_no"]
|
||||
self.GUI_ok: str = data["GUI_ok"]
|
||||
self.GUI_cancel: str = data["GUI_cancel"]
|
||||
self.GUI_need_key_message: str = data["GUI_need_key_message"]
|
||||
self.GUI_enter_key_message: str = data["GUI_enter_key_message"]
|
||||
self.GUI_cannot_open_browser: str = data["GUI_cannot_open_browser"]
|
||||
|
||||
# Web phases
|
||||
self.web_start: str = data["web_start"]
|
||||
|
||||
# Command: man
|
||||
self.man_message_man: str = data["man_message_man"]
|
||||
self.help_message_man: str = data["help_message_man"]
|
||||
self.man_for: str = data["man_for"]
|
||||
self.man_message_not_found: str = data["man_message_not_found"]
|
||||
self.man_command_not_found: str = data["man_command_not_found"]
|
||||
|
||||
# Command: help
|
||||
self.man_message_help: str = data["man_message_help"]
|
||||
self.help_message_help: str = data["help_message_help"]
|
||||
self.help_command: str = data["help_command"]
|
||||
self.help_message: str = data["help_message"]
|
||||
self.help_message_not_found: str = data["help_message_not_found"]
|
||||
|
||||
# Command: help
|
||||
self.man_message_stop: str = data["man_message_stop"]
|
||||
self.help_message_stop: str = data["help_message_stop"]
|
||||
|
||||
# Command: exit
|
||||
self.man_message_exit: str = data["man_message_exit"]
|
||||
self.help_message_exit: str = data["help_message_exit"]
|
||||
|
||||
self.data = data
|
||||
def __getattribute__(self, key):
|
||||
return i18n.data[key]
|
||||
|
||||
|
||||
class MultiLanguage:
|
||||
@@ -91,53 +48,112 @@ class MultiLanguage:
|
||||
else:
|
||||
# noinspection PyDictDuplicateKeys
|
||||
self.__data = {
|
||||
"": "Basic phases",
|
||||
"hello": "Greetings from KuiToi Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization complete.",
|
||||
"start": "Server started!",
|
||||
"stop": "Server stopped!",
|
||||
"": "Basic phases",
|
||||
"hello": "Hello from KuiToi-Server!",
|
||||
"config_path": "Use {} to configure.",
|
||||
"init_ok": "Initialization completed.",
|
||||
"start": "Server started!",
|
||||
"stop": "Server stopped!",
|
||||
|
||||
"": "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: {}",
|
||||
"": "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": "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: {}",
|
||||
"": "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 at {} (Press CTRL+C to quit)",
|
||||
"": "Web phases",
|
||||
"web_start": "WebAPI started on {} (CTRL+C to stop)",
|
||||
|
||||
"": "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!",
|
||||
"": "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.",
|
||||
|
||||
"": "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.",
|
||||
"": "In-game phrases",
|
||||
"game_welcome_message": "Welcome {}!",
|
||||
|
||||
"": "Command: stop",
|
||||
"man_message_stop": "stop - Stops the server.\nUsage: stop",
|
||||
"help_message_stop": "Stops the server.",
|
||||
"": "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",
|
||||
|
||||
"": "Command: exit",
|
||||
"man_message_exit": "exit - Stops the server.\nUsage: exit",
|
||||
"help_message_exit": "Stops the server."
|
||||
}
|
||||
"": "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)
|
||||
|
||||
def open_file(self):
|
||||
|
||||
BIN
win-logo.ico
Normal file
BIN
win-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
19
win-metadata.yml
Normal file
19
win-metadata.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# pip install pyinstaller-versionfile
|
||||
# create-version-file win-metadata.yml --outfile win-ver_info.txt
|
||||
Version: 0.4.3
|
||||
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
|
||||
85
win-pyinstaller.json
Normal file
85
win-pyinstaller.json
Normal file
@@ -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