Loading CHANGES.md +20 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,26 @@ To be released. - Added `MemoryKvStore.cas()` method. - Added `DenoKvStore.cas()` method. - Added useful functions for fediverse handles at `@fedify/fedify/vocab`. This functions simplify working with fediverse handles and URLs. - `FediverseHandle`: An interface representing a fediverse handle. - `parseFediverseHandle()`: A function to parse a fediverse handle into its components. - `isFediverseHandle()`: A function to check if a string is a valid fediverse handle. - `toAcctUrl()`: A function to convert a fediverse handle to a `URL`. - Added `fedify webfinger` command. This command allows users to look up WebFinger information for a given resource. - The input can be a handle (e.g., `@user@server`, `user@server`) or a URL (e.g., `https://server/users/path`). - The `--user-agent` or `-a` option used as `User-Agent` header value in the WebFinger request. - The `--allow-private-address` or `-p` option allows looking up WebFinger information for private addresses (e.g., `localhost`). Version 1.7.3 ------------- Loading cli/mod.ts +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ import { logFile, recordingSink } from "./log.ts"; import { command as lookup } from "./lookup.ts"; import { command as node } from "./node.ts"; import { command as tunnel } from "./tunnel.ts"; import { command as webfinger } from "./webfinger.ts"; const command = new Command() .name("fedify") Loading Loading @@ -65,6 +66,7 @@ const command = new Command() .command("node", node) .command("tunnel", tunnel) .command("completions", new CompletionsCommand()) .command("webfinger", webfinger) .command("help", new HelpCommand().global()); if (import.meta.main) { Loading cli/webfinger.ts 0 → 100644 +107 −0 Original line number Diff line number Diff line import { Command } from "@cliffy/command"; import { toAcctUrl } from "@fedify/fedify/vocab"; import { lookupWebFinger } from "@fedify/fedify/webfinger"; import ora from "ora"; import { printJson } from "./utils.ts"; export const command = new Command() .arguments("<...resources:string>") .description( "Look up a WebFinger resource by resource. The argument can be multiple.", ) .option( "-a, --user-agent <userAgent:string>", "The user agent to use for the request.", ) .option( "-p, --allow-private-address", "Allow private IP addresses in the URL.", ) .action(async (options, ...resources: string[]) => { for (const resource of resources) { const spinner = ora({ // Create a spinner for the lookup process text: `Looking up WebFinger for ${resource}`, discardStdin: false, }).start(); try { const url = convertUrlIfHandle(resource); // Convert resource to URL const webFinger = await lookupWebFinger(url, options) ?? // Look up WebFinger new NotFoundError(resource).throw(); // throw NotFoundError if not found spinner.succeed(`WebFinger found for ${resource}:`); // Succeed the spinner printJson(webFinger); // Print the WebFinger } catch (error) { if (error instanceof InvalidHandleError) { // If the handle format is invalid, spinner.fail(`Invalid handle format: ${error.handle}`); // log error message with handle } else if (error instanceof NotFoundError) { // If the resource is not found, spinner.fail(`Resource not found: ${error.resource}`); // log not found message } else if (error instanceof Error) { spinner.fail( // For other errors, log the error message `Error looking up WebFinger for ${resource}: ${error}`, ); } } } }); /** * Converts a handle or URL to a URL object. * If the input is a valid URL, it returns the URL object. * If the input is a handle in the format `@username@domain`, it converts it to a URL. * @param handleOrUrl The handle or URL to convert. * @returns A URL object representing the handle or URL. */ function convertUrlIfHandle(handleOrUrl: string): URL { try { return new URL(handleOrUrl); // Try to convert the input to a URL } catch { return convertHandleToUrl(handleOrUrl); // If it fails, treat it as a handle } } /** * Custom error class for invalid handle formats. * @param {string} handle The invalid handle that caused the error. * @extends {Error} */ class InvalidHandleError extends Error { constructor(public handle: string) { super(`Invalid handle format: ${handle}`); this.name = "InvalidHandleError"; } throw(): never { throw this; } } /** * Custom error class for not found resources. * @param {string} resource The resource that was not found. * @extends {Error} */ class NotFoundError extends Error { constructor(public resource: string) { super(`Resource not found: ${resource}`); this.name = "NotFoundError"; } throw(): never { throw this; } } /** * Converts a handle in the format `@username@domain` to a URL. * The resulting URL will be in the format `https://domain/@username`. * @param handle The handle to convert, in the format `@username@domain`. * @returns A URL object representing the handle. * @throws {Error} If the handle format is invalid. * @example * ```ts * const url = convertHandleToUrl("@username@domain.com"); * console.log(url.toString()); // "https://domain.com/@username" * ``` */ function convertHandleToUrl(handle: string): URL { return toAcctUrl(handle) ?? // Convert the handle to a URL new InvalidHandleError(handle).throw(); // or throw an error if invalid } docs/cli.md +85 −0 Original line number Diff line number Diff line Loading @@ -1010,6 +1010,91 @@ command. For example, to use the serveo.net, run the below command: fedify tunnel --service serveo.net 3000 ~~~~ `fedify webfinger`: Looking up a WebFinger resource --------------------------------------------------- *This command is available since Fedify 1.8.0.* The `fedify webfinger` command is used to look up a WebFinger resource by resource URI or handle. [WebFinger] is a protocol that allows discovery of information about people and other entities on the Internet using simple web requests. This command is useful for debugging and testing WebFinger implementations. To look up a WebFinger resource, for example, for a user handle, run the below command: ~~~~ sh fedify webfinger @username@domain.com ~~~~ The output will be like the below: ~~~~ json { "subject": "acct:username@domain.com", "aliases": [ "https://domain.com/@username", "https://domain.com/users/username" ], "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://domain.com/@username" }, { "rel": "self", "type": "application/activity+json", "href": "https://domain.com/users/username" } ] } ~~~~ You can also look up a WebFinger resource by its URL. For example, the below command looks up a WebFinger resource by http or acct URL: ~~~~ sh fedify webfinger https://domain.com/@username fedify webfinger acct:username@domain.com ~~~~ Or, you can also look up multiple WebFinger resources at once. For example, the below command looks up multiple WebFinger resources: ~~~~ sh fedify webfinger @user1@domain.com https://domain.com/@username acct:username@domain.com ~~~~ The outputs will be displayed sequentially, each preceded by a success message indicating which resource was found. [WebFinger]: https://tools.ietf.org/html/rfc7033 ### `-a`/`--user-agent`: Custom `User-Agent` header By default, the `fedify webfinger` command sends the `User-Agent` header with the value `Fedify/1.8.0 (Deno/2.4.0)` (version numbers may vary). You can specify a custom `User-Agent` header by using the `-a`/`--user-agent` option. For example, to send the `User-Agent` header with the value `MyApp/1.0`, run the below command: ~~~~ sh fedify webfinger --user-agent MyApp/1.0 @username@domain.com ~~~~ ### `-p`/`--allow-private-address`: Allow private IP addresses The `-p`/`--allow-private-address` option is used to allow private IP addresses. If you want to allow private IP addresses, run the below command: ~~~~ sh fedify webfinger --allow-private-address @username@localhost ~~~~ Mostly useful for testing purposes. *Do not use this in production.* Shell completions ----------------- Loading fedify/vocab/handle.ts 0 → 100644 +104 −0 Original line number Diff line number Diff line /** * Regular expression to match a fediverse handle in the format `@user@server` * or `user@server`. The `user` part can contain alphanumeric characters and * some special characters except `@`. The `server` part is all characters * after the `@` symbol in the middle. */ const handleRegexp = /^@?((?:[-A-Za-z0-9._~!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)@([^@]+)$/; /** * Represents a fediverse handle, which consists of a username and a host. * The username can be alphanumeric and may include special characters, * while the host is typically a domain name. * @since 1.8.0 */ export interface FediverseHandle { /** * The username part of the fediverse handle. * It can include alphanumeric characters and some special characters. */ readonly username: string; /** * The host part of the fediverse handle, typically a domain name. * It is the part after the `@` symbol in the handle. */ readonly host: string; } /** * Parses a fediverse handle in the format `@user@server` or `user@server`. * The `user` part can contain alphanumeric characters and some special * characters except `@`. The `server` part is all characters after the `@` * symbol in the middle. * * @example * ```typescript * const handle = parseFediverseHandle("@username@example.com"); * console.log(handle?.username); // "username" * console.log(handle?.host); // "example.com" * ``` * * @param handle - The fediverse handle string to parse. * @returns A {@link FediverseHandle} object with `username` and `host` * if the input is valid; otherwise `null`. * @since 1.8.0 */ export function parseFediverseHandle( handle: string, ): FediverseHandle | null { const match = handleRegexp.exec(handle); if (match) { return { username: match[1], host: match[2], }; } return null; } /** * Checks if a string is a valid fediverse handle in the format `@user@server` * or `user@server`. The `user` part can contain alphanumeric characters and * some special characters except `@`. The `server` part is all characters * after the `@` symbol in the middle. * * @example * ```typescript * console.log(isFediverseHandle("@username@example.com")); // true * console.log(isFediverseHandle("username@example.com")); // true * console.log(isFediverseHandle("@username@")); // false * ``` * * @param handle - The string to test as a fediverse handle. * @returns `true` if the string matches the fediverse handle pattern; * otherwise `false`. * @since 1.8.0 */ export function isFediverseHandle( handle: string, ): handle is `${string}@${string}` { return handleRegexp.test(handle); } /** * Converts a fediverse handle in the format `@user@server` or `user@server` * to an `acct:` URI, which is a URL-like identifier for ActivityPub actors. * * @example * ```typescript * const identifier = toAcctUrl("@username@example.com"); * console.log(identifier?.href); // "acct:username@example.com" * ``` * * @param handle - The fediverse handle string to convert. * @returns A `URL` object representing the `acct:` URI if conversion succeeds; * otherwise `null`. * @since 1.8.0 */ export function toAcctUrl(handle: string): URL | null { const parsed = parseFediverseHandle(handle); if (!parsed) return null; const identifier = new URL(`acct:${parsed.username}@${parsed.host}`); return identifier; } Loading
CHANGES.md +20 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,26 @@ To be released. - Added `MemoryKvStore.cas()` method. - Added `DenoKvStore.cas()` method. - Added useful functions for fediverse handles at `@fedify/fedify/vocab`. This functions simplify working with fediverse handles and URLs. - `FediverseHandle`: An interface representing a fediverse handle. - `parseFediverseHandle()`: A function to parse a fediverse handle into its components. - `isFediverseHandle()`: A function to check if a string is a valid fediverse handle. - `toAcctUrl()`: A function to convert a fediverse handle to a `URL`. - Added `fedify webfinger` command. This command allows users to look up WebFinger information for a given resource. - The input can be a handle (e.g., `@user@server`, `user@server`) or a URL (e.g., `https://server/users/path`). - The `--user-agent` or `-a` option used as `User-Agent` header value in the WebFinger request. - The `--allow-private-address` or `-p` option allows looking up WebFinger information for private addresses (e.g., `localhost`). Version 1.7.3 ------------- Loading
cli/mod.ts +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ import { logFile, recordingSink } from "./log.ts"; import { command as lookup } from "./lookup.ts"; import { command as node } from "./node.ts"; import { command as tunnel } from "./tunnel.ts"; import { command as webfinger } from "./webfinger.ts"; const command = new Command() .name("fedify") Loading Loading @@ -65,6 +66,7 @@ const command = new Command() .command("node", node) .command("tunnel", tunnel) .command("completions", new CompletionsCommand()) .command("webfinger", webfinger) .command("help", new HelpCommand().global()); if (import.meta.main) { Loading
cli/webfinger.ts 0 → 100644 +107 −0 Original line number Diff line number Diff line import { Command } from "@cliffy/command"; import { toAcctUrl } from "@fedify/fedify/vocab"; import { lookupWebFinger } from "@fedify/fedify/webfinger"; import ora from "ora"; import { printJson } from "./utils.ts"; export const command = new Command() .arguments("<...resources:string>") .description( "Look up a WebFinger resource by resource. The argument can be multiple.", ) .option( "-a, --user-agent <userAgent:string>", "The user agent to use for the request.", ) .option( "-p, --allow-private-address", "Allow private IP addresses in the URL.", ) .action(async (options, ...resources: string[]) => { for (const resource of resources) { const spinner = ora({ // Create a spinner for the lookup process text: `Looking up WebFinger for ${resource}`, discardStdin: false, }).start(); try { const url = convertUrlIfHandle(resource); // Convert resource to URL const webFinger = await lookupWebFinger(url, options) ?? // Look up WebFinger new NotFoundError(resource).throw(); // throw NotFoundError if not found spinner.succeed(`WebFinger found for ${resource}:`); // Succeed the spinner printJson(webFinger); // Print the WebFinger } catch (error) { if (error instanceof InvalidHandleError) { // If the handle format is invalid, spinner.fail(`Invalid handle format: ${error.handle}`); // log error message with handle } else if (error instanceof NotFoundError) { // If the resource is not found, spinner.fail(`Resource not found: ${error.resource}`); // log not found message } else if (error instanceof Error) { spinner.fail( // For other errors, log the error message `Error looking up WebFinger for ${resource}: ${error}`, ); } } } }); /** * Converts a handle or URL to a URL object. * If the input is a valid URL, it returns the URL object. * If the input is a handle in the format `@username@domain`, it converts it to a URL. * @param handleOrUrl The handle or URL to convert. * @returns A URL object representing the handle or URL. */ function convertUrlIfHandle(handleOrUrl: string): URL { try { return new URL(handleOrUrl); // Try to convert the input to a URL } catch { return convertHandleToUrl(handleOrUrl); // If it fails, treat it as a handle } } /** * Custom error class for invalid handle formats. * @param {string} handle The invalid handle that caused the error. * @extends {Error} */ class InvalidHandleError extends Error { constructor(public handle: string) { super(`Invalid handle format: ${handle}`); this.name = "InvalidHandleError"; } throw(): never { throw this; } } /** * Custom error class for not found resources. * @param {string} resource The resource that was not found. * @extends {Error} */ class NotFoundError extends Error { constructor(public resource: string) { super(`Resource not found: ${resource}`); this.name = "NotFoundError"; } throw(): never { throw this; } } /** * Converts a handle in the format `@username@domain` to a URL. * The resulting URL will be in the format `https://domain/@username`. * @param handle The handle to convert, in the format `@username@domain`. * @returns A URL object representing the handle. * @throws {Error} If the handle format is invalid. * @example * ```ts * const url = convertHandleToUrl("@username@domain.com"); * console.log(url.toString()); // "https://domain.com/@username" * ``` */ function convertHandleToUrl(handle: string): URL { return toAcctUrl(handle) ?? // Convert the handle to a URL new InvalidHandleError(handle).throw(); // or throw an error if invalid }
docs/cli.md +85 −0 Original line number Diff line number Diff line Loading @@ -1010,6 +1010,91 @@ command. For example, to use the serveo.net, run the below command: fedify tunnel --service serveo.net 3000 ~~~~ `fedify webfinger`: Looking up a WebFinger resource --------------------------------------------------- *This command is available since Fedify 1.8.0.* The `fedify webfinger` command is used to look up a WebFinger resource by resource URI or handle. [WebFinger] is a protocol that allows discovery of information about people and other entities on the Internet using simple web requests. This command is useful for debugging and testing WebFinger implementations. To look up a WebFinger resource, for example, for a user handle, run the below command: ~~~~ sh fedify webfinger @username@domain.com ~~~~ The output will be like the below: ~~~~ json { "subject": "acct:username@domain.com", "aliases": [ "https://domain.com/@username", "https://domain.com/users/username" ], "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://domain.com/@username" }, { "rel": "self", "type": "application/activity+json", "href": "https://domain.com/users/username" } ] } ~~~~ You can also look up a WebFinger resource by its URL. For example, the below command looks up a WebFinger resource by http or acct URL: ~~~~ sh fedify webfinger https://domain.com/@username fedify webfinger acct:username@domain.com ~~~~ Or, you can also look up multiple WebFinger resources at once. For example, the below command looks up multiple WebFinger resources: ~~~~ sh fedify webfinger @user1@domain.com https://domain.com/@username acct:username@domain.com ~~~~ The outputs will be displayed sequentially, each preceded by a success message indicating which resource was found. [WebFinger]: https://tools.ietf.org/html/rfc7033 ### `-a`/`--user-agent`: Custom `User-Agent` header By default, the `fedify webfinger` command sends the `User-Agent` header with the value `Fedify/1.8.0 (Deno/2.4.0)` (version numbers may vary). You can specify a custom `User-Agent` header by using the `-a`/`--user-agent` option. For example, to send the `User-Agent` header with the value `MyApp/1.0`, run the below command: ~~~~ sh fedify webfinger --user-agent MyApp/1.0 @username@domain.com ~~~~ ### `-p`/`--allow-private-address`: Allow private IP addresses The `-p`/`--allow-private-address` option is used to allow private IP addresses. If you want to allow private IP addresses, run the below command: ~~~~ sh fedify webfinger --allow-private-address @username@localhost ~~~~ Mostly useful for testing purposes. *Do not use this in production.* Shell completions ----------------- Loading
fedify/vocab/handle.ts 0 → 100644 +104 −0 Original line number Diff line number Diff line /** * Regular expression to match a fediverse handle in the format `@user@server` * or `user@server`. The `user` part can contain alphanumeric characters and * some special characters except `@`. The `server` part is all characters * after the `@` symbol in the middle. */ const handleRegexp = /^@?((?:[-A-Za-z0-9._~!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)@([^@]+)$/; /** * Represents a fediverse handle, which consists of a username and a host. * The username can be alphanumeric and may include special characters, * while the host is typically a domain name. * @since 1.8.0 */ export interface FediverseHandle { /** * The username part of the fediverse handle. * It can include alphanumeric characters and some special characters. */ readonly username: string; /** * The host part of the fediverse handle, typically a domain name. * It is the part after the `@` symbol in the handle. */ readonly host: string; } /** * Parses a fediverse handle in the format `@user@server` or `user@server`. * The `user` part can contain alphanumeric characters and some special * characters except `@`. The `server` part is all characters after the `@` * symbol in the middle. * * @example * ```typescript * const handle = parseFediverseHandle("@username@example.com"); * console.log(handle?.username); // "username" * console.log(handle?.host); // "example.com" * ``` * * @param handle - The fediverse handle string to parse. * @returns A {@link FediverseHandle} object with `username` and `host` * if the input is valid; otherwise `null`. * @since 1.8.0 */ export function parseFediverseHandle( handle: string, ): FediverseHandle | null { const match = handleRegexp.exec(handle); if (match) { return { username: match[1], host: match[2], }; } return null; } /** * Checks if a string is a valid fediverse handle in the format `@user@server` * or `user@server`. The `user` part can contain alphanumeric characters and * some special characters except `@`. The `server` part is all characters * after the `@` symbol in the middle. * * @example * ```typescript * console.log(isFediverseHandle("@username@example.com")); // true * console.log(isFediverseHandle("username@example.com")); // true * console.log(isFediverseHandle("@username@")); // false * ``` * * @param handle - The string to test as a fediverse handle. * @returns `true` if the string matches the fediverse handle pattern; * otherwise `false`. * @since 1.8.0 */ export function isFediverseHandle( handle: string, ): handle is `${string}@${string}` { return handleRegexp.test(handle); } /** * Converts a fediverse handle in the format `@user@server` or `user@server` * to an `acct:` URI, which is a URL-like identifier for ActivityPub actors. * * @example * ```typescript * const identifier = toAcctUrl("@username@example.com"); * console.log(identifier?.href); // "acct:username@example.com" * ``` * * @param handle - The fediverse handle string to convert. * @returns A `URL` object representing the `acct:` URI if conversion succeeds; * otherwise `null`. * @since 1.8.0 */ export function toAcctUrl(handle: string): URL | null { const parsed = parseFediverseHandle(handle); if (!parsed) return null; const identifier = new URL(`acct:${parsed.username}@${parsed.host}`); return identifier; }