diff --git a/maubot/cli/commands/build.py b/maubot/cli/commands/build.py
index 7d1a687..cd359e0 100644
--- a/maubot/cli/commands/build.py
+++ b/maubot/cli/commands/build.py
@@ -13,20 +13,22 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from typing import Optional
+from typing import Optional, Union, IO
from io import BytesIO
import zipfile
import os
from mautrix.client.api.types.util import SerializerError
from ruamel.yaml import YAML, YAMLError
-from colorama import Fore, Style
+from colorama import Fore
from PyInquirer import prompt
import click
from ...loader import PluginMeta
-from ..base import app
from ..cliq.validators import PathValidator
+from ..base import app
+from ..config import config
+from .upload import upload_file, UploadError
yaml = YAML()
@@ -44,16 +46,16 @@ def read_meta(path: str) -> Optional[PluginMeta]:
meta_dict = yaml.load(meta_file)
except YAMLError as e:
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
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
try:
meta = PluginMeta.deserialize(meta_dict)
except SerializerError as e:
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 meta
@@ -77,7 +79,7 @@ def read_output_path(output: str, meta: PluginMeta) -> Optional[str]:
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:
meta_dump = BytesIO()
yaml.dump(meta.serialize(), meta_dump)
@@ -89,12 +91,26 @@ def write_plugin(meta: PluginMeta, output: str) -> None:
elif os.path.isdir(module):
zipdir(zip, module)
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:
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",
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.")
@@ -105,9 +121,16 @@ def write_plugin(meta: PluginMeta, output: str) -> None:
default=False)
def build(path: str, output: str, upload: bool) -> None:
meta = read_meta(path)
- output = read_output_path(output, meta)
- if not output:
- return
+ if output or not upload:
+ output = read_output_path(output, meta)
+ if not output:
+ return
+ else:
+ output = BytesIO()
os.chdir(path)
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)
diff --git a/maubot/cli/commands/login.py b/maubot/cli/commands/login.py
index 7cc85f1..aa138a9 100644
--- a/maubot/cli/commands/login.py
+++ b/maubot/cli/commands/login.py
@@ -18,7 +18,7 @@ from urllib.error import HTTPError
import json
import os
-from colorama import Fore, Style
+from colorama import Fore
from ..config import save_config, config
from ..cliq import cliq
@@ -38,8 +38,9 @@ def login(server, username, password) -> None:
data=json.dumps(data).encode("utf-8")) as resp_data:
resp = json.load(resp_data)
config["servers"][server] = resp["token"]
+ config["default_server"] = server
save_config()
print(Fore.GREEN + "Logged in successfully")
except HTTPError as e:
if e.code == 401:
- print(Fore.RED + "Invalid username or password" + Style.RESET_ALL)
+ print(Fore.RED + "Invalid username or password" + Fore.RESET)
diff --git a/maubot/cli/commands/upload.py b/maubot/cli/commands/upload.py
index 43a9850..f307f6f 100644
--- a/maubot/cli/commands/upload.py
+++ b/maubot/cli/commands/upload.py
@@ -13,12 +13,53 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from urllib.request import urlopen, Request
+from urllib.error import HTTPError
+from typing import IO, Tuple
+import json
+
+from colorama import Fore
import click
from ..base import app
+from ..config import config
+
+
+class UploadError(Exception):
+ pass
@app.command(help="Upload a maubot plugin")
+@click.argument("path")
@click.option("-s", "--server", help="The maubot instance to upload the plugin to")
-def upload(server: str) -> None:
- pass
+def upload(path: str, server: str) -> None:
+ 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)
diff --git a/maubot/cli/config.py b/maubot/cli/config.py
index fba278a..653ffef 100644
--- a/maubot/cli/config.py
+++ b/maubot/cli/config.py
@@ -17,7 +17,8 @@ import json
import os
config = {
- "servers": {}
+ "servers": {},
+ "default_server": None,
}
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:
loaded = json.load(file)
config["servers"] = loaded["servers"]
+ config["default_server"] = loaded["default_server"]
except FileNotFoundError:
pass
diff --git a/maubot/loader/zip.py b/maubot/loader/zip.py
index e341cff..8e1cfbd 100644
--- a/maubot/loader/zip.py
+++ b/maubot/loader/zip.py
@@ -185,7 +185,7 @@ class ZippedPluginLoader(PluginLoader):
importer = self._get_importer(reset_cache=reset_cache)
self._run_preload_checks(importer)
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:
try:
importer.load_module(module)
diff --git a/maubot/management/api/plugin.py b/maubot/management/api/plugin.py
index 421584d..d0c79f8 100644
--- a/maubot/management/api/plugin.py
+++ b/maubot/management/api/plugin.py
@@ -124,10 +124,10 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes,
new_version: Version) -> web.Response:
dirname = os.path.dirname(plugin.path)
old_filename = os.path.basename(plugin.path)
- if plugin.version in old_filename:
- replacement = (new_version if plugin.version != new_version
+ if str(plugin.meta.version) in old_filename:
+ replacement = (new_version if plugin.meta.version != new_version
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)
else:
filename = old_filename.rstrip(".mbp")