/background_scripts/completion_engines.js
JavaScript | 259 lines | 199 code | 28 blank | 32 comment | 2 complexity | b04162eca6596d147b6fce5960a27050 MD5 | raw file
- // A completion engine provides search suggestions for a custom search engine. A custom search engine is
- // identified by a "searchUrl". An "engineUrl" is used for fetching suggestions, whereas a "searchUrl" is used
- // for the actual search itself.
- //
- // Each completion engine defines:
- //
- // 1. An "engineUrl". This is the URL to use for search completions and is passed as the option "engineUrl"
- // to the "BaseEngine" constructor.
- //
- // 2. One or more regular expressions which define the custom search engine URLs for which the completion
- // engine will be used. This is passed as the "regexps" option to the "BaseEngine" constructor.
- //
- // 3. A "parse" function. This takes a successful XMLHttpRequest object (the request has completed
- // successfully), and returns a list of suggestions (a list of strings). This method is always executed
- // within the context of a try/catch block, so errors do not propagate.
- //
- // 4. Each completion engine *must* include an example custom search engine. The example must include an
- // example "keyword" and an example "searchUrl", and may include an example "description" and an
- // "explanation".
- //
- // Each new completion engine must be added to the list "CompletionEngines" at the bottom of this file.
- //
- // The lookup logic which uses these completion engines is in "./completion_search.coffee".
- //
- // A base class for common regexp-based matching engines. "options" must define:
- // options.engineUrl: the URL to use for the completion engine. This must be a string.
- // options.regexps: one or regular expressions. This may either a single string or a list of strings.
- // options.example: an example object containing at least "keyword" and "searchUrl", and optional "description".
- class BaseEngine {
- constructor(options) {
- extend(this, options);
- this.regexps = this.regexps.map(regexp => new RegExp(regexp));
- }
- match(searchUrl) { return Utils.matchesAnyRegexp(this.regexps, searchUrl); }
- getUrl(queryTerms) { return Utils.createSearchUrl(queryTerms, this.engineUrl); }
- }
- // Several Google completion engines package responses as XML. This parses such XML.
- class GoogleXMLBaseEngine extends BaseEngine {
- parse(xhr) {
- return Array.from(xhr.responseXML.getElementsByTagName("suggestion"))
- .map(suggestion => suggestion.getAttribute("data"))
- .filter(suggestion => suggestion);
- }
- }
- class Google extends GoogleXMLBaseEngine {
- constructor() {
- super({
- engineUrl: "https://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s",
- regexps: ["^https?://[a-z]+\\.google\\.(com|ie|co\\.(uk|jp)|ca|com\\.au)/"],
- example: {
- searchUrl: "https://www.google.com/search?q=%s",
- keyword: "g"
- }
- });
- }
- }
- class GoogleMaps extends GoogleXMLBaseEngine {
- constructor() {
- const q = GoogleMaps.prefix.split(" ").join("+");
- super({
- engineUrl: `https://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=${q}%s`,
- regexps: ["^https?://[a-z]+\\.google\\.(com|ie|co\\.(uk|jp)|ca|com\\.au)/maps"],
- example: {
- searchUrl: "https://www.google.com/maps?q=%s",
- keyword: "m",
- explanation:
- `\
- This uses regular Google completion, but prepends the text "<tt>map of</tt>" to the query. It works
- well for places, countries, states, geographical regions and the like, but will not perform address
- search.\
- `
- }
- });
- }
- parse(xhr) {
- return Array.from(super.parse(xhr))
- .filter(suggestion => suggestion.startsWith(GoogleMaps.prefix))
- .map(suggestion => suggestion.slice(GoogleMaps.prefix.length));
- }
- }
- GoogleMaps.prefix = "map of";
- class Youtube extends GoogleXMLBaseEngine {
- constructor() {
- super({
- engineUrl: "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s",
- regexps: ["^https?://[a-z]+\\.youtube\\.com/results"],
- example: {
- searchUrl: "https://www.youtube.com/results?search_query=%s",
- keyword: "y"
- }
- });
- }
- }
- class Wikipedia extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s",
- regexps: ["^https?://[a-z]+\\.wikipedia\\.org/"],
- example: {
- searchUrl: "https://www.wikipedia.org/w/index.php?title=Special:Search&search=%s",
- keyword: "w"
- }
- });
- }
- parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
- }
- class Bing extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://api.bing.com/osjson.aspx?query=%s",
- regexps: ["^https?://www\\.bing\\.com/search"],
- example: {
- searchUrl: "https://www.bing.com/search?q=%s",
- keyword: "b"
- }
- });
- }
- parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
- }
- class Amazon extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s",
- regexps: ["^https?://www\\.amazon\\.(com|co\\.uk|ca|de|com\\.au)/s/"],
- example: {
- searchUrl: "https://www.amazon.com/s/?field-keywords=%s",
- keyword: "a"
- }
- });
- }
- parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
- }
- class AmazonJapan extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://completion.amazon.co.jp/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=6&q=%s",
- regexps: ["^https?://www\\.amazon\\.co\\.jp/(s/|gp/search)"],
- example: {
- searchUrl: "https://www.amazon.co.jp/s/?field-keywords=%s",
- keyword: "aj"
- }
- });
- }
- parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
- }
- class DuckDuckGo extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://duckduckgo.com/ac/?q=%s",
- regexps: ["^https?://([a-z]+\\.)?duckduckgo\\.com/"],
- example: {
- searchUrl: "https://duckduckgo.com/?q=%s",
- keyword: "d"
- }
- });
- }
- parse(xhr) {
- return Array.from(JSON.parse(xhr.responseText)).map((suggestion) => suggestion.phrase);
- }
- }
- class Webster extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://www.merriam-webster.com/lapi/v1/mwol-search/autocomplete?search=%s",
- regexps: ["^https?://www.merriam-webster.com/dictionary/"],
- example: {
- searchUrl: "https://www.merriam-webster.com/dictionary/%s",
- keyword: "dw",
- description: "Dictionary"
- }
- });
- }
- parse(xhr) {
- return Array.from(JSON.parse(xhr.responseText).docs).map((suggestion) => suggestion.word);
- }
- }
- class Qwant extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://api.qwant.com/api/suggest?q=%s",
- regexps: ["^https?://www\\.qwant\\.com/"],
- example: {
- searchUrl: "https://www.qwant.com/?q=%s",
- keyword: "qw"
- }
- });
- }
- parse(xhr) {
- return Array.from(JSON.parse(xhr.responseText).data.items).map((suggestion) => suggestion.value);
- }
- }
- class UpToDate extends BaseEngine {
- constructor() {
- super({
- engineUrl: "https://www.uptodate.com/services/app/contents/search/autocomplete/json?term=%s&limit=10",
- regexps: ["^https?://www\\.uptodate\\.com/"],
- example: {
- searchUrl: "https://www.uptodate.com/contents/search?search=%s&searchType=PLAIN_TEXT&source=USER_INPUT&searchControl=TOP_PULLDOWN&autoComplete=false",
- keyword: "upto"
- }
- });
- }
- parse(xhr) { return JSON.parse(xhr.responseText).data.searchTerms; }
- }
- // A dummy search engine which is guaranteed to match any search URL, but never produces completions. This
- // allows the rest of the logic to be written knowing that there will always be a completion engine match.
- class DummyCompletionEngine extends BaseEngine {
- constructor() {
- super({
- regexps: ["."],
- dummy: true
- });
- }
- }
- // Note: Order matters here.
- const CompletionEngines = [
- Youtube,
- GoogleMaps,
- Google,
- DuckDuckGo,
- Wikipedia,
- Bing,
- Amazon,
- AmazonJapan,
- Webster,
- Qwant,
- UpToDate,
- DummyCompletionEngine
- ];
- root = typeof exports !== 'undefined' && exports !== null ? exports : window;
- root.CompletionEngines = CompletionEngines;