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

Helper function for adding search engines to FireFox based on Opensearch description XML #5358

Open
jennydaman opened this issue May 1, 2024 · 0 comments
Assignees

Comments

@jennydaman
Copy link

Description

Currently there exists the option programs.firefox.profiles..search.engines where search engines can be configured for Firefox. It would be convenient if the search engines could be added from Opensearch description XML.

For example, the search engine at https://search.nixos.org has a <link> element in its <head>

<link rel="search" type="application/opensearchdescription+xml" title="NixOS packages" href="/desc-search-packages.xml">

The document at https://search.nixos.org/desc-search-packages.xml looks like:

<?xml version="1.0"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                       xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  <ShortName>NixOS packages</ShortName>
  <Description>Search NixOS packages by name or description.</Description>
  <InputEncoding>UTF-8</InputEncoding>
  <Image width="16" height="16" type="image/png">https://nixos.org/favicon.png</Image>
  <Url type="text/html" template="https://search.nixos.org/packages?query={searchTerms}"/>
  <moz:SearchForm>https://search.nixos.org/packages</moz:SearchForm>
</OpenSearchDescription>

This could be translated into the following Nix expression:

programs.firefox.profiles.default.search.engines."NixOS packages" = {
  urls = [{ "https://search.nixos.org/packages?query={searchTerms}" }];
  iconUpdateURL = "https://nixos.org/favicon.png";
  updateInterval = 24 * 60 * 60 * 1000; # every day;
};

I myself am a newcomer to Nix and am not fluent with the Nix language. I was able to write a proof-of-concept in TypeScript:

import * as cheerio from "cheerio";

const URLS = [
  "https://www.startpage.com",
  "https://github.com",
  "https://archlinux.org",
  "https://search.nixos.org"
];

function main() {
  Promise.all(URLS.map(homeManagerFirefoxEngineNixExpressions))
    .then((x) => x.flat(1).forEach(console.log));
}

main();

type OpenSearchDescription = {
  shortName: string,
  description: string,
  inputEncoding: string,
  image: string,
  urlTemplate: string,
};

function fromOpensearchXml(xml: string): OpenSearchDescription {
  const $ = cheerio.load(xml, { xmlMode: true });
  return {
    shortName: $("ShortName").text(),
    description: $("Description").text(),
    inputEncoding: $("InputEncoding").text(),
    image: $("Image").text(),
    urlTemplate: $("Url").attr("template"),
  };
}

function toNixExpression({shortName, image, urlTemplate}: OpenSearchDescription): string {
  return `
"${shortName}" = {
  urls = [{ "${urlTemplate}" }];
  iconUpdateURL = "${image}";
  updateInterval = 24 * 60 * 60 * 1000; # every day;
};
`;
}

async function homeManagerFirefoxEngineNixExpressions(url: string): Promise<string[]> {
  const links = getOpenSearchLinks(url, await fetchBody(url));
  const opensearchXmls = await Promise.all(links.map(fetchBody));
  const descriptions = opensearchXmls.map(fromOpensearchXml);
  return descriptions.map(toNixExpression);
}

async function fetchBody(url: string): Promise<string> {
  const res = await fetch(url, { redirect: "follow" });
  return await res.text();
}

function getOpenSearchLinks(url: string, htmlBody: string): string[] {
  const $ = cheerio.load(htmlBody);
  const selection = $('head link[type="application/opensearchdescription+xml"]');
  return cheerioSelection2Array(selection)
    .map((element) => element.attribs["href"])
    .map((href) => href.startsWith("https://") ? href : joinUrl(url, href));
}

function cheerioSelection2Array<E>($: cheerio.Cheerio<E>): E[] {
  const elements = [];
  for (let i = 0; i < $.length; i++) {
    elements.push($[i]);
  }
  return elements;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants