PageRenderTime 57ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/background_scripts/completion_engines.js

http://github.com/philc/vimium
JavaScript | 259 lines | 199 code | 28 blank | 32 comment | 2 complexity | b04162eca6596d147b6fce5960a27050 MD5 | raw file
  1. // A completion engine provides search suggestions for a custom search engine. A custom search engine is
  2. // identified by a "searchUrl". An "engineUrl" is used for fetching suggestions, whereas a "searchUrl" is used
  3. // for the actual search itself.
  4. //
  5. // Each completion engine defines:
  6. //
  7. // 1. An "engineUrl". This is the URL to use for search completions and is passed as the option "engineUrl"
  8. // to the "BaseEngine" constructor.
  9. //
  10. // 2. One or more regular expressions which define the custom search engine URLs for which the completion
  11. // engine will be used. This is passed as the "regexps" option to the "BaseEngine" constructor.
  12. //
  13. // 3. A "parse" function. This takes a successful XMLHttpRequest object (the request has completed
  14. // successfully), and returns a list of suggestions (a list of strings). This method is always executed
  15. // within the context of a try/catch block, so errors do not propagate.
  16. //
  17. // 4. Each completion engine *must* include an example custom search engine. The example must include an
  18. // example "keyword" and an example "searchUrl", and may include an example "description" and an
  19. // "explanation".
  20. //
  21. // Each new completion engine must be added to the list "CompletionEngines" at the bottom of this file.
  22. //
  23. // The lookup logic which uses these completion engines is in "./completion_search.coffee".
  24. //
  25. // A base class for common regexp-based matching engines. "options" must define:
  26. // options.engineUrl: the URL to use for the completion engine. This must be a string.
  27. // options.regexps: one or regular expressions. This may either a single string or a list of strings.
  28. // options.example: an example object containing at least "keyword" and "searchUrl", and optional "description".
  29. class BaseEngine {
  30. constructor(options) {
  31. extend(this, options);
  32. this.regexps = this.regexps.map(regexp => new RegExp(regexp));
  33. }
  34. match(searchUrl) { return Utils.matchesAnyRegexp(this.regexps, searchUrl); }
  35. getUrl(queryTerms) { return Utils.createSearchUrl(queryTerms, this.engineUrl); }
  36. }
  37. // Several Google completion engines package responses as XML. This parses such XML.
  38. class GoogleXMLBaseEngine extends BaseEngine {
  39. parse(xhr) {
  40. return Array.from(xhr.responseXML.getElementsByTagName("suggestion"))
  41. .map(suggestion => suggestion.getAttribute("data"))
  42. .filter(suggestion => suggestion);
  43. }
  44. }
  45. class Google extends GoogleXMLBaseEngine {
  46. constructor() {
  47. super({
  48. engineUrl: "https://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s",
  49. regexps: ["^https?://[a-z]+\\.google\\.(com|ie|co\\.(uk|jp)|ca|com\\.au)/"],
  50. example: {
  51. searchUrl: "https://www.google.com/search?q=%s",
  52. keyword: "g"
  53. }
  54. });
  55. }
  56. }
  57. class GoogleMaps extends GoogleXMLBaseEngine {
  58. constructor() {
  59. const q = GoogleMaps.prefix.split(" ").join("+");
  60. super({
  61. engineUrl: `https://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=${q}%s`,
  62. regexps: ["^https?://[a-z]+\\.google\\.(com|ie|co\\.(uk|jp)|ca|com\\.au)/maps"],
  63. example: {
  64. searchUrl: "https://www.google.com/maps?q=%s",
  65. keyword: "m",
  66. explanation:
  67. `\
  68. This uses regular Google completion, but prepends the text "<tt>map of</tt>" to the query. It works
  69. well for places, countries, states, geographical regions and the like, but will not perform address
  70. search.\
  71. `
  72. }
  73. });
  74. }
  75. parse(xhr) {
  76. return Array.from(super.parse(xhr))
  77. .filter(suggestion => suggestion.startsWith(GoogleMaps.prefix))
  78. .map(suggestion => suggestion.slice(GoogleMaps.prefix.length));
  79. }
  80. }
  81. GoogleMaps.prefix = "map of";
  82. class Youtube extends GoogleXMLBaseEngine {
  83. constructor() {
  84. super({
  85. engineUrl: "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s",
  86. regexps: ["^https?://[a-z]+\\.youtube\\.com/results"],
  87. example: {
  88. searchUrl: "https://www.youtube.com/results?search_query=%s",
  89. keyword: "y"
  90. }
  91. });
  92. }
  93. }
  94. class Wikipedia extends BaseEngine {
  95. constructor() {
  96. super({
  97. engineUrl: "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s",
  98. regexps: ["^https?://[a-z]+\\.wikipedia\\.org/"],
  99. example: {
  100. searchUrl: "https://www.wikipedia.org/w/index.php?title=Special:Search&search=%s",
  101. keyword: "w"
  102. }
  103. });
  104. }
  105. parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
  106. }
  107. class Bing extends BaseEngine {
  108. constructor() {
  109. super({
  110. engineUrl: "https://api.bing.com/osjson.aspx?query=%s",
  111. regexps: ["^https?://www\\.bing\\.com/search"],
  112. example: {
  113. searchUrl: "https://www.bing.com/search?q=%s",
  114. keyword: "b"
  115. }
  116. });
  117. }
  118. parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
  119. }
  120. class Amazon extends BaseEngine {
  121. constructor() {
  122. super({
  123. engineUrl: "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s",
  124. regexps: ["^https?://www\\.amazon\\.(com|co\\.uk|ca|de|com\\.au)/s/"],
  125. example: {
  126. searchUrl: "https://www.amazon.com/s/?field-keywords=%s",
  127. keyword: "a"
  128. }
  129. });
  130. }
  131. parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
  132. }
  133. class AmazonJapan extends BaseEngine {
  134. constructor() {
  135. super({
  136. engineUrl: "https://completion.amazon.co.jp/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=6&q=%s",
  137. regexps: ["^https?://www\\.amazon\\.co\\.jp/(s/|gp/search)"],
  138. example: {
  139. searchUrl: "https://www.amazon.co.jp/s/?field-keywords=%s",
  140. keyword: "aj"
  141. }
  142. });
  143. }
  144. parse(xhr) { return JSON.parse(xhr.responseText)[1]; }
  145. }
  146. class DuckDuckGo extends BaseEngine {
  147. constructor() {
  148. super({
  149. engineUrl: "https://duckduckgo.com/ac/?q=%s",
  150. regexps: ["^https?://([a-z]+\\.)?duckduckgo\\.com/"],
  151. example: {
  152. searchUrl: "https://duckduckgo.com/?q=%s",
  153. keyword: "d"
  154. }
  155. });
  156. }
  157. parse(xhr) {
  158. return Array.from(JSON.parse(xhr.responseText)).map((suggestion) => suggestion.phrase);
  159. }
  160. }
  161. class Webster extends BaseEngine {
  162. constructor() {
  163. super({
  164. engineUrl: "https://www.merriam-webster.com/lapi/v1/mwol-search/autocomplete?search=%s",
  165. regexps: ["^https?://www.merriam-webster.com/dictionary/"],
  166. example: {
  167. searchUrl: "https://www.merriam-webster.com/dictionary/%s",
  168. keyword: "dw",
  169. description: "Dictionary"
  170. }
  171. });
  172. }
  173. parse(xhr) {
  174. return Array.from(JSON.parse(xhr.responseText).docs).map((suggestion) => suggestion.word);
  175. }
  176. }
  177. class Qwant extends BaseEngine {
  178. constructor() {
  179. super({
  180. engineUrl: "https://api.qwant.com/api/suggest?q=%s",
  181. regexps: ["^https?://www\\.qwant\\.com/"],
  182. example: {
  183. searchUrl: "https://www.qwant.com/?q=%s",
  184. keyword: "qw"
  185. }
  186. });
  187. }
  188. parse(xhr) {
  189. return Array.from(JSON.parse(xhr.responseText).data.items).map((suggestion) => suggestion.value);
  190. }
  191. }
  192. class UpToDate extends BaseEngine {
  193. constructor() {
  194. super({
  195. engineUrl: "https://www.uptodate.com/services/app/contents/search/autocomplete/json?term=%s&limit=10",
  196. regexps: ["^https?://www\\.uptodate\\.com/"],
  197. example: {
  198. searchUrl: "https://www.uptodate.com/contents/search?search=%s&searchType=PLAIN_TEXT&source=USER_INPUT&searchControl=TOP_PULLDOWN&autoComplete=false",
  199. keyword: "upto"
  200. }
  201. });
  202. }
  203. parse(xhr) { return JSON.parse(xhr.responseText).data.searchTerms; }
  204. }
  205. // A dummy search engine which is guaranteed to match any search URL, but never produces completions. This
  206. // allows the rest of the logic to be written knowing that there will always be a completion engine match.
  207. class DummyCompletionEngine extends BaseEngine {
  208. constructor() {
  209. super({
  210. regexps: ["."],
  211. dummy: true
  212. });
  213. }
  214. }
  215. // Note: Order matters here.
  216. const CompletionEngines = [
  217. Youtube,
  218. GoogleMaps,
  219. Google,
  220. DuckDuckGo,
  221. Wikipedia,
  222. Bing,
  223. Amazon,
  224. AmazonJapan,
  225. Webster,
  226. Qwant,
  227. UpToDate,
  228. DummyCompletionEngine
  229. ];
  230. root = typeof exports !== 'undefined' && exports !== null ? exports : window;
  231. root.CompletionEngines = CompletionEngines;