KuiToi-Server/src/modules/PluginsLoader/lua_plugins_loader.py
SantaSpeen 1b5ddbdd45 Refactor package.path;
Fix: Now .lua run from loadfrom(file);
2023-07-22 00:20:24 +03:00

563 lines
20 KiB
Python

import asyncio
import json
import os
import platform
import random
import shutil
from threading import Thread
from lupa.lua53 import LuaRuntime
from core import get_logger
# noinspection PyPep8Naming
class MP:
# In ./in_lua.lua
# MP.CreateTimer
# MP.Sleep
def __init__(self, name: str, lua: LuaRuntime):
self.loop = asyncio.get_event_loop()
self.log = get_logger(f"LuaPlugin | {name}")
self.name = name
self.tasks = []
self._lua = lua
self._local_events = {
"onInit": [], "onShutdown": [], "onPlayerAuth": [], "onPlayerConnecting": [], "onPlayerJoining": [],
"onPlayerJoin": [], "onPlayerDisconnect": [], "onChatMessage": [], "onVehicleSpawn": [],
"onVehicleEdited": [], "onVehicleDeleted": [], "onVehicleReset": [], "onFileChanged": []
}
def _print(self, *args):
s = " ".join(map(str, args))
self.log.info(s)
def GetOSName(self) -> str:
self.log.debug("request MP.GetOSName()")
pl = platform.system()
if pl in ["Linux", "Windows"]:
return pl
return "Other"
def GetServerVersion(self) -> tuple[int, int, int]:
self.log.debug("request MP.GetServerVersion()")
return ev.call_event("_get_BeamMP_version")[0]
def RegisterEvent(self, event_name: str, function_name: str) -> None:
self.log.debug("request MP.RegisterEvent()")
event_func = self._lua.globals()[function_name]
ev.register_event(event_name, event_func, lua=True)
if event_name not in self._local_events:
self._local_events.update({str(event_name): [event_func]})
else:
self._local_events[event_name].append(event_func)
self.log.debug("Register ok (local)")
def CreateEventTimer(self, event_name: str, interval_ms: int, strategy: int = None):
self.log.debug("request CreateEventTimer()")
# TODO: CreateEventTimer
def CancelEventTimer(self, event_name: str):
self.log.debug("request CancelEventTimer()")
# TODO: CancelEventTimer
def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()")
self.log.debug(f"Calling lcoal lua event: '{event_name}'")
funcs_data = []
if event_name in self._local_events.keys():
for func in self._local_events[event_name]:
try:
funcs_data.append(func(*args))
except Exception as e:
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
self.log.exception(e)
else:
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
f"Just skipping it...")
return self._lua.table_from({i: v for i, v in enumerate(funcs_data)})
def TriggerGlobalEvent(self, event_name, *args):
self.log.debug("request TriggerGlobalEvent()")
return self._lua.table(
IsDone=lambda: True,
GetResults=lambda: self._lua.table_from({i: v for i, v in enumerate(ev.call_lua_event(event_name, *args))})
)
def SendChatMessage(self, player_id, message):
self.log.debug("request SendChatMessage()")
client = ev.call_event("_get_player", cid=player_id)[0]
to_all = False
if player_id < 0:
to_all = True
client = client[0]
if client and message:
t = self.loop.create_task(client.send_message(f"Server: {message}", to_all=to_all))
self.tasks.append(t)
def TriggerClientEvent(self, player_id, event_name, data):
self.log.debug("request TriggerClientEvent()")
client = ev.call_event("_get_player", cid=player_id)[0]
to_all = False
if player_id < 0:
to_all = True
client = client[0]
if client and event_name and data:
t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all))
self.tasks.append(t)
return True, None
elif not client:
return False, "Client expired"
else:
return False, "Can't found event_name or data"
def TriggerClientEventJson(self, player_id, event_name, data):
self.log.debug("request TriggerClientEventJson()")
data = self._lua.globals().Util.JsonEncode(data)
self.TriggerClientEvent(player_id, event_name, data)
def GetPlayerCount(self):
self.log.debug("request GetPlayerCount()")
return len(ev.call_event("_get_player", cid=-1)[0])
def GetPositionRaw(self, player_id, car_id):
self.log.debug("request GetPositionRaw()")
if player_id < 0:
return self._lua.table(), "Bad client"
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
car = client.cars[car_id]
if car:
return self._lua.table_from(car['pos'])
return self._lua.table(), "Vehicle not found"
return self._lua.table(), "Client expired"
def IsPlayerConnected(self, player_id):
self.log.debug("request IsPlayerConnected()")
if player_id < 0:
return False
return bool(ev.call_event("_get_player", cid=player_id)[0])
def GetPlayerName(self, player_id):
self.log.debug("request GetPlayerName()")
if player_id < 0:
return None
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return client.nick
return
def RemoveVehicle(self, player_id, vehicle_id):
self.log.debug("request RemoveVehicle()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client._delete_car(car_id=vehicle_id))
self.tasks.append(t)
def GetPlayerVehicles(self, player_id):
self.log.debug("request GetPlayerVehicles()")
if player_id < 0:
return self._lua.table()
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from(
{i: f'{v["json"]}' for i, d in enumerate([i for i in client.cars if i is not None]) for k, v in
d.items() if k == "json"})
def GetPlayers(self):
self.log.debug("request GetPlayers()")
clients = ev.call_event("_get_players", cid=-1)
return self._lua.table_from({i: n for i, n in enumerate(clients)})
def IsPlayerGuest(self, player_id) -> bool:
self.log.debug("request IsPlayerGuest()")
if player_id < 0:
return True
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return client.guest
return False
def DropPlayer(self, player_id, reason="Kicked"):
self.log.debug("request DropPlayer()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client.kick(reason))
self.tasks.append(t)
def GetStateMemoryUsage(self):
self.log.debug("request GetStateMemoryUsage()")
return self._lua.get_memory_used()
def GetLuaMemoryUsage(self):
self.log.debug("request GetStateMemoryUsage()")
lua_plugins = ev.call_event("_lua_plugins_get")[0]
return sum(pl['lua'].get_memory_used() for pls in lua_plugins.values() for pl in pls.values())
def GetPlayerIdentifiers(self, player_id):
self.log.debug("request GetStateMemoryUsage()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from(client.identifiers)
return self._lua.table()
def Set(self, *args):
self.log.debug("request Set")
self.log.warning("KuiToi cannot support this: MP.Set()")
def Settings(self, *args):
self.log.debug("request Set")
self.log.warning("KuiToi cannot support this: MP.Settings()")
# noinspection PyPep8Naming
class Util:
def __init__(self, name, lua):
self.log = get_logger(f"LuaPlugin | Util | {name}")
self.name = name
self._lua = lua
def _recursive_list_encode(self, table):
new_list = list(table.values())
for i, v in enumerate(list(table.values())):
if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)):
new_list[i] = None
continue
if "LuaTable" in str(type(v)):
d = dict(v)
if all(isinstance(ii, int) for ii in d.keys()):
new_list[i] = self._recursive_list_encode(d)
continue
else:
new_list[i] = self._recursive_dict_encode(d)
return [i for i in new_list if i is not None]
def _recursive_dict_encode(self, table):
for k, v in table.items():
if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)):
table[k] = None
continue
if "LuaTable" in str(type(v)):
d = dict(v)
if all(isinstance(i, int) for i in d.keys()):
table[k] = self._recursive_list_encode(d)
continue
else:
table[k] = self._recursive_dict_encode(d)
return {k: v for k, v in dict(table).items() if v is not None}
def JsonEncode(self, table):
self.log.debug("requesting JsonEncode()")
if all(isinstance(k, int) for k in table.keys()):
data = self._recursive_list_encode(table)
else:
data = self._recursive_dict_encode(table)
return json.dumps(data)
def JsonDecode(self, string):
self.log.debug("requesting JsonDecode()")
return self._lua.table_from(json.loads(string))
def JsonPrettify(self, string):
self.log.debug("requesting JsonPrettify()")
data = json.loads(string)
return json.dumps(data, indent=4, sort_keys=True)
def JsonMinify(self, string):
self.log.debug("requesting JsonMinify()")
data = json.loads(string)
return json.dumps(data, separators=(',', ':'))
def JsonFlatten(self, json_str):
self.log.debug("request JsonFlatten()")
json_obj = json.loads(json_str)
flat_obj = {}
def flatten(obj, path=''):
if isinstance(obj, dict):
for key in obj:
flatten(obj[key], path + '/' + key)
elif isinstance(obj, list):
for i in range(len(obj)):
flatten(obj[i], path + '/' + str(i))
else:
flat_obj[path] = obj
flatten(json_obj)
flat_json = json.dumps(flat_obj)
return flat_json
def JsonUnflatten(self, flat_json):
self.log.debug("request JsonUnflatten")
flat_obj = json.loads(flat_json)
def unflatten(obj):
result = {}
for key in obj:
parts = key.split('/')
d = result
for part in parts[:-1]:
if part not in d:
# create a new node in the dictionary
# if the path doesn't exist
d[part] = {}
d = d[part]
# assign the value to the last part of the path
d[parts[-1]] = obj[key]
return result
json_obj = unflatten(flat_obj)
return json.dumps(json_obj)
def JsonDiff(self, a: str, b: str) -> str:
self.log.debug("requesting JsonDiff()")
a_obj = json.loads(a)
b_obj = json.loads(b)
diff = []
for k, v in b_obj.items():
if k not in a_obj:
diff.append({"op": "add", "path": "/" + k, "value": v})
elif a_obj[k] != v:
diff.append({"op": "replace", "path": "/" + k, "value": v})
for k in a_obj.keys() - b_obj.keys():
diff.append({"op": "remove", "path": "/" + k})
return json.dumps(diff)
@staticmethod
def _apply_patch(base_obj, patch_obj):
for patch in patch_obj:
op = patch['op']
path = patch['path']
value = patch.get('value', None)
tokens = path.strip('/').split('/')
obj = base_obj
for i, token in enumerate(tokens):
if isinstance(obj, list):
token = int(token)
if i == len(tokens) - 1:
if op == 'add':
if isinstance(obj, list):
obj.insert(int(token), value)
else:
obj[token] = value
elif op == 'replace':
obj[token] = value
elif op == 'remove':
if isinstance(obj, list):
obj.pop(int(token))
else:
del obj[token]
else:
obj = obj[token]
return base_obj
def JsonDiffApply(self, base: str, diff: str) -> str:
self.log.debug("requesting JsonDiffApply()")
base_obj = json.loads(base)
diff_obj = json.loads(diff)
result = self._apply_patch(base_obj, diff_obj)
return json.dumps(result)
def Random(self) -> float:
self.log.debug("requesting Random()")
return random.random()
def RandomIntRange(self, min_v, max_v) -> int:
self.log.debug("requesting RandomIntRange()")
return random.randint(min_v, max_v)
def RandomRange(self, min_v, max_v) -> float:
self.log.debug("requesting RandomRange()")
return random.uniform(min_v, max_v)
# noinspection PyPep8Naming
class FP:
def __init__(self, name: str, lua: LuaRuntime):
self.log = get_logger(f"LuaPlugin | FP | {name}")
self.name = name
self._lua = lua
def CreateDirectory(self, path):
self.log.debug("requesting CreateDirectory()")
try:
os.makedirs(path)
return True, None
except FileNotFoundError | NotADirectoryError as e:
return False, f"{e}"
except PermissionError as e:
return False, f"{e}"
except OSError as e:
return False, f"{e}"
except TypeError as e:
return False, f"{e}"
except ValueError as e:
return False, f"{e}"
def Remove(self, path):
self.log.debug("requesting Remove()")
try:
if os.path.isdir(path):
os.rmdir(path)
else:
os.remove(path)
return True, None
except (FileNotFoundError, NotADirectoryError) as e:
return False, f"{e}"
except PermissionError as e:
return False, f"{e}"
except OSError as e:
return False, f"{e}"
except TypeError as e:
return False, f"{e}"
def Rename(self, path_from, path_to):
self.log.debug("requesting Rename()")
try:
os.rename(path_from, path_to)
return True, None
except (FileNotFoundError, NotADirectoryError) as e:
return False, f"{e}"
except PermissionError as e:
return False, f"{e}"
except OSError as e:
return False, f"{e}"
except TypeError as e:
return False, f"{e}"
def Copy(self, path_from, path_to):
self.log.debug("requesting Copy()")
try:
if os.path.isfile(path_from):
shutil.copy2(path_from, path_to)
elif os.path.isdir(path_from):
shutil.copytree(path_from, path_to)
else:
raise ValueError("Invalid path: {}".format(path_from))
return True, None
except (FileNotFoundError, NotADirectoryError, shutil.Error) as e:
return False, f"{e}"
except PermissionError as e:
return False, f"{e}"
except OSError as e:
return False, f"{e}"
except TypeError as e:
return False, f"{e}"
def GetFilename(self, path):
self.log.debug("requesting GetFilename()")
return os.path.basename(path)
def GetExtension(self, path):
self.log.debug("requesting GetExtension()")
return os.path.splitext(path)[1]
def GetParentFolder(self, path):
self.log.debug("requesting GetParentFolder()")
return os.path.dirname(path)
def Exists(self, path):
self.log.debug("requesting Exists()")
return os.path.exists(path)
def IsDirectory(self, path):
self.log.debug("requesting IsDirectory()")
return os.path.isdir(path)
def IsFile(self, path):
self.log.debug("requesting IsFile()")
return os.path.isfile(path)
def ListDirectories(self, path):
self.log.debug("requesting ListDirectories()")
directories = []
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isdir(item_path):
directories.append(item)
return self._lua.table_from({i: v for i, v in enumerate(directories)})
def ListFiles(self, path):
self.log.debug("requesting ListFiles()")
files = []
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isfile(item_path):
files.append(item)
return self._lua.table_from({i: v for i, v in enumerate(files)})
def ConcatPaths(self, *args):
self.log.debug("requesting ConcatPaths()")
return os.path.join(*args)
class LuaPluginsLoader:
def __init__(self, plugins_dir):
self.loop = asyncio.get_event_loop()
self.plugins_dir = plugins_dir
self.lua_plugins = {}
self.lua_plugins_tasks = []
self.lua_dirs = []
self.log = get_logger("LuaPluginsLoader")
self.loaded_str = "Lua plugins: "
ev.register_event("_lua_plugins_get", lambda x: self.lua_plugins)
ev.register_event("_lua_plugins_unload", self.unload)
console.add_command("lua_plugins", lambda x: self.loaded_str[:-2])
console.add_command("lua_pl", lambda x: self.loaded_str[:-2])
def _start(self, obj, lua, file):
try:
f = lua.globals().loadfile(os.path.abspath(f"plugins/{obj}/{file}"))
f()
self.lua_plugins[obj]['ok'] = True
self.loaded_str += f"{obj}:ok, "
lua.globals().MP.TriggerLocalEvent("onInit")
except Exception as e:
self.loaded_str += f"{obj}:no, "
self.log.error(f"Cannot load lua plugin from `{obj}/main.lua`\n{e}")
# self.log.exception(e)
def load(self):
self.log.debug("Loading Lua plugins...")
py_folders = ev.call_event("_plugins_get")[0]
for obj in os.listdir(self.plugins_dir):
path = os.path.join(self.plugins_dir, obj)
if os.path.isdir(path) and obj not in py_folders and obj not in "__pycache__":
if os.path.isfile(os.path.join(path, "main.lua")):
self.lua_dirs.append([path, obj])
self.log.debug(f"py_folders {py_folders}, lua_dirs {self.lua_dirs}")
for path, obj in self.lua_dirs:
# noinspection PyArgumentList
lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc, unpack_returned_tuples=True)
lua.globals().printRaw = lua.globals().print
lua.globals().exit = lambda x: self.log.info(f"{obj}: You can't disable server..")
mp = MP(obj, lua)
lua.globals().MP = mp
lua.globals().print = mp._print
lua.globals().Util = Util(obj, lua)
lua.globals().FP = FP(obj, lua)
pa = os.path.abspath(self.plugins_dir)
p0 = os.path.join(pa, obj, "?.lua")
p1 = os.path.join(pa, obj, "lua", "?.lua")
lua.globals().package.path += f';{p0};{p1}'
# with open("modules/PluginsLoader/add_in.lua", "r") as f:
# code += f.read()
self.lua_plugins.update({obj: {"mp": mp, "lua": lua, "thread": None, "ok": False}})
th = Thread(target=self._start, args=(obj, lua, "main.lua"), name=f"lua_plugin_{obj}-Thread")
th.start()
self.lua_plugins[obj]['thread'] = th
def unload(self, _):
...