Commit 78962316 authored by ChanHaeng Lee's avatar ChanHaeng Lee
Browse files

`fedify webfinger` command #260

parent fd671f7f
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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")
@@ -65,7 +66,8 @@ const command = new Command()
  .command("node", node)
  .command("tunnel", tunnel)
  .command("completions", new CompletionsCommand())
  .command("help", new HelpCommand().global());
  .command("help", new HelpCommand().global())
  .command("webfinger", webfinger);

if (import.meta.main) {
  await command.parse(Deno.args);

cli/webfinger.ts

0 → 100644
+89 −0
Original line number Diff line number Diff line
import { Command } from "@cliffy/command";
import { lookupWebFinger } from "@fedify/fedify/webfinger";
import ora from "ora";
import { printJson } from "./utils.ts";

export const command = new Command()
  .arguments("<handle:string>")
  .description(
    "Lookup a WebFinger resource by handle. 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, handle: string) => {
    try {
      const url = convertHandleToUrl(handle); // Convert handle to URL
      const spinner = ora({ // Create a spinner for the lookup process
        text: `Looking up WebFinger for ${handle}`,
        discardStdin: false,
      }).start();
      const webFinger = await lookupWebFinger(url, options); // Look up WebFinger
      if (webFinger == null) { // If no WebFinger found,
        spinner.fail(`No WebFinger found for ${handle}`); // fail the spinner
      }

      spinner.succeed(`WebFinger found for ${handle}:`); // Succeed the spinner
      printJson(webFinger); // Print the WebFinger
    } catch (error) {
      if (error instanceof InvalidHandleError) { // If the handle format is invalid,
        console.error(`Invalid handle format: ${error.handle}`); // log error message with handle
      } else {
        console.error( // For other errors, log the error message
          `Error looking up WebFinger for ${handle}:`,
          error,
        );
      }
    }
  });

/**
 * Regular expression to match a handle in the format `@username@domain`.
 * The username can contain any characters except `@`.
 * The domain must be match with `[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}`.
 */
const HANDLE_REGEX =
  /^@?([^@]+)@([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})$/;

/**
 * Custom error class for invalid handle formats.
 * @param 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";
  }
}

/**
 * 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 {
  const match = handle.match(HANDLE_REGEX); // Match the handle format
  if (!match) { // If the handle does not match,
    throw new InvalidHandleError(handle); // throw `Invalid handle format` error
  }

  const [, username, domain] = match; // Extract username and domain from the match
  if (!username || !domain) { // If username or domain is empty,
    throw new InvalidHandleError(handle); // throw `Invalid handle format` error
  }

  return new URL(`https://${domain}/@${username}`); // Create a URL object with the domain and username
}