Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: module installation via URL #237

Merged
merged 5 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,13 @@ try {
manager.installMultiple(modules, false);
manager.install(managerModule(manager), false);
manager.installMultiple(await ModuleManager.directory("externals"), true);
manager.installMultiple(
await ModuleManager.files(
Object.keys(localStorage).filter((v) => v.startsWith("module_")).map((v) =>
localStorage.getItem(v)!
),
),
true,
);
client.addEventHandler(manager.handler, new NewMessage({}));
client.start();
3 changes: 2 additions & 1 deletion module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ export function isModule(value: unknown): value is Module {
const obj = Object(value);
return (
typeof obj.name === "string" &&
/^[a-z]{1,10}$/.test(obj.name) &&
Array.isArray(obj.handlers) &&
obj.handlers.filter((handler: unknown) => handler instanceof Handler)
.length != undefined
.length != 0
);
}

Expand Down
106 changes: 71 additions & 35 deletions module_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,54 @@ export function managerModule(manager: ModuleManager): Module {
return {
name: "manager",
handlers: [
new CommandHandler("install", async ({ client, event }) => {
const reply = await event.message.getReplyMessage();
if (!reply) {
return;
}
const { media } = reply;
if (
!(media instanceof Api.MessageMediaDocument) ||
!(media.document instanceof Api.Document) ||
!(media.document.attributes[0] instanceof
Api.DocumentAttributeFilename) ||
!media.document.attributes[0].fileName.endsWith(".ts") ||
media.document.size.gt(5000)
) {
return;
}
const result = await client.downloadMedia(media, {});
if (!result) {
await updateMessage(event, "Could not download the module.");
return;
new CommandHandler("install", async ({ client, event, args }) => {
let spec = args[0] ?? "";
let path: string | undefined;
if (!spec) {
const reply = await event.message.getReplyMessage();
if (!reply) {
return;
}
const { media } = reply;
if (
!(media instanceof Api.MessageMediaDocument) ||
!(media.document instanceof Api.Document) ||
!(media.document.attributes[0] instanceof
Api.DocumentAttributeFilename) ||
!media.document.attributes[0].fileName.endsWith(".ts") ||
media.document.size.gt(5000)
) {
return;
}
const result = await client.downloadMedia(media, {});
if (!result) {
await updateMessage(event, "Could not download the module.");
return;
}
path = join(externals, `.${media.document.id}.ts`);
await Deno.writeTextFile(
path,
typeof result === "string" ? result : result.toString(),
);
spec = ModuleManager.pathToSpec(path);
}
const spec = join(externals, `.${media.document.id}.ts`);
await Deno.writeTextFile(
spec,
typeof result === "string" ? result : result.toString(),
);
let module;
try {
module = await ModuleManager.file(spec);
} catch (_err) {
await updateMessage(event, "The replied file is not a valid module.");
await updateMessage(event, "Not a module.");
return;
}
if (manager.modules.has(module.name)) {
await updateMessage(event, "Module already installed.");
return;
}
await Deno.rename(spec, join(externals, `${module.name}.ts`));
manager.install(module, true);
if (path) {
await Deno.rename(path, join(externals, `${module.name}.ts`));
} else {
localStorage.setItem(`module_${module.name}`, spec);
}
await updateMessage(event, "Module installed.");
}),
new CommandHandler("uninstall", async ({ event, args }) => {
Expand All @@ -64,6 +73,12 @@ export function managerModule(manager: ModuleManager): Module {
} catch (_err) {
//
}
const key = `module_${arg}`;
const item = localStorage.getItem(key);
if (item) {
localStorage.removeItem(key);
uninstalled++;
}
}
await updateMessage(
event,
Expand Down Expand Up @@ -254,34 +269,55 @@ export class ModuleManager {
}

static async file(spec: string) {
spec = toFileUrl(resolve(spec)).href;
const mod = (await import(spec)).default;
if (!isModule(mod)) {
throw new Error("Invalid module");
}
return mod;
}

static async files(specs: string[]) {
const modules = new Array<Module>();
let all = 0;
for (const spec of specs) {
try {
const module = await ModuleManager.file(spec);
modules.push(module);
} catch (err) {
log.warning(`failed to load ${spec}: ${err}`);
} finally {
all++;
}
}
log.info(`loaded ${modules.length}/${all} external modules`);
return modules;
}

static pathToSpec(path: string) {
return toFileUrl(resolve(path)).href;
}

static async directory(path: string) {
const modules = new Array<Module>();
let all = 0;
let loaded = 0;
for await (let { name, isFile, isDirectory } of Deno.readDir(path)) {
if ((!isFile && !isDirectory) || name.startsWith(".")) {
continue;
}
all++;
name = name.endsWith(".ts") ? name : `${name}/mod.ts`;
const spec = join(path, name);
const filePath = join(path, name);
try {
const mod = await this.file(spec);
const mod = await ModuleManager.file(
ModuleManager.pathToSpec(filePath),
);
modules.push(mod);
loaded++;
} catch (err) {
log.warning(`failed to load ${spec} from ${path}: ${err}`);
log.warning(`failed to load ${filePath} from ${path}: ${err}`);
} finally {
all++;
}
}
log.info(`loaded ${loaded}/${all} modules from ${path}`);
log.info(`loaded ${modules.length}/${all} external modules from ${path}`);
return modules;
}
}