mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
feat(federation): ✨ Add ActivityPub bridge support with CLI command
This commit is contained in:
parent
153aa061f0
commit
ff315af230
13 changed files with 2337 additions and 15 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import { parseUserAddress, userAddressValidator } from "@/api";
|
||||
import { Args, type Command, Flags, type Interfaces } from "@oclif/core";
|
||||
import chalk from "chalk";
|
||||
import { and, eq, getTableColumns, like } from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis, Instances, Users } from "~/drizzle/schema";
|
||||
import { Instance } from "~/packages/database-interface/instance";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import { BaseCommand } from "./base";
|
||||
|
||||
|
|
@ -25,8 +27,15 @@ export abstract class UserFinderCommand<
|
|||
type: Flags.string({
|
||||
char: "t",
|
||||
description: "Type of identifier",
|
||||
options: ["id", "username", "note", "display-name", "email"],
|
||||
default: "id",
|
||||
options: [
|
||||
"id",
|
||||
"username",
|
||||
"note",
|
||||
"display-name",
|
||||
"email",
|
||||
"address",
|
||||
],
|
||||
default: "address",
|
||||
}),
|
||||
limit: Flags.integer({
|
||||
char: "n",
|
||||
|
|
@ -44,7 +53,7 @@ export abstract class UserFinderCommand<
|
|||
static baseArgs = {
|
||||
identifier: Args.string({
|
||||
description:
|
||||
"Identifier of the user (by default this must be an ID)",
|
||||
"Identifier of the user (by default this must be an address, i.e. name@host.com)",
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
|
@ -78,10 +87,26 @@ export abstract class UserFinderCommand<
|
|||
|
||||
const operator = this.flags.pattern ? like : eq;
|
||||
// Replace wildcards with an SQL LIKE pattern
|
||||
const identifier = this.flags.pattern
|
||||
const identifier: string = this.flags.pattern
|
||||
? this.args.identifier.replace(/\*/g, "%")
|
||||
: this.args.identifier;
|
||||
|
||||
if (this.flags.type === "address") {
|
||||
// Check if the address is valid
|
||||
if (!userAddressValidator.exec(identifier)) {
|
||||
this.log(
|
||||
"Invalid address. Please check the address format and try again. For example: name@host.com",
|
||||
);
|
||||
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
// Check instance exists, if not, create it
|
||||
await Instance.resolve(
|
||||
`https://${parseUserAddress(identifier).domain}`,
|
||||
);
|
||||
}
|
||||
|
||||
return await User.manyFromSql(
|
||||
and(
|
||||
this.flags.type === "id"
|
||||
|
|
@ -99,6 +124,30 @@ export abstract class UserFinderCommand<
|
|||
this.flags.type === "email"
|
||||
? operator(Users.email, identifier)
|
||||
: undefined,
|
||||
this.flags.type === "address"
|
||||
? and(
|
||||
operator(
|
||||
Users.username,
|
||||
parseUserAddress(identifier).username,
|
||||
),
|
||||
operator(
|
||||
Users.instanceId,
|
||||
(
|
||||
await Instance.fromSql(
|
||||
eq(
|
||||
Instances.baseUrl,
|
||||
new URL(
|
||||
`https://${
|
||||
parseUserAddress(identifier)
|
||||
.domain
|
||||
}`,
|
||||
).host,
|
||||
),
|
||||
)
|
||||
)?.id ?? "",
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
undefined,
|
||||
this.flags.limit,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { parseUserAddress, userAddressValidator } from "@/api";
|
||||
import { SignatureConstructor } from "@lysand-org/federation";
|
||||
import { FederationRequester } from "@lysand-org/federation/requester";
|
||||
import { Args } from "@oclif/core";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { BaseCommand } from "~/cli/base";
|
||||
import { Instance } from "~/packages/database-interface/instance";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
||||
export default class FederationUserFetch extends BaseCommand<
|
||||
|
|
@ -16,7 +18,7 @@ export default class FederationUserFetch extends BaseCommand<
|
|||
}),
|
||||
};
|
||||
|
||||
static override description = "Fetch the URL of remote users via WebFinger";
|
||||
static override description = "Fetch remote users";
|
||||
|
||||
static override examples = ["<%= config.bin %> <%= command.id %>"];
|
||||
|
||||
|
|
@ -25,9 +27,21 @@ export default class FederationUserFetch extends BaseCommand<
|
|||
public async run(): Promise<void> {
|
||||
const { args } = await this.parse(FederationUserFetch);
|
||||
|
||||
const spinner = ora("Fetching user URI").start();
|
||||
// Check if the address is valid
|
||||
if (!args.address.match(userAddressValidator)) {
|
||||
this.log(
|
||||
"Invalid address. Please check the address format and try again. For example: name@host.com",
|
||||
);
|
||||
|
||||
const [username, host] = args.address.split("@");
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
const spinner = ora("Fetching user").start();
|
||||
|
||||
const { username, domain: host } = parseUserAddress(args.address);
|
||||
|
||||
// Check instance exists, if not, create it
|
||||
await Instance.resolve(`https://${host}`);
|
||||
|
||||
const requester = await User.getServerActor();
|
||||
|
||||
|
|
@ -42,9 +56,15 @@ export default class FederationUserFetch extends BaseCommand<
|
|||
|
||||
const uri = await manager.webFinger(username);
|
||||
|
||||
spinner.succeed("Fetched user URI");
|
||||
const newUser = await User.resolve(uri);
|
||||
|
||||
this.log(`URI: ${chalk.blueBright(uri)}`);
|
||||
if (newUser) {
|
||||
spinner.succeed();
|
||||
this.log(chalk.green(`User found: ${newUser.getUri()}`));
|
||||
} else {
|
||||
spinner.fail();
|
||||
this.log(chalk.red("User not found"));
|
||||
}
|
||||
|
||||
this.exit(0);
|
||||
}
|
||||
|
|
|
|||
65
cli/commands/federation/user/finger.ts
Normal file
65
cli/commands/federation/user/finger.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { parseUserAddress, userAddressValidator } from "@/api";
|
||||
import { SignatureConstructor } from "@lysand-org/federation";
|
||||
import { FederationRequester } from "@lysand-org/federation/requester";
|
||||
import { Args } from "@oclif/core";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { BaseCommand } from "~/cli/base";
|
||||
import { Instance } from "~/packages/database-interface/instance";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
||||
export default class FederationUserFinger extends BaseCommand<
|
||||
typeof FederationUserFinger
|
||||
> {
|
||||
static override args = {
|
||||
address: Args.string({
|
||||
description: "Address of remote user (name@host.com)",
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
static override description = "Fetch the URL of remote users via WebFinger";
|
||||
|
||||
static override examples = ["<%= config.bin %> <%= command.id %>"];
|
||||
|
||||
static override flags = {};
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const { args } = await this.parse(FederationUserFinger);
|
||||
|
||||
// Check if the address is valid
|
||||
if (!args.address.match(userAddressValidator)) {
|
||||
this.log(
|
||||
"Invalid address. Please check the address format and try again. For example: name@host.com",
|
||||
);
|
||||
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
const spinner = ora("Fetching user URI").start();
|
||||
|
||||
const { username, domain: host } = parseUserAddress(args.address);
|
||||
|
||||
// Check instance exists, if not, create it
|
||||
await Instance.resolve(`https://${host}`);
|
||||
|
||||
const requester = await User.getServerActor();
|
||||
|
||||
const signatureConstructor = await SignatureConstructor.fromStringKey(
|
||||
requester.data.privateKey ?? "",
|
||||
requester.getUri(),
|
||||
);
|
||||
const manager = new FederationRequester(
|
||||
new URL(`https://${host}`),
|
||||
signatureConstructor,
|
||||
);
|
||||
|
||||
const uri = await manager.webFinger(username);
|
||||
|
||||
spinner.succeed("Fetched user URI");
|
||||
|
||||
this.log(`URI: ${chalk.blueBright(uri)}`);
|
||||
|
||||
this.exit(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import EmojiImport from "./commands/emoji/import";
|
|||
import EmojiList from "./commands/emoji/list";
|
||||
import FederationInstanceFetch from "./commands/federation/instance/fetch";
|
||||
import FederationUserFetch from "./commands/federation/user/fetch";
|
||||
import FederationUserFinger from "./commands/federation/user/finger";
|
||||
import IndexRebuild from "./commands/index/rebuild";
|
||||
import Start from "./commands/start";
|
||||
import UserCreate from "./commands/user/create";
|
||||
|
|
@ -29,6 +30,7 @@ export const commands = {
|
|||
"emoji:import": EmojiImport,
|
||||
"index:rebuild": IndexRebuild,
|
||||
"federation:instance:fetch": FederationInstanceFetch,
|
||||
"federation:user:finger": FederationUserFinger,
|
||||
"federation:user:fetch": FederationUserFetch,
|
||||
start: Start,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue