234 lines
5.4 KiB
Python
Executable File
234 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Create a reviewed command-extension skeleton without runtime privileges."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path[:0] = [
|
|
str(ROOT / "packages" / "jwebot-sdk" / "src"),
|
|
str(ROOT / "packages" / "jwebot-kernel" / "src"),
|
|
]
|
|
|
|
from jwebot_kernel import ManifestError, parse_manifest # noqa: E402
|
|
|
|
|
|
def module_name(plugin_id: str) -> str:
|
|
return plugin_id.replace("-", "_")
|
|
|
|
|
|
def manifest_text(
|
|
plugin_id: str,
|
|
command: str,
|
|
author: str,
|
|
description: str,
|
|
help_entry: str,
|
|
) -> str:
|
|
module = module_name(plugin_id)
|
|
return f"""id = "{plugin_id}"
|
|
author = "{author}"
|
|
description = {json.dumps(description, ensure_ascii=False)}
|
|
help = [{json.dumps(help_entry, ensure_ascii=False)}]
|
|
version = "1.0.0"
|
|
api_version = "1"
|
|
entrypoint = "{module}:plugin"
|
|
type = "command"
|
|
|
|
[capabilities]
|
|
commands = ["{command}"]
|
|
storage = false
|
|
network_profiles = []
|
|
service_profiles = []
|
|
"""
|
|
|
|
|
|
def module_text(plugin_id: str, command: str) -> str:
|
|
return f'''"""Command extension: {plugin_id}."""
|
|
|
|
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
|
|
|
|
|
def handle(event: Event, arguments: str) -> ExtensionResult:
|
|
del event, arguments
|
|
return ExtensionResult(reply=ReplyPlan.text("TODO"))
|
|
|
|
|
|
def plugin(context: SingleGroupContext) -> None:
|
|
context.commands.register("{command}", handle)
|
|
'''
|
|
|
|
|
|
def test_text(plugin_id: str) -> str:
|
|
module = module_name(plugin_id)
|
|
return f'''import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1] / "src"
|
|
sys.path.insert(0, str(SOURCE))
|
|
|
|
from jwebot_sdk import Event, PlanKind
|
|
|
|
from {module} import handle
|
|
|
|
|
|
class PluginTests(unittest.TestCase):
|
|
def test_command_returns_a_structured_reply(self):
|
|
event = Event(
|
|
event_id="event-1",
|
|
observed_at="2026-01-01T00:00:00+08:00",
|
|
sender="tester",
|
|
text="@JWeBot test",
|
|
group_fingerprint="group-test",
|
|
mention_bot=True,
|
|
)
|
|
result = handle(event, "")
|
|
self.assertEqual(result.reply.kind, PlanKind.TEXT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
'''
|
|
|
|
|
|
def readme_text(
|
|
plugin_id: str,
|
|
command: str,
|
|
author: str,
|
|
description: str,
|
|
help_entry: str,
|
|
) -> str:
|
|
return f"""# {plugin_id}
|
|
|
|
Author: `{author}`
|
|
|
|
{description}
|
|
|
|
Command: `{command}`
|
|
|
|
Help: `{help_entry}`
|
|
|
|
This extension may return structured JWeBot SDK results only. It must not use
|
|
GUI automation, raw storage, environment variables, subprocesses, SSH, Docker,
|
|
or unrestricted network access.
|
|
"""
|
|
|
|
|
|
def validate_inputs(
|
|
plugin_id: str,
|
|
command: str,
|
|
author: str,
|
|
description: str,
|
|
help_entry: str,
|
|
) -> None:
|
|
parse_manifest(
|
|
{
|
|
"id": plugin_id,
|
|
"author": author,
|
|
"description": description,
|
|
"help": [help_entry],
|
|
"version": "1.0.0",
|
|
"api_version": "1",
|
|
"entrypoint": f"{module_name(plugin_id)}:plugin",
|
|
"type": "command",
|
|
"capabilities": {
|
|
"commands": [command],
|
|
"storage": False,
|
|
"network_profiles": [],
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
def create_plugin(
|
|
output: Path,
|
|
plugin_id: str,
|
|
command: str,
|
|
author: str,
|
|
description: str,
|
|
help_entry: str | None = None,
|
|
) -> Path:
|
|
help_entry = help_entry or command
|
|
validate_inputs(
|
|
plugin_id,
|
|
command,
|
|
author,
|
|
description,
|
|
help_entry,
|
|
)
|
|
destination = output / plugin_id
|
|
if destination.exists():
|
|
raise FileExistsError(f"destination already exists: {destination}")
|
|
source = destination / "src"
|
|
tests = destination / "tests"
|
|
source.mkdir(parents=True)
|
|
tests.mkdir()
|
|
module = module_name(plugin_id)
|
|
(destination / "plugin.toml").write_text(
|
|
manifest_text(
|
|
plugin_id,
|
|
command,
|
|
author,
|
|
description,
|
|
help_entry,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(source / f"{module}.py").write_text(
|
|
module_text(plugin_id, command),
|
|
encoding="utf-8",
|
|
)
|
|
(tests / "test_plugin.py").write_text(
|
|
test_text(plugin_id),
|
|
encoding="utf-8",
|
|
)
|
|
(destination / "README.md").write_text(
|
|
readme_text(
|
|
plugin_id,
|
|
command,
|
|
author,
|
|
description,
|
|
help_entry,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
return destination
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("plugin_id")
|
|
parser.add_argument("command")
|
|
parser.add_argument("--author", required=True)
|
|
parser.add_argument("--description", required=True)
|
|
parser.add_argument("--help-entry")
|
|
parser.add_argument(
|
|
"--output",
|
|
type=Path,
|
|
default=ROOT / "extensions" / "friends",
|
|
)
|
|
args = parser.parse_args()
|
|
try:
|
|
destination = create_plugin(
|
|
args.output,
|
|
args.plugin_id,
|
|
args.command,
|
|
args.author,
|
|
args.description,
|
|
args.help_entry,
|
|
)
|
|
except (ManifestError, FileExistsError) as error:
|
|
parser.error(str(error))
|
|
print(destination)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|