Commit 924f677b authored by ChanHaeng Lee's avatar ChanHaeng Lee
Browse files

Add Fediverse handle functions and update changelog

parent ae8dc145
Loading
Loading
Loading
Loading
+19 −6
Original line number Diff line number Diff line
@@ -41,6 +41,19 @@ To be released.
     -  The `--allow-private-address` or `-p` option allows looking up
        WebFinger information for private addresses (e.g., `localhost`).

 -  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.
         - `username`: The username part of the handle.
         - `host`: The host part of the handle.
    - `parseFediverseHandle`: A function to parse a Fediverse handle into its components.
         - If the input is a valid Fediverse handle, it returns a `FediverseHandle` object.
         - Else, it returns `null`.
     - `isFediverseHandle`: A function to check if a string is a valid Fediverse handle.
     - `convertFediverseHandle`: A function to convert a Fediverse handle to a URL.
         - If the input is a valid Fediverse handle, it returns a `URL` object.
         - Else, it returns `null`.

Version 1.7.2
-------------

fedify/vocab/handle.ts

0 → 100644
+94 −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.
 */
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.
 *
 * @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
 * @example
 * ```typescript
 * const handle = parseFediverseHandle("@username@example.com");
 * console.log(handle?.username); // "username"
 * console.log(handle?.host);     // "example.com"
 * ```
 */
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.
 *
 * @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
 * @example
 * ```typescript
 * console.log(isFediverseHandle("@username@example.com")); // true
 * console.log(isFediverseHandle("username@example.com"));  // true
 * console.log(isFediverseHandle("@username@"));            // false
 * ```
 */
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.
 *
 * @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
 * @example
 * ```typescript
 * const identifier = convertFediverseHandle("@username@example.com");
 * console.log(identifier?.href); // "acct:username@example.com"
 * ```
 */
export function convertFediverseHandle(handle: string): URL | null {
  const parsed = parseFediverseHandle(handle);
  if (!parsed) return null;
  const identifier = new URL(`acct:${parsed.username}@${parsed.host}`);
  return identifier;
}
+2 −89
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import {
  type GetUserAgentOptions,
} from "../runtime/docloader.ts";
import { lookupWebFinger } from "../webfinger/lookup.ts";
import { convertFediverseHandle } from "./handle.ts";
import { getTypeId } from "./type.ts";
import { type Collection, type Link, Object } from "./vocab.ts";

@@ -47,92 +48,6 @@ export interface LookupObjectOptions {
  tracerProvider?: TracerProvider;
}

/**
 * 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.
 */
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"
 * ```
 */
export function parseFediverseHandle(
  handle: string,
): FediverseHandle | undefined {
  const match = handleRegexp.exec(handle);
  if (match) {
    return {
      username: match[1],
      host: match[2],
    };
  }
  return undefined;
}

/**
 * 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
 * ```
 */
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 = convertFediverseHandle("@username@example.com");
 * console.log(identifier?.href); // "acct:username@example.com"
 * ```
 */
export function convertFediverseHandle(handle: string): URL | undefined {
  const parsed = parseFediverseHandle(handle);
  if (!parsed) return undefined;
  const identifier = new URL(`acct:${parsed.username}@${parsed.host}`);
  return identifier;
}

/**
 * Looks up an ActivityStreams object by its URI (including `acct:` URIs)
 * or a fediverse handle (e.g., `@user@server` or `user@server`).
@@ -213,9 +128,7 @@ async function lookupObjectInternal(
  const documentLoader = options.documentLoader ??
    getDocumentLoader({ userAgent: options.userAgent });
  if (typeof identifier === "string") {
    const match = handleRegexp.exec(identifier);
    if (match) identifier = `acct:${match[1]}@${match[2]}`;
    identifier = new URL(identifier);
    identifier = convertFediverseHandle(identifier) ?? new URL(identifier);
  }
  let document: unknown | null = null;
  if (identifier.protocol === "http:" || identifier.protocol === "https:") {
+1 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@
 */
export * from "./actor.ts";
export * from "./constants.ts";
export * from "./handle.ts";
export * from "./lookup.ts";
export * from "./type.ts";
export * from "./vocab.ts";