DuckDuckGo Extended [fork]

Extends DDG by adding a customizable list of additional search engines for making fast searches from other engines.

  1. // ==UserScript==
  2. // @name DuckDuckGo Extended [fork]
  3. // @description Extends DDG by adding a customizable list of additional search engines for making fast searches from other engines.
  4. // @namespace userscripts.org/users/439657
  5. // @homepage http://userscripts-mirror.org/scripts/show/129505
  6. // @icon http://s3.amazonaws.com/uso_ss/icon/129505/large.png?1368599692
  7. // @OLDupdateURL https://userscripts.org/scripts/source/129505.meta.js
  8. // @OLDdownloadURL https://userscripts.org/scripts/source/129505.user.js
  9. // @match *://duckduckgo.com/*
  10. // @exclude *://duckduckgo.com/post2.html
  11. // @match http://mycroftproject.com/*
  12. // @grant GM_addStyle
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_xmlhttpRequest
  16. // @version 2.0.4-joey3
  17. // @author tumpio
  18. // ==/UserScript==
  19.  
  20. var ddg_e = {
  21.  
  22. list: document.createElement("ol"),
  23. engines: [],
  24.  
  25. default: "Google==http://www.google.com/search?q={searchTerms}\
  26. ;;Images==http://www.bing.com/images/search?q={searchTerms}&FORM=BIFD\
  27. ;;Wiki==http://en.wikipedia.org/w/index.php?title=Special%3ASearch&profile=default&search={searchTerms}\
  28. ;;Maps==http://maps.google.com/maps?q={searchTerms}\
  29. ;;YouTube==http://www.youtube.com/results?search_query={searchTerms}&aq=f\
  30. ;;IMDb==http://www.imdb.com/find?s=all&q={searchTerms}\
  31. ;;Music==http://www.musicsmasher.net/#{searchTerms}\
  32. ;;Facebook==http://www.facebook.com/search.php?q={searchTerms}\
  33. ;;Google+==https://plus.google.com/s/{searchTerms}\
  34. ;;twitter==https://twitter.com/#!/search/realtime/{searchTerms}\
  35. ;;Amazon==http://www.amazon.com/gp/search?ie=UTF8&keywords={searchTerms}\
  36. ;;Torrents==http://scrapetorrent.com/Search/index.php?search={searchTerms}&sort=seed&fz=&zs=&cat=\
  37. ;;filesTube==http://www.filestube.com/search.html?q={searchTerms}\
  38. ;;Translate==http://translate.google.com/translate_t#auto|en|{searchTerms}",
  39.  
  40. style: "#ddg_extented { float:left; width:100%; position:relative; top:0px; z-index:999 }\
  41. #ddg_extented ol { clear:left; float:right; list-style:none; position:relative; right:50%; text-align:center; margin:0; padding:0 }\
  42. #ddg_extented li { display:inline; list-style:none; position:relative; left:50%; box-shadow:0 2px 5px 0 #888; margin:0; padding:0; background:#b60002 }\
  43. #ddg_extented li:first-child { border-bottom-left-radius: 10px }\
  44. #ddg_extented li:last-child { border-bottom-right-radius: 10px }\
  45. #ddg_extented li a:link,li a:visited { text-decoration:none; color:#fff; font-size:1.0em; font-weight:700; padding:0 9px }\
  46. #ddg_extented li a:hover { color:#91C5EE }\
  47. #ddg_extented li:hover { box-shadow:0 3px 3px 0 #888 }\
  48. #ddg_extented li.disabled a {pointer-events: none }\
  49. body #header_wrapper #header #header_content_wrapper #header_content #header_button_wrapper #header_button #header_button_menu_wrapper #header_button_menu li.disabled {display: none!important;}\
  50. #ddg_e_save a { background: #88FF61!important }\
  51. #ddg_e_cancel a { background: #FF8861!important }",
  52.  
  53. get: function() {
  54. var e = GM_getValue("engines", this.
  55. default).split(";;");
  56. var a = [];
  57. for (var i = 0; i < e.length; i++)
  58. a.push(e[i].split("=="));
  59. this.length = a.length;
  60. return a;
  61. },
  62.  
  63. set: function() {
  64. var e = "";
  65. for (var i = 0; i < this.engines.length; i++) {
  66. e += ";;" + this.engines[i].textContent + "==" + this.engines[i].firstChild.getAttribute("data-engine");
  67. }
  68. GM_setValue("engines", e.substr(2));
  69. },
  70.  
  71. newList: function() {
  72. var l = "";
  73. var e = this.get();
  74. for (var i = 0; i < e.length; i++)
  75. l += '<li draggable="true" value=' + (i + 1) + '"><a data-engine="' + e[i][1] + '"href="#' + e[i][1] + '">' + e[i][0] + "</a></li>";
  76. this.list.innerHTML = l;
  77. this.engines = this.list.getElementsByTagName("li");
  78. // Joey added this so that clicks work again.
  79. // I don't know how they worked before.
  80. // Were they handled by DDG itself?
  81. // Or is it somewhere in this script that broke in Greasemonkey?
  82. for (var i = 0; i < this.engines.length; i++)
  83. this.engines[i].firstChild.onclick = onHashChange;
  84. },
  85.  
  86. append: function(h) {
  87. var e = document.createElement("div");
  88. var b = h.style.background;
  89. if (b !== "")
  90. this.style += "#ddg_extented li { background:" + b + "!important }";
  91. GM_addStyle(this.style);
  92. e.setAttribute("id", "ddg_extented");
  93. e.appendChild(this.list);
  94. this.newList();
  95. h.appendChild(e);
  96. }
  97. };
  98.  
  99. var options = {
  100.  
  101. size: 7,
  102. list: [],
  103. strings: [
  104. ["Find new", "Find and add new search engines from MyCroft"],
  105. ["Reorder", "Drag and drop search engines"],
  106. ["Rename", "Click to rename search engines"],
  107. ["Remove", "Click to remove search engines"],
  108. ["Restore all", "Restores all default search engines"],
  109. ["Save", "Saves modified search engines"],
  110. ["Cancel", "Cancels all recent modifications"]
  111. ],
  112.  
  113. create: function(m) {
  114. m.innerHTML += '<li class="header_button_menu_header">DDG Extended</li>';
  115. for (var i = 0, li; i < this.size; i++) {
  116. li = document.createElement("li");
  117. li.className = "enabled";
  118. li.innerHTML = '<a tabindex="-1" title="' + this.strings[i][1] + '">' + this.strings[i][0] + "</a>";
  119. this.list.push(li);
  120. m.appendChild(this.list[i]);
  121. }
  122. this.list[5].className = "disabled";
  123. this.list[6].className = "disabled";
  124. this.list[5].id = "ddg_e_save";
  125. this.list[6].id = "ddg_e_cancel";
  126. this.list[0].addEventListener("click", newEngine, false);
  127. this.list[1].addEventListener("click", enableDrag, false);
  128. this.list[2].addEventListener("click", enableRename, false);
  129. this.list[3].addEventListener("click", enableRemove, false);
  130. this.list[4].addEventListener("click", restoreEngines, false);
  131. this.list[5].addEventListener("click", saveActions, false);
  132. this.list[6].addEventListener("click", cancelActions, false);
  133. },
  134.  
  135. toggleClass: function() {
  136. for (var i = 0; i < this.list.length; i++) {
  137. if (this.list[i].className === "disabled")
  138. this.list[i].className = "enabled";
  139. else
  140. this.list[i].className = "disabled";
  141. }
  142. }
  143.  
  144. };
  145.  
  146. var mycroft = {
  147.  
  148. plugins: null,
  149.  
  150. addLinks: function(p) {
  151.  
  152. if (p) {
  153. this.plugins = document.evaluate('//a[@href="/jsreq.html"]',
  154. p, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  155. var reviews = document.evaluate('//a[.="[Review]"]',
  156. p, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  157. var addLink = document.createElement("a");
  158. addLink.setAttribute("href", "javascript:void(0)");
  159. addLink.setAttribute("style", "margin-left:5px; color:#000099");
  160. addLink.innerHTML = "[Add to DDG]";
  161. for (var i = 0, tmp; i < reviews.snapshotLength; i++) {
  162. tmp = addLink.cloneNode(true);
  163. tmp.setAttribute("data-ind", i);
  164. tmp.addEventListener("click", this.addNewEngine, false);
  165. reviews.snapshotItem(i).parentNode.insertBefore(tmp, reviews.snapshotItem(i).nextSibling);
  166. }
  167. }
  168.  
  169. },
  170.  
  171. addNewEngine: function() {
  172. var current = GM_getValue("engines", ddg_e.
  173. default);
  174. var i = this.getAttribute("data-ind");
  175. var name = mycroft.plugins.snapshotItem(i).innerHTML.split(" (")[0].split(" -")[0];
  176. var newEngine = mycroft.plugins.snapshotItem(i).getAttribute("onClick").split("'")[1];
  177. var newName = prompt("This engine will be added to DDG Extended.\nGive a name or cancel.", name);
  178. if (newName && newName.length > 0) {
  179. this.innerHTML = "[Added]";
  180. this.removeEventListener("click", this.addNewEngine, false);
  181. this.style.color = "#009900";
  182. this.removeAttribute("href");
  183.  
  184. GM_xmlhttpRequest({
  185. method: "GET",
  186. url: "http://mycroftproject.com/externalos.php/" + newEngine + ".xml",
  187. onload: function(response) {
  188. var responseXML = null;
  189. // Inject responseXML into existing Object (only appropriate for XML content).
  190. if (!response.responseXML) {
  191. responseXML = new DOMParser()
  192. .parseFromString(response.responseText, "text/xml");
  193. }
  194. var engine = responseXML.getElementsByTagName("Url");
  195. if (engine.length > 0 && engine[0].getAttribute("template"))
  196. GM_setValue("engines", current + ";;" + newName + "==" + engine[0].getAttribute("template"));
  197. }
  198. });
  199. }
  200. }
  201. };
  202.  
  203. // FUNCTIONS
  204. function onHashChange() {
  205. if (window.location.hash.indexOf("{searchTerms}") !== -1)
  206. window.location.hash = "";
  207. window.addEventListener("hashchange", function() {
  208. if (window.location.hash.indexOf("{searchTerms}") !== -1)
  209. window.location.href = window.location.hash.substr(1).replace("{searchTerms}", escape(document.getElementById("search_form_input").value));
  210. }, false);
  211. }
  212.  
  213. function onColorChange(h, c) {
  214. if (c !== null) {
  215. c.addEventListener("change", function() {
  216. for (var i = 0; i < ddg_e.engines.length; i++)
  217. ddg_e.engines[i].style.background = h.style.background;
  218. }, false);
  219. }
  220. }
  221.  
  222. function restoreEngines() {
  223. if (confirm("Do you want to restore the default search engines?")) {
  224. GM_setValue("engines", ddg_e.
  225. default);
  226. ddg_e.newList();
  227. }
  228. }
  229.  
  230. function saveActions() {
  231. disableEvents();
  232. ddg_e.set();
  233. }
  234.  
  235. function cancelActions() {
  236. disableEvents();
  237. ddg_e.newList();
  238. }
  239.  
  240. function enableDrag() {
  241. options.toggleClass();
  242. for (var i = 0; i < ddg_e.engines.length; i++) {
  243. ddg_e.engines[i].style.cursor = "move";
  244. ddg_e.engines[i].className = "disabled";
  245. ddg_e.engines[i].addEventListener("dragstart", handleDragStart, false);
  246. ddg_e.engines[i].addEventListener("dragover", handleDragOver, false);
  247. ddg_e.engines[i].addEventListener("drop", handleDrop, false);
  248. }
  249. }
  250.  
  251. function enableRemove() {
  252. options.toggleClass();
  253. for (var i = 0; i < ddg_e.engines.length; i++) {
  254. ddg_e.engines[i].style.cursor = "crosshair";
  255. ddg_e.engines[i].className = "disabled";
  256. ddg_e.engines[i].addEventListener("click", remove, false);
  257. }
  258. }
  259.  
  260. function enableRename() {
  261. options.toggleClass();
  262. for (var i = 0; i < ddg_e.engines.length; i++) {
  263. ddg_e.engines[i].style.cursor = "text";
  264. ddg_e.engines[i].className = "disabled";
  265. ddg_e.engines[i].addEventListener("click", rename, false);
  266. }
  267. }
  268.  
  269. function disableEvents() {
  270. options.toggleClass();
  271. for (var i = 0; i < ddg_e.engines.length; i++) {
  272. ddg_e.engines[i].style.cursor = "auto";
  273. ddg_e.engines[i].removeAttribute("class");
  274. ddg_e.engines[i].removeEventListener("dragstart", handleDragStart, false);
  275. ddg_e.engines[i].removeEventListener("dragover", handleDragOver, false);
  276. ddg_e.engines[i].removeEventListener("drop", handleDrop, false);
  277. ddg_e.engines[i].removeEventListener("click", remove, false);
  278. ddg_e.engines[i].removeEventListener("click", rename, false);
  279. }
  280. }
  281.  
  282. function remove() {
  283. this.parentNode.removeChild(this);
  284. }
  285.  
  286. function rename() {
  287. var n = prompt("Rename search engine", this.textContent);
  288. if (n && n.length > 0 && n.length < 20)
  289. this.firstChild.innerHTML = n;
  290. }
  291.  
  292. function newEngine() {
  293. var n = prompt("Search and add new search engines from MyCroft.\nYou can search by name and url.", "");
  294. if (n && n.length > 0)
  295. window.location.href = "http://mycroftproject.com/search-engines.html?name=" + n;
  296. }
  297.  
  298.  
  299. /* Pure javascript and html5 drag-and-drop for an ordered list
  300. * source: https://gist.github.com/robophilosopher/7520460
  301. */
  302. var dragSrcEl,drop_index,numLis,count,token1,token2;
  303. function handleDragStart(e) {
  304. dragSrcEl = this;
  305. e.dataTransfer.effectAllowed = "move";
  306. e.dataTransfer.setData("text/html", this.innerHTML);
  307. }
  308.  
  309. function handleDragOver(e) {
  310. if (e.preventDefault) e.preventDefault();
  311. return false;
  312. }
  313.  
  314. function handleDrop(e) {
  315. if (e.stopPropagation) e.stopPropagation();
  316. if (e.preventDefault) e.preventDefault();
  317. drop_index = this.value;
  318. numLis = parseInt(drop_index - dragSrcEl.value);
  319. count = Math.abs(numLis);
  320. if (dragSrcEl != this) {
  321. // swap data when premises are adjacent
  322. if (Math.abs(numLis) == 1) {
  323. dragSrcEl.innerHTML = this.innerHTML;
  324. this.innerHTML = e.dataTransfer.getData("text/html");
  325. }
  326. // propagate data from non-adjacent drops
  327. if (Math.abs(numLis) > 1) {
  328. token1 = this.innerHTML;
  329. this.innerHTML = e.dataTransfer.getData("text/html");
  330. // bring premise up list
  331. if (numLis < -1) {
  332. for (var i = drop_index, counter = 0; counter < count; counter++, i++) {
  333. token2 = ddg_e.engines[i].innerHTML;
  334. ddg_e.engines[i].innerHTML = token1;
  335. token1 = token2;
  336. }
  337. }
  338. // bring premise down list
  339. if (numLis > 1) {
  340. for (var i = drop_index - 1, counter = 0; counter < count; counter++, i--) {
  341. token2 = ddg_e.engines[i - 1].innerHTML;
  342. ddg_e.engines[i - 1].innerHTML = token1;
  343. token1 = token2;
  344. }
  345. }
  346. }
  347. }
  348. return false;
  349. }
  350.  
  351. // START
  352. if (window.location.href.indexOf("http://mycroftproject.com/") !== -1) {
  353. mycroft.addLinks(document.getElementById("plugins"));
  354. } else {
  355. var header = document.getElementById("header");
  356. if (header) {
  357. ddg_e.append(header);
  358. options.create(document.getElementById("header_button_menu"));
  359. onColorChange(header, document.getElementById("kj"));
  360. onHashChange();
  361. }
  362. }