mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 16:25:36 +00:00
Compare commits
7 Commits
102891c8e8
...
a5a7a5dfc9
Author | SHA1 | Date | |
---|---|---|---|
a5a7a5dfc9 | |||
f6ff018b03 | |||
1829113ae5 | |||
e72c371e20 | |||
57b7cebeca | |||
2a2d55946e | |||
ea2d715cae |
@ -7,6 +7,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
import time
|
||||
import zlib
|
||||
|
||||
from core import utils
|
||||
@ -33,6 +34,7 @@ class Client:
|
||||
self._guest = True
|
||||
self._ready = False
|
||||
self._cars = []
|
||||
self._connect_time: float = 0
|
||||
|
||||
@property
|
||||
def _writer(self):
|
||||
@ -89,9 +91,15 @@ class Client:
|
||||
return
|
||||
# TODO: i18n
|
||||
self.log.info(f"Kicked with reason: \"{reason}\"")
|
||||
await self._send(b"K" + bytes(reason, "utf-8"))
|
||||
await self._send(f"K{reason}")
|
||||
self.__alive = False
|
||||
|
||||
async def send_message(self, message, to_all=True):
|
||||
pass
|
||||
|
||||
async def send_event(self, event_name, event_data):
|
||||
pass
|
||||
|
||||
async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None):
|
||||
|
||||
# TNetwork.cpp; Line: 383
|
||||
@ -101,7 +109,7 @@ class Client:
|
||||
# size data
|
||||
|
||||
if type(data) == str:
|
||||
data = bytes(data, "utf-8")
|
||||
data = bytes(data, config.enc)
|
||||
|
||||
if to_all:
|
||||
code = chr(data[0])
|
||||
@ -198,7 +206,7 @@ class Client:
|
||||
self.log.warning("Client sent header of >100MB - "
|
||||
"assuming malicious intent and disconnecting the client.")
|
||||
self.__packets_queue.append(None)
|
||||
self.log.debug(f"Last recv: {await self.__reader.read(100 * MB)}")
|
||||
self.log.error(f"Last recv: {await self.__reader.read(100 * MB)}")
|
||||
continue
|
||||
|
||||
data = await self.__reader.read(int_header)
|
||||
@ -222,31 +230,42 @@ class Client:
|
||||
self.__alive = False
|
||||
self.__packets_queue.append(None)
|
||||
|
||||
async def _split_load(self, start, end, d_sock, filename):
|
||||
# TODO: Speed limiter
|
||||
async def _split_load(self, start, end, d_sock, filename, speed_limit=None):
|
||||
real_size = end - start
|
||||
writer = self._down_sock[1] if d_sock else self.__writer
|
||||
who = 'dwn' if d_sock else 'srv'
|
||||
if config.Server["debug"]:
|
||||
self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}")
|
||||
self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}")
|
||||
|
||||
with open(filename, 'rb') as f:
|
||||
f.seek(start)
|
||||
data = f.read(end)
|
||||
try:
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
self.log.debug(f"[{who}] File sent.")
|
||||
except ConnectionError:
|
||||
self.__alive = False
|
||||
self.log.debug(f"[{who}] Disconnected.")
|
||||
return real_size
|
||||
total_sent = 0
|
||||
start_time = time.monotonic()
|
||||
while total_sent < real_size:
|
||||
data = f.read(min(MB, real_size - total_sent)) # read data in chunks of 1MB or less
|
||||
try:
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
self.log.debug(f"[{who}] Sent {len(data)} bytes.")
|
||||
except ConnectionError:
|
||||
self.__alive = False
|
||||
self.log.debug(f"[{who}] Disconnected.")
|
||||
break
|
||||
total_sent += len(data)
|
||||
|
||||
# Calculate delay based on speed limit
|
||||
if speed_limit:
|
||||
elapsed_time = time.monotonic() - start_time
|
||||
expected_time = total_sent / (speed_limit * MB)
|
||||
if expected_time > elapsed_time:
|
||||
await asyncio.sleep(expected_time - elapsed_time)
|
||||
|
||||
return total_sent
|
||||
|
||||
async def _sync_resources(self):
|
||||
while self.__alive:
|
||||
data = await self._recv(True)
|
||||
if data.startswith(b"f"):
|
||||
file = data[1:].decode("utf-8")
|
||||
file = data[1:].decode(config.enc)
|
||||
# TODO: i18n
|
||||
self.log.info(f"Requested mode: {file!r}")
|
||||
size = -1
|
||||
@ -270,13 +289,28 @@ class Client:
|
||||
if t > 50:
|
||||
await self.kick("Missing download socket")
|
||||
return
|
||||
|
||||
if config.Options['use_queue']:
|
||||
while self.__Core.lock_upload:
|
||||
await asyncio.sleep(.2)
|
||||
self.__Core.lock_upload = True
|
||||
speed = config.Options["speed_limit"]
|
||||
if speed:
|
||||
speed = speed / 2
|
||||
half_size = math.floor(size / 2)
|
||||
t = time.monotonic()
|
||||
uploads = [
|
||||
self._split_load(0, half_size, False, file),
|
||||
self._split_load(half_size, size, True, file)
|
||||
self._split_load(0, half_size, False, file, speed),
|
||||
self._split_load(half_size, size, True, file, speed)
|
||||
]
|
||||
sl0, sl1 = await asyncio.gather(*uploads)
|
||||
tr = time.monotonic() - t
|
||||
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)"
|
||||
if speed:
|
||||
msg += f" of limit {int(speed * 2)}Mb/s"
|
||||
self.log.info(msg)
|
||||
sent = sl0 + sl1
|
||||
ok = sent == size
|
||||
lost = size - sent
|
||||
@ -284,7 +318,7 @@ class Client:
|
||||
if not ok:
|
||||
self.__alive = False
|
||||
# TODO: i18n
|
||||
self.log.error(f"Error while sending.")
|
||||
self.log.error(f"Error while sending: {file!r}")
|
||||
return
|
||||
elif data.startswith(b"SR"):
|
||||
path_list = ''
|
||||
@ -299,11 +333,11 @@ class Client:
|
||||
if len(mod_list) == 0:
|
||||
await self._send(b"-")
|
||||
else:
|
||||
await self._send(bytes(mod_list, "utf-8"))
|
||||
await self._send(mod_list)
|
||||
elif data == b"Done":
|
||||
for c in range(int(config.Game['max_cars'] * 2.3)):
|
||||
self._cars.append(None)
|
||||
await self._send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
|
||||
await self._send(f"M/levels/{config.Game['map']}/info.json")
|
||||
break
|
||||
return
|
||||
|
||||
@ -312,7 +346,8 @@ class Client:
|
||||
s = data[sep:sep + 3]
|
||||
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}'")
|
||||
self.log.debug(
|
||||
f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}'")
|
||||
return -1, -1
|
||||
cid = s[:id_sep]
|
||||
vid = s[id_sep + 1:]
|
||||
@ -349,9 +384,14 @@ class Client:
|
||||
car_json = json.loads(data[data.find("{"):])
|
||||
except Exception as e:
|
||||
self.log.debug(f"Invalid car_json: Error: {e}; Data: {car_data}")
|
||||
allow = True
|
||||
# allow = True
|
||||
over_spawn = False
|
||||
# TODO: Call event onCarSpawn
|
||||
ev_data = 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.extend(d2)
|
||||
for d in ev_data:
|
||||
# TODO: handle event onCarSpawn
|
||||
pass
|
||||
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
|
||||
unicycle = car_json.get("jbm") == "unicycle"
|
||||
# FIXME: unicycle
|
||||
@ -388,29 +428,39 @@ class Client:
|
||||
self.log.debug(f"Unknown car: car_id={car_id}")
|
||||
case "c": # Edit car
|
||||
self.log.debug("Trying to edit car")
|
||||
allow = True
|
||||
# TODO: Call event onCarEdited
|
||||
cid, car_id = self._get_cid_vid(dta)
|
||||
if car_id != -1 and cid == self.cid:
|
||||
try:
|
||||
car = self.cars[car_id]
|
||||
if car['unicycle']:
|
||||
self._cars.pop(car_id)
|
||||
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
|
||||
elif allow:
|
||||
await self._send(dta, to_all=True, to_self=False)
|
||||
if car['json_ok']:
|
||||
old_car_json = car['json']
|
||||
try:
|
||||
new_car_json = json.loads(data[data.find("{"):])
|
||||
old_car_json.update(new_car_json)
|
||||
car['json'] = new_car_json
|
||||
self.log.debug(f"Updated car: car_id={car_id}")
|
||||
except Exception as e:
|
||||
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {data}")
|
||||
try:
|
||||
client = self.__Core.get_client(cid=cid)
|
||||
if client:
|
||||
car = client.cars[car_id]
|
||||
new_car_json = {}
|
||||
try:
|
||||
new_car_json = json.loads(data[data.find("{"):])
|
||||
except Exception as e:
|
||||
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {data}")
|
||||
|
||||
allow = False
|
||||
ev_data = 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.extend(d2)
|
||||
for d in ev_data:
|
||||
# TODO: handle event onCarEdited
|
||||
pass
|
||||
|
||||
if car_id != -1 and cid == self.cid or allow:
|
||||
if car['unicycle']:
|
||||
self._cars.pop(car_id)
|
||||
await self._send(f"Od:{cid}-{car_id}", to_all=True, to_self=True)
|
||||
else:
|
||||
await self._send(dta, to_all=True, to_self=False)
|
||||
if car['json_ok']:
|
||||
old_car_json = car['json']
|
||||
old_car_json.update(new_car_json)
|
||||
car['json'] = old_car_json
|
||||
self.log.debug(f"Updated car: car_id={car_id}")
|
||||
except IndexError:
|
||||
self.log.debug(f"Unknown car: car_id={car_id}")
|
||||
|
||||
except IndexError:
|
||||
self.log.debug(f"Unknown car: car_id={car_id}")
|
||||
case "r": # Reset car
|
||||
self.log.debug("Trying to reset car")
|
||||
cid, car_id = self._get_cid_vid(dta)
|
||||
@ -442,6 +492,7 @@ class Client:
|
||||
# Codes: p, Z in udp_server.py
|
||||
match code:
|
||||
case "H":
|
||||
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)
|
||||
@ -462,7 +513,7 @@ class Client:
|
||||
sup = data.find(":", 2)
|
||||
if sup == -1:
|
||||
await self._send("C:Server: Invalid message.")
|
||||
msg = data[sup+2:]
|
||||
msg = data[sup + 2:]
|
||||
if not msg:
|
||||
self.log.debug("Tried to send an empty event, ignoring")
|
||||
return
|
||||
@ -484,6 +535,7 @@ class Client:
|
||||
to_client = ev_data.get("to_client")
|
||||
writer = None
|
||||
if to_client:
|
||||
# noinspection PyProtectedMember
|
||||
writer = to_client._writer
|
||||
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
|
||||
need_send = False
|
||||
@ -503,6 +555,7 @@ class Client:
|
||||
await self._send(data, to_all=True, to_self=False)
|
||||
|
||||
async def _looper(self):
|
||||
self._connect_time = time.monotonic()
|
||||
await self._send(f"P{self.cid}") # Send clientID
|
||||
await self._sync_resources()
|
||||
tasks = self.__tasks
|
||||
@ -533,7 +586,7 @@ class Client:
|
||||
await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False) # I'm disconnected.
|
||||
self.log.debug(f"Removing client")
|
||||
# TODO: i18n
|
||||
self.log.info("Disconnected")
|
||||
self.log.info(f"Disconnected, online time: {round((time.monotonic() - self._connect_time) / 60, 2)}min.")
|
||||
self.__Core.clients[self.cid] = None
|
||||
self.__Core.clients_by_id.pop(self.cid)
|
||||
self.__Core.clients_by_nick.pop(self.nick)
|
||||
|
@ -15,6 +15,7 @@ from core import Core, utils
|
||||
class Client:
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
|
||||
self._connect_time: float = None
|
||||
self.__tasks = []
|
||||
self.__reader = reader
|
||||
self.__writer = writer
|
||||
@ -51,11 +52,13 @@ class Client:
|
||||
def cars(self) -> List[dict | None]: ...
|
||||
def is_disconnected(self) -> bool: ...
|
||||
async def kick(self, reason: str) -> None: ...
|
||||
async def send_message(self, message: str | bytes, to_all: bool = True) -> None:...
|
||||
async def send_event(self, event_name: str, event_data: str) -> None: ...
|
||||
async def _send(self, data: bytes | str, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ...
|
||||
async def _sync_resources(self) -> None: ...
|
||||
async def __handle_packet(self, data, int_header): ...
|
||||
# async def __handle_packet(self, data, int_header): ...
|
||||
async def _recv(self, one=False) -> bytes | None: ...
|
||||
async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ...
|
||||
async def _split_load(self, start: int, end: int, d_sock: bool, filename: str, sl: float) -> None: ...
|
||||
async def _get_cid_vid(self, s: str) -> Tuple[int, int]: ...
|
||||
async def _handle_car_codes(self, data) -> None: ...
|
||||
async def _handle_codes(self, data) -> None: ...
|
||||
|
@ -46,17 +46,17 @@ if args.config:
|
||||
config_provider = ConfigProvider(config_path)
|
||||
config = config_provider.open_config()
|
||||
builtins.config = config
|
||||
if config.Server['debug'] is True:
|
||||
if config.Options['debug'] is True:
|
||||
utils.set_debug_status()
|
||||
log.info("Debug enabled!")
|
||||
log = get_logger("core.init")
|
||||
log.debug("Debug mode enabled!")
|
||||
log.debug(f"Server config: {config}")
|
||||
|
||||
config.enc = config.Options['encoding']
|
||||
# i18n init
|
||||
log.debug("Initializing i18n...")
|
||||
ml = MultiLanguage()
|
||||
ml.set_language(args.language or config.Server['language'])
|
||||
ml.set_language(args.language or config.Options['language'])
|
||||
ml.builtins_hook()
|
||||
|
||||
log.debug("Initializing EventsSystem...")
|
||||
|
@ -20,6 +20,7 @@ from modules import PluginsLoader
|
||||
from modules.WebAPISystem import app as webapp
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class Core:
|
||||
|
||||
def __init__(self):
|
||||
@ -40,6 +41,8 @@ class Core:
|
||||
self.web_pool = webapp.data_pool
|
||||
self.web_stop = None
|
||||
|
||||
self.lock_upload = False
|
||||
|
||||
self.client_major_version = "2.0"
|
||||
self.BeamMP_version = "3.2.0"
|
||||
|
||||
@ -105,7 +108,7 @@ class Core:
|
||||
if not client.ready:
|
||||
client.is_disconnected()
|
||||
continue
|
||||
await client._send(bytes(ca, "utf-8"))
|
||||
await client._send(ca)
|
||||
except Exception as e:
|
||||
self.log.error("Error in check_alive.")
|
||||
self.log.exception(e)
|
||||
@ -145,10 +148,10 @@ class Core:
|
||||
modstotal = len(self.mods_list) - 1
|
||||
while self.run:
|
||||
try:
|
||||
data = {"uuid": config.Auth["key"], "players": len(self.clients_by_id), "maxplayers": config.Game["players"],
|
||||
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
|
||||
"private": config.Auth['private'], "version": self.BeamMP_version,
|
||||
"clientversion": self.client_major_version,
|
||||
data = {"uuid": config.Auth["key"], "players": len(self.clients_by_id),
|
||||
"maxplayers": config.Game["players"], "port": config.Server["server_port"],
|
||||
"map": f"/levels/{config.Game['map']}/info.json", "private": config.Auth['private'],
|
||||
"version": self.BeamMP_version, "clientversion": self.client_major_version,
|
||||
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
|
||||
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
|
||||
self.log.debug(f"Auth: data {data}")
|
||||
|
@ -32,6 +32,7 @@ class Core:
|
||||
self.udp = UDPServer
|
||||
self.web_thread: Thread = None
|
||||
self.web_stop: Callable = lambda: None
|
||||
self.lock_upload = False
|
||||
self.client_major_version = "2.0"
|
||||
self.BeamMP_version = "3.2.0"
|
||||
def get_client(self, cid=None, nick=None) -> Client | None: ...
|
||||
|
@ -12,6 +12,7 @@ import aiohttp
|
||||
from core import utils
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class TCPServer:
|
||||
def __init__(self, core, host, port):
|
||||
self.log = utils.get_logger("TCPServer")
|
||||
|
@ -5,7 +5,6 @@
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import asyncio
|
||||
import traceback
|
||||
|
||||
from core import utils
|
||||
|
||||
@ -95,7 +94,6 @@ class UDPServer(asyncio.DatagramTransport):
|
||||
self.log.error(f"Error: {e}")
|
||||
self.log.exception(e)
|
||||
finally:
|
||||
self.log.info("UDP сервер сдох 2")
|
||||
self.run = False
|
||||
self.Core.run = False
|
||||
|
||||
|
@ -20,8 +20,8 @@ logging.basicConfig(level=log_level, format=log_format)
|
||||
if not os.path.exists(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
if os.path.exists(log_file):
|
||||
mtime = os.path.getmtime(log_file)
|
||||
gz_path = log_dir + datetime.datetime.fromtimestamp(mtime).strftime('%d.%m.%Y') + "-%s.tar.gz"
|
||||
ftime = os.path.getmtime(log_file)
|
||||
gz_path = log_dir + datetime.datetime.fromtimestamp(ftime).strftime('%d.%m.%Y') + "-%s.tar.gz"
|
||||
index = 1
|
||||
while True:
|
||||
if not os.path.exists(gz_path % index):
|
||||
@ -33,11 +33,15 @@ if os.path.exists(log_file):
|
||||
if os.path.exists(file):
|
||||
tar.add(file, os.path.basename(file))
|
||||
os.remove(file)
|
||||
fh = logging.FileHandler(log_file, encoding='utf-8')
|
||||
fh = logging.FileHandler(log_file, encoding="utf-8")
|
||||
fh.setFormatter(logging.Formatter(log_format))
|
||||
|
||||
|
||||
def get_logger(name):
|
||||
try:
|
||||
fh.encoding = config.enc
|
||||
except NameError:
|
||||
fh.encoding = "utf-8"
|
||||
log = logging.getLogger(name=name)
|
||||
log.addHandler(fh)
|
||||
log.level = log_level
|
||||
|
@ -1,8 +1,17 @@
|
||||
import secrets
|
||||
|
||||
|
||||
class Config:
|
||||
Auth: dict
|
||||
Game: dict
|
||||
Server: dict
|
||||
WebAPI: dict
|
||||
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)}
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
|
||||
class config (Config): ...
|
||||
|
@ -1,9 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Developed by KuiToi Dev
|
||||
# File modules.config_provider.config_provider.py
|
||||
# Written by: SantaSpeen
|
||||
# Version 1.0
|
||||
# Version 1.1
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
import os
|
||||
@ -11,13 +10,14 @@ import secrets
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, auth=None, game=None, server=None, web=None):
|
||||
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!", "language": "en",
|
||||
"server_ip": "0.0.0.0", "server_port": 30814, "debug": False}
|
||||
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": "utf-8", "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)}
|
||||
|
||||
|
@ -27,12 +27,16 @@ class EventsSystem:
|
||||
"onPlayerAuthenticated": [], # Only sync
|
||||
"onPlayerJoin": [],
|
||||
"onChatReceive": [],
|
||||
"onCarSpawn": [],
|
||||
"onCarEdited": [],
|
||||
"onServerStopped": [],
|
||||
}
|
||||
self.__async_events = {
|
||||
"onServerStarted": [],
|
||||
"onPlayerJoin": [],
|
||||
"onChatReceive": [],
|
||||
"onCarSpawn": [],
|
||||
"onCarEdited": [],
|
||||
"onServerStopped": []
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ class PluginsLoader:
|
||||
plugin.print = print
|
||||
file_path = os.path.join(self.plugins_dir, file)
|
||||
plugin.__file__ = file_path
|
||||
with open(f'{file_path}', 'r', encoding="utf-8") as f:
|
||||
with open(f'{file_path}', 'r', encoding=config.enc) as f:
|
||||
code = f.read()
|
||||
exec(code, plugin.__dict__)
|
||||
|
||||
|
@ -68,7 +68,9 @@ class i18n:
|
||||
|
||||
class MultiLanguage:
|
||||
|
||||
def __init__(self, language: str = None, files_dir="modules/i18n/files/", encoding="utf-8"):
|
||||
def __init__(self, language: str = None, files_dir="modules/i18n/files/", encoding=None):
|
||||
if encoding is None:
|
||||
encoding = config.enc
|
||||
if language is None:
|
||||
language = "en"
|
||||
self.__data = {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user