Implement uploading plugins
This commit is contained in:
parent
c334afd38b
commit
cb3993d79f
@ -13,20 +13,22 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Optional
|
from typing import Optional, Union, IO
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import zipfile
|
import zipfile
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from mautrix.client.api.types.util import SerializerError
|
from mautrix.client.api.types.util import SerializerError
|
||||||
from ruamel.yaml import YAML, YAMLError
|
from ruamel.yaml import YAML, YAMLError
|
||||||
from colorama import Fore, Style
|
from colorama import Fore
|
||||||
from PyInquirer import prompt
|
from PyInquirer import prompt
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from ...loader import PluginMeta
|
from ...loader import PluginMeta
|
||||||
from ..base import app
|
|
||||||
from ..cliq.validators import PathValidator
|
from ..cliq.validators import PathValidator
|
||||||
|
from ..base import app
|
||||||
|
from ..config import config
|
||||||
|
from .upload import upload_file, UploadError
|
||||||
|
|
||||||
yaml = YAML()
|
yaml = YAML()
|
||||||
|
|
||||||
@ -44,16 +46,16 @@ def read_meta(path: str) -> Optional[PluginMeta]:
|
|||||||
meta_dict = yaml.load(meta_file)
|
meta_dict = yaml.load(meta_file)
|
||||||
except YAMLError as e:
|
except YAMLError as e:
|
||||||
print(Fore.RED + "Failed to build plugin: Metadata file is not YAML")
|
print(Fore.RED + "Failed to build plugin: Metadata file is not YAML")
|
||||||
print(Fore.RED + str(e) + Style.RESET_ALL)
|
print(Fore.RED + str(e) + Fore.RESET)
|
||||||
return None
|
return None
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(Fore.RED + "Failed to build plugin: Metadata file not found" + Style.RESET_ALL)
|
print(Fore.RED + "Failed to build plugin: Metadata file not found" + Fore.RESET)
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
meta = PluginMeta.deserialize(meta_dict)
|
meta = PluginMeta.deserialize(meta_dict)
|
||||||
except SerializerError as e:
|
except SerializerError as e:
|
||||||
print(Fore.RED + "Failed to build plugin: Metadata file is not valid")
|
print(Fore.RED + "Failed to build plugin: Metadata file is not valid")
|
||||||
print(Fore.RED + str(e) + Style.RESET_ALL)
|
print(Fore.RED + str(e) + Fore.RESET)
|
||||||
return None
|
return None
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ def read_output_path(output: str, meta: PluginMeta) -> Optional[str]:
|
|||||||
return os.path.abspath(output)
|
return os.path.abspath(output)
|
||||||
|
|
||||||
|
|
||||||
def write_plugin(meta: PluginMeta, output: str) -> None:
|
def write_plugin(meta: PluginMeta, output: Union[str, IO]) -> None:
|
||||||
with zipfile.ZipFile(output, "w") as zip:
|
with zipfile.ZipFile(output, "w") as zip:
|
||||||
meta_dump = BytesIO()
|
meta_dump = BytesIO()
|
||||||
yaml.dump(meta.serialize(), meta_dump)
|
yaml.dump(meta.serialize(), meta_dump)
|
||||||
@ -89,12 +91,26 @@ def write_plugin(meta: PluginMeta, output: str) -> None:
|
|||||||
elif os.path.isdir(module):
|
elif os.path.isdir(module):
|
||||||
zipdir(zip, module)
|
zipdir(zip, module)
|
||||||
else:
|
else:
|
||||||
print(Fore.YELLOW + f"Module {module} not found, skipping")
|
print(Fore.YELLOW + f"Module {module} not found, skipping" + Fore.RESET)
|
||||||
|
|
||||||
for file in meta.extra_files:
|
for file in meta.extra_files:
|
||||||
zip.write(file)
|
zip.write(file)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_plugin(output: Union[str, IO]) -> None:
|
||||||
|
try:
|
||||||
|
server = config["default_server"]
|
||||||
|
token = config["servers"][server]
|
||||||
|
except KeyError:
|
||||||
|
print(Fore.RED + "Default server not configured." + Fore.RESET)
|
||||||
|
return
|
||||||
|
if isinstance(output, str):
|
||||||
|
with open(output, "rb") as file:
|
||||||
|
upload_file(file, server, token)
|
||||||
|
else:
|
||||||
|
upload_file(output, server, token)
|
||||||
|
|
||||||
|
|
||||||
@app.command(short_help="Build a maubot plugin",
|
@app.command(short_help="Build a maubot plugin",
|
||||||
help="Build a maubot plugin. First parameter is the path to root of the plugin "
|
help="Build a maubot plugin. First parameter is the path to root of the plugin "
|
||||||
"to build. You can also use --output to specify output file.")
|
"to build. You can also use --output to specify output file.")
|
||||||
@ -105,9 +121,16 @@ def write_plugin(meta: PluginMeta, output: str) -> None:
|
|||||||
default=False)
|
default=False)
|
||||||
def build(path: str, output: str, upload: bool) -> None:
|
def build(path: str, output: str, upload: bool) -> None:
|
||||||
meta = read_meta(path)
|
meta = read_meta(path)
|
||||||
output = read_output_path(output, meta)
|
if output or not upload:
|
||||||
if not output:
|
output = read_output_path(output, meta)
|
||||||
return
|
if not output:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
output = BytesIO()
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
write_plugin(meta, output)
|
write_plugin(meta, output)
|
||||||
print(Fore.GREEN + "Plugin build complete.")
|
output.seek(0)
|
||||||
|
if isinstance(output, str):
|
||||||
|
print(f"{Fore.GREEN}Plugin built to {Fore.CYAN}{path}{Fore.GREEN}.{Fore.RESET}")
|
||||||
|
if upload:
|
||||||
|
upload_plugin(output)
|
||||||
|
@ -18,7 +18,7 @@ from urllib.error import HTTPError
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from colorama import Fore, Style
|
from colorama import Fore
|
||||||
|
|
||||||
from ..config import save_config, config
|
from ..config import save_config, config
|
||||||
from ..cliq import cliq
|
from ..cliq import cliq
|
||||||
@ -38,8 +38,9 @@ def login(server, username, password) -> None:
|
|||||||
data=json.dumps(data).encode("utf-8")) as resp_data:
|
data=json.dumps(data).encode("utf-8")) as resp_data:
|
||||||
resp = json.load(resp_data)
|
resp = json.load(resp_data)
|
||||||
config["servers"][server] = resp["token"]
|
config["servers"][server] = resp["token"]
|
||||||
|
config["default_server"] = server
|
||||||
save_config()
|
save_config()
|
||||||
print(Fore.GREEN + "Logged in successfully")
|
print(Fore.GREEN + "Logged in successfully")
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
if e.code == 401:
|
if e.code == 401:
|
||||||
print(Fore.RED + "Invalid username or password" + Style.RESET_ALL)
|
print(Fore.RED + "Invalid username or password" + Fore.RESET)
|
||||||
|
@ -13,12 +13,53 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from urllib.request import urlopen, Request
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
from typing import IO, Tuple
|
||||||
|
import json
|
||||||
|
|
||||||
|
from colorama import Fore
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from ..base import app
|
from ..base import app
|
||||||
|
from ..config import config
|
||||||
|
|
||||||
|
|
||||||
|
class UploadError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@app.command(help="Upload a maubot plugin")
|
@app.command(help="Upload a maubot plugin")
|
||||||
|
@click.argument("path")
|
||||||
@click.option("-s", "--server", help="The maubot instance to upload the plugin to")
|
@click.option("-s", "--server", help="The maubot instance to upload the plugin to")
|
||||||
def upload(server: str) -> None:
|
def upload(path: str, server: str) -> None:
|
||||||
pass
|
if not server:
|
||||||
|
try:
|
||||||
|
server = config["default_server"]
|
||||||
|
except KeyError:
|
||||||
|
print(Fore.RED + "Default server not configured" + Fore.RESET)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
token = config["servers"][server]
|
||||||
|
except KeyError:
|
||||||
|
print(Fore.RED + "Server not found" + Fore.RESET)
|
||||||
|
return
|
||||||
|
with open(path, "rb") as file:
|
||||||
|
upload_file(file, server, token)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file(file: IO, server: str, token: str) -> None:
|
||||||
|
req = Request(f"{server}/_matrix/maubot/v1/plugins/upload?allow_override=true", data=file,
|
||||||
|
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/zip"})
|
||||||
|
try:
|
||||||
|
with urlopen(req) as resp_data:
|
||||||
|
resp = json.load(resp_data)
|
||||||
|
print(f"{Fore.GREEN}Plugin {Fore.CYAN}{resp['id']} v{resp['version']}{Fore.GREEN} "
|
||||||
|
f"uploaded to {Fore.CYAN}{server}{Fore.GREEN} successfully.{Fore.RESET}")
|
||||||
|
except HTTPError as e:
|
||||||
|
try:
|
||||||
|
err = json.load(e)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
err = {}
|
||||||
|
print(err.get("stacktrace", ""))
|
||||||
|
print(Fore.RED + "Failed to upload plugin: " + err.get("error", str(e)) + Fore.RESET)
|
||||||
|
@ -17,7 +17,8 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"servers": {}
|
"servers": {},
|
||||||
|
"default_server": None,
|
||||||
}
|
}
|
||||||
configdir = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.environ.get("HOME"), ".config"))
|
configdir = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.environ.get("HOME"), ".config"))
|
||||||
|
|
||||||
@ -32,5 +33,6 @@ def load_config() -> None:
|
|||||||
with open(f"{configdir}/maubot-cli.json") as file:
|
with open(f"{configdir}/maubot-cli.json") as file:
|
||||||
loaded = json.load(file)
|
loaded = json.load(file)
|
||||||
config["servers"] = loaded["servers"]
|
config["servers"] = loaded["servers"]
|
||||||
|
config["default_server"] = loaded["default_server"]
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
@ -185,7 +185,7 @@ class ZippedPluginLoader(PluginLoader):
|
|||||||
importer = self._get_importer(reset_cache=reset_cache)
|
importer = self._get_importer(reset_cache=reset_cache)
|
||||||
self._run_preload_checks(importer)
|
self._run_preload_checks(importer)
|
||||||
if reset_cache:
|
if reset_cache:
|
||||||
self.log.debug(f"Re-preloaded plugin {self.meta.id} from {self.meta.path}")
|
self.log.debug(f"Re-preloaded plugin {self.meta.id} from {self.path}")
|
||||||
for module in self.meta.modules:
|
for module in self.meta.modules:
|
||||||
try:
|
try:
|
||||||
importer.load_module(module)
|
importer.load_module(module)
|
||||||
|
@ -124,10 +124,10 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes,
|
|||||||
new_version: Version) -> web.Response:
|
new_version: Version) -> web.Response:
|
||||||
dirname = os.path.dirname(plugin.path)
|
dirname = os.path.dirname(plugin.path)
|
||||||
old_filename = os.path.basename(plugin.path)
|
old_filename = os.path.basename(plugin.path)
|
||||||
if plugin.version in old_filename:
|
if str(plugin.meta.version) in old_filename:
|
||||||
replacement = (new_version if plugin.version != new_version
|
replacement = (new_version if plugin.meta.version != new_version
|
||||||
else f"{new_version}-ts{int(time())}")
|
else f"{new_version}-ts{int(time())}")
|
||||||
filename = re.sub(f"{re.escape(plugin.version)}(-ts[0-9]+)?",
|
filename = re.sub(f"{re.escape(str(plugin.meta.version))}(-ts[0-9]+)?",
|
||||||
replacement, old_filename)
|
replacement, old_filename)
|
||||||
else:
|
else:
|
||||||
filename = old_filename.rstrip(".mbp")
|
filename = old_filename.rstrip(".mbp")
|
||||||
|
Loading…
Reference in New Issue
Block a user