Faris Handy Webdev JavaScript functions

A bunch of useful JavaScript functions

目前為 2018-11-15 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Faris Handy Webdev JavaScript functions
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.4
  5. // @description A bunch of useful JavaScript functions
  6. // @description This is not a regular script for you to run! Only use this via the @require keyword.
  7. // @author Faris Hijazi
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_download
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_setClipboard
  13. // @grant unsafeWindow
  14. // @grant window.close
  15. // @grant window.focus
  16. // @run-at document-start
  17. // @include *
  18. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  19. // ==/UserScript==
  20.  
  21.  
  22. // adding Element.before() and Element.after() (since some brwosers like MS Edge don't already have them)
  23. if (Element.prototype.before === undefined) Element.prototype.before = function (newNode) {
  24. if (this.parentNode) {
  25. return this.parentNode.insertBefore(newNode, this);
  26. }
  27. };
  28. if (Element.prototype.after === undefined) Element.prototype.after = function (newNode) {
  29. if (this.parentNode) {
  30. return this.parentNode.insertBefore(newNode, this.nextSibling);
  31. }
  32. };
  33.  
  34. /**/
  35. if (typeof unsafeWindow === "undefined") unsafeWindow = window;
  36.  
  37. unsafeWindow.URL_REGEX_STR = `(//|http(s?:))[\\d\\w?%\\-_/\\\\=.]+?`;
  38. unsafeWindow.IMAGE_URL_REGEX = new RegExp(`(//|http(s?:))[\\d\\w?%\\-_/\\\\=.]+?\\.(jpg|png|jpeg|gif|tiff|&f=1)`, 'gim');
  39. // /(\/\/|http(s?:))[\d\w?%\-_\/\\=.]+?\.(jpg|png|jpeg|gif|tiff|&f=1)/gim;
  40. unsafeWindow.VID_URL_REGEX = /(\/\/|http(s?:))[\d\w?%\-_\/\\=.]+?\.(mov|webm|mp4|wmv|&f=1)/gim;
  41.  
  42. var useEncryptedGoogle = /encrypted.google.com/.test(location.hostname);
  43. var googleBaseURL = `https://${/google\./.test(location.hostname) ? location.hostname :
  44. ((useEncryptedGoogle ? "encrypted" : "www") + ".google.com")}`;
  45.  
  46. unsafeWindow.gImgSearchURL = `${googleBaseURL}/search?&hl=en&tbm=isch&q=`;
  47. unsafeWindow.GIMG_REVERSE_SEARCH_URL = `${googleBaseURL}/searchbyimage?&image_url=`;
  48.  
  49. if (typeof GM_setClipboard !== 'undefined') {
  50. unsafeWindow.setClipboard = GM_setClipboard;
  51. unsafeWindow.GM_setClipboard = GM_setClipboard;
  52. }
  53. if (typeof GM_xmlhttpRequest !== 'undefined') {
  54. unsafeWindow.GM_xmlhttpRequest =
  55. /**
  56. * Description
  57. GM_xmlhttpRequest is a cross-origin version of XMLHttpRequest. The beauty of this function is that a user script can make requests that do not use the same-origin policy, creating opportunities for powerful mashups.
  58.  
  59. Restrictions
  60. GM_xmlhttpRequest restricts access to the http, https, ftp, data, blob, and moz-blob protocols.
  61.  
  62. If a script uses one or more @domains then the GM_xmlhttpRequest api will be restricted to those domains.
  63.  
  64. If the url provided does not pass the above criteria then a error will be thrown when calling GM_xmlhttpRequest
  65.  
  66. Arguments
  67. Object details
  68. A single object with properties defining the request behavior.
  69.  
  70. String method: Optional. The HTTP method to utilize. Currently only "GET" and "POST" are supported. Defaults to "GET".
  71. String url: The URL to which the request will be sent. This value may be relative to the page the user script is running on.
  72. Function onload: Optional. A function called if the request finishes successfully. Passed a Scriptish response object (see below).
  73. Function onerror: Optional. A function called if the request fails. Passed a Scriptish response object (see below).
  74. Function onreadystatechange: Optional. A function called whenever the request's readyState changes. Passed a Scriptish response object (see below).
  75. String data: Optional. Content to send as the body of the request.
  76. Object headers: Optional. An object containing headers to be sent as part of the request.
  77. Boolean binary: Optional. Forces the request to send data as binary. Defaults to false.
  78. Boolean makePrivate: Optional. Forces the request to be a private request (same as initiated from a private window). (0.1.9+)
  79. Boolean mozBackgroundRequest: Optional. If true security dialogs will not be shown, and the request will fail. Defaults to true.
  80. String user: Optional. The user name to use for authentication purposes. Defaults to the empty string "".
  81. String password: Optional. The password to use for authentication purposes. Defaults to the empty string "".
  82. String overrideMimeType: Optional. Overrides the MIME type returned by the server.
  83. Boolean ignoreCache: Optional. Forces a request to the server, bypassing the cache. Defaults to false.
  84. Boolean ignoreRedirect: Optional. Forces the request to ignore both temporary and permanent redirects.
  85. Boolean ignoreTempRedirect: Optional. Forces the request to ignore only temporary redirects.
  86. Boolean ignorePermanentRedirect: Optional. Forces the request to ignore only permanent redirects.
  87. Boolean failOnRedirect: Optional. Forces the request to fail if a redirect occurs.
  88. Integer redirectionLimit: Optional. Range allowed: 0-10. Forces the request to fail if a certain number of redirects occur.
  89. Note: A redirectionLimit of 0 is equivalent to setting failOnRedirect to true.
  90. Note: If both are set, redirectionLimit will take priority over failOnRedirect.
  91.  
  92. Note: When ignore*Redirect is set and a redirect is encountered the request will still succeed, and subsequently call onload. failOnRedirect or redirectionLimit exhaustion, however, will produce an error when encountering a redirect, and subsequently call onerror.
  93.  
  94. Response Object
  95. This is the response object passed to the onload, onerror, and onreadystatechange callbacks described for the details object above.
  96.  
  97. String responseText: The response to the request in text form.
  98. String responseJSON: If the content type is JSON (example: application/json, text/x-json, and more..) then responseJSON will be available.
  99. Integer readyState: The state of the request. Refer to https://developer.mozilla.org/en/XMLHttpRequest#Properties
  100. String responseHeaders: The string value of all response headers. null if no response has been received.
  101. Integer status: The HTTP status code from the server. null if the request hasn't yet completed, or resulted in an error.
  102. String statusText: The entire HTTP status response string from the server. null if the request hasn't yet completed, or resulted in an error.
  103. String finalUrl: The final URL used for the request. Takes redirects into account. null if the request hasn't yet completed, or resulted in an error.
  104. For "onprogress" only:
  105.  
  106. Boolean lengthComputable: Whether it is currently possible to know the total size of the response.
  107. Integer loaded: The number of bytes loaded thus far.
  108. Integer total: The total size of the response.
  109. Returns
  110. */
  111. GM_xmlhttpRequest;
  112. }
  113.  
  114. unsafeWindow.log = console.debug;
  115. unsafeWindow.setLog = newDebugState => debug = (typeof newDebugState === "boolean") ? newDebugState : debug;
  116. unsafeWindow.matchSite = matchSite;
  117. unsafeWindow.createElement = createElement;
  118. unsafeWindow.loadScript = loadScript;
  119. unsafeWindow.Proxy = {
  120. fileStack: url => (`https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/${encodeURIComponent(url.trim())}`),
  121. steemitimages: url => /\.(jpg|jpeg|tiff|png|gif)($|\?)/i.test(url) ? (`https://steemitimages.com/0x0/${url.trim()}`) : url,
  122. ddg: ddgProxy
  123. };
  124. unsafeWindow.ddgProxy = ddgProxy;
  125. unsafeWindow.getOGZscalarUrl = getOGZscalarUrl;
  126. unsafeWindow.reverseDdgProxy = reverseDdgProxy;
  127. unsafeWindow.isDdgUrl = isDdgUrl;
  128. unsafeWindow.targetIsInput = targetIsInput;
  129. unsafeWindow.createAndAddAttribute = createAndAddAttribute;
  130. unsafeWindow.getGImgReverseSearchURL = getGImgReverseSearchURL;
  131.  
  132. unsafeWindow.toDdgProxy = () => location.href = ddgProxy(location.href);
  133. unsafeWindow.isIterable = obj => obj != null && typeof obj[Symbol.iterator] == 'function';
  134. unsafeWindow.GM_setValue = GM_setValue;
  135. unsafeWindow.GM_getValue = GM_getValue;
  136.  
  137. // unsafeWindow.q = q;
  138. // unsafeWindow.qa = qa;
  139. unsafeWindow.siteSearchUrl = siteSearchUrl;
  140. unsafeWindow.getAbsoluteURI = getAbsoluteURI;
  141.  
  142. /**Returns the HOSTNAME of a website url*/
  143. unsafeWindow.getHostname = getHostname;
  144. /***/
  145. unsafeWindow.openAllLinks = function () {
  146. Array.from(document.links).forEach(function (link) {
  147. if (link.hasAttribute("href")) {
  148. window.open(link.href);
  149. }
  150. });
  151. };
  152.  
  153.  
  154. unsafeWindow.getElementsByXPath = function getElementsByXPath(xpath, parent) {
  155. let results = [];
  156. let query = document.evaluate(xpath,
  157. parent || document,
  158. null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  159. for (let i = 0, length = query.snapshotLength; i < length; ++i) {
  160. results.push(query.snapshotItem(i));
  161. }
  162. return results;
  163. };
  164.  
  165. /**Returns a DuckDuckGo proxy url (attempts to unblock the url)*/
  166. function ddgProxy(href) {
  167. return isDdgUrl(href) || /^(javascript)/i.test(href) ? href : (`https://proxy.duckduckgo.com/iu/?u=${encodeURIComponent(href)}&f=1`);
  168. }
  169.  
  170. /**Opens the url via fetch(), then performs a callback giving it the document element*/
  171. unsafeWindow.fetchElement = fetchElement;
  172. /**Opens the url via xmlhttpRequest, then performs a callback giving it the document element*/
  173. unsafeWindow.xmlRequestElement = xmlRequestElement;
  174. unsafeWindow.onLoadDim = onLoadDim;
  175. unsafeWindow.addCss = addCss;
  176.  
  177. /**
  178. * @author https://codepen.io/frosas/
  179. * Also works with scripts from other sites if they have CORS enabled (look for the header Access-Control-Allow-Origin: *).
  180. *
  181. * // Usage
  182. * var url = 'https://raw.githubusercontent.com/buzamahmooza/Helpful-Web-Userscripts/master/GM_dummy_functions.js?token=AZoN2Rl0UPDtcrOIgaESbGp_tuHy51Hmks5bpijqwA%3D%3D';
  183. * loadGitHubScript(url).then((event) => { });
  184. */
  185. function loadGitHubScript(url) {
  186. return fetch(url).then(res => res.blob()).then(body => loadScript(URL.createObjectURL(body)));
  187.  
  188. function loadScript(url) {
  189. return new Promise(function (resolve, reject) {
  190. var script = document.createElement('script');
  191. script.src = url;
  192. script.onload = resolve;
  193. script.onerror = function () {
  194. console.warn("couldn't load script: ", url);
  195. if (typeof reject === 'function')
  196. reject();
  197. }; // TODO Not sure it really works
  198. document.head.appendChild(script);
  199. });
  200. }
  201. }
  202.  
  203.  
  204. /**@deprecated doesn't actually succeed*/
  205. unsafeWindow.addJs = function addJs(js, id) {
  206. const jsScript = document.createElement('script');
  207. jsScript.appendChild(document.createTextNode(js));
  208. if (!!id) jsScript.id = id;
  209. jsScript.classList.add('addJs');
  210. return document.getElementsByTagName('head')[0].appendChild(jsScript);
  211. };
  212. unsafeWindow.observe = observe;
  213. unsafeWindow.gfycatPage2GifUrl = function (gfycatPageUrl) {
  214. if (!/https:\/\/gfycat\.com\/gifs\/detail\/.+/.test(gfycatPageUrl)) {
  215. throw error("Not a gfycat home url:" + gfycatPageUrl);
  216. }
  217. return `https://thumbs.gfycat.com/${gfycatPageUrl.split('/').pop()}-size_restricted.gif`;
  218. };
  219. unsafeWindow.preloader = preloader;
  220. unsafeWindow.waitForElement = waitForElement;
  221. unsafeWindow.includeJs = includeJs;
  222. /**
  223. * Appends a style element to the head of the document containing the given cssStr text
  224. * @param cssStr
  225. * @param id
  226. * @return {HTMLStyleElement}
  227. */
  228. function addCss(cssStr, id) {
  229. const style = document.createElement('style');
  230. if (style.styleSheet) {
  231. style.styleSheet.cssText = cssStr;
  232. } else {
  233. style.appendChild(document.createTextNode(cssStr));
  234. }
  235. if (!!id) style.id = id;
  236. style.classList.add('addCss');
  237. return document.getElementsByTagName('head')[0].appendChild(style);
  238. }
  239. unsafeWindow.disableStyles = disableStyles;
  240. function disableStyles(enable) {
  241. console.log('Disabling styles');
  242. for (const styleEl of document.querySelectorAll('style, link')) {
  243. styleEl.disabled = !enable
  244. }
  245. }
  246.  
  247. unsafeWindow.createAndGetNavbar = createAndGetNavbar;
  248. /**
  249. * Creates a static navbar at the top of the page.
  250. * Useful for adding buttons and controls to it
  251. * @param callback this callback should be used when instantly adding content to the navbar,
  252. * do NOT just take the returned value and start adding elements.
  253. * @return {HTMLDivElement} returns the parent navbar element
  254. */
  255. function createAndGetNavbar(callback) {
  256. // Settings up the navbar
  257. // language=CSS
  258. addCss(`
  259. div#topnav {
  260. position: fixed;
  261. z-index: 1000;
  262. min-height: 50px;
  263. top: 0;
  264. right: 0;
  265. left: 0;
  266. background: #525252;
  267. font-size: 14px;
  268. }
  269.  
  270. div#topnav-content {
  271. margin-left: 115px;
  272. padding: 10px;
  273. font-family: inherit;
  274. font-stretch: extra-condensed;
  275. font-size: 20px;
  276. }`, "navbar-css");
  277.  
  278. function adjustTopMargin() {
  279. document.body.style.top = `${q('#topnav').offsetHeight}px`;
  280. }
  281.  
  282. const navbar = document.createElement(`div`);
  283. navbar.id = "topnav";
  284. const navbarContentDiv = document.createElement('div');
  285. navbarContentDiv.id = "topnav-content";
  286.  
  287. navbar.appendChild(navbarContentDiv);
  288.  
  289. document.body.firstElementChild.before(navbar);
  290.  
  291. window.addEventListener('resize', adjustTopMargin);
  292.  
  293. document.body.style.position = "relative";
  294.  
  295. // keep trying to use the callback, works when the navbarContentDiv is finally added
  296. var interval = setInterval(function () {
  297. const topnavContentDiv = q('#topnav-content');
  298. if (topnavContentDiv) {
  299. clearInterval(interval);
  300. if (callback)
  301. callback(topnavContentDiv);
  302. adjustTopMargin();
  303. }
  304. }, 100);
  305. return navbar;
  306. }
  307.  
  308.  
  309. unsafeWindow.setStyleInHTML = setStyleInHTML;
  310. /**
  311. * This will set the style of an element by force, by manipulating the style HTML attribute.
  312. * This gives you more control, you can set the exact text you want in the HTML element (like giving a style priority via "!important").
  313. * Example calls:
  314. * setStyleByHTML(el, "background-image", "url(http://www.example.com/cool.png)")
  315. * setStyleByHTML(el, "{ background-image : url(http://www.example.com/cool.png) }")
  316. * @param {HTMLElement} el
  317. * @param {String} styleProperty
  318. * @param {String} styleValue
  319. * @return el
  320. */
  321. function setStyleInHTML(el, styleProperty, styleValue) {
  322. styleProperty = styleProperty.trim().replace(/^.*{|}.*$/g, '');
  323.  
  324. const split = styleProperty.split(':');
  325. if (!styleValue && split.length > 1) {
  326. styleValue = split.pop();
  327. styleProperty = split.pop();
  328. }
  329.  
  330. if (el.hasAttribute('style')) {
  331. const styleText = el.getAttribute('style');
  332. const styleArgument = `${styleProperty}: ${styleValue};`;
  333.  
  334. let newStyle = new RegExp(styleProperty, 'i').test(styleText) ?
  335. styleText.replace(new RegExp(`${styleProperty}:.+?;`, 'im'), styleArgument) :
  336. `${styleText} ${styleArgument}`;
  337.  
  338. el.setAttribute('style', newStyle);
  339.  
  340. console.debug(
  341. 'adding to style ', `"${styleArgument}"`,
  342. '\nnewStyle:', `"${newStyle}"`,
  343. '\nelement:', el
  344. );
  345. }
  346. return el;
  347. }
  348. Math.clamp = function (a, min, max) {
  349. return a < min ? min :
  350. a > max ? max : a;
  351. };
  352.  
  353. /**
  354. * @param targetElement
  355. * @param callback
  356. * @param options mutationObserver options{ childList: boolean, subtree: boolean, attributes: boolean, characterData: boolean }
  357. * @returns the mutationObserver object
  358. */
  359. function observe(targetElement, callback, options) {
  360. if (!targetElement) targetElement = document.body;
  361. if (!options) {
  362. options = {
  363. childList: true, subtree: true,
  364. attributes: false, characterData: false
  365. };
  366. }
  367. const mutationsHandler = function (mutations) {
  368. for (const mutation of mutations) {
  369. if (mutation.addedNodes.length) {
  370. callback(mutation.target);
  371. }
  372. callback();
  373. }
  374. };
  375. callback(targetElement);
  376. const mutationObserver = new MutationObserver(mutationsHandler);
  377. mutationObserver.observe(targetElement, options);
  378. return mutationObserver;
  379. }
  380.  
  381. function getGImgReverseSearchURL(url) {
  382. // console.debug('gImgReverseSearchURL=', gImgReverseSearchURL);
  383. return url ? GIMG_REVERSE_SEARCH_URL + encodeURIComponent(url.trim()) : "";
  384. }
  385.  
  386. unsafeWindow.nodeDepth = nodeDepth;
  387. /**
  388. * returns the number of nodes between the child and parent
  389. * @param child
  390. * @param parent the parent (direct or indirect) of the child element
  391. * @param currentDepth used for recursion only, do NOT modify it's value
  392. * @return {number}
  393. */
  394. function nodeDepth(child, parent = document, currentDepth = 0) {
  395. if (!child || !parent) throw "Both the child and parent must non-null.";
  396. if (!parent.contains(child)) throw "The given parent does not contain the child.";
  397.  
  398. currentDepth++;
  399. return child.parentNode == parent ?
  400. currentDepth :
  401. nodeDepth(child.parentNode, parent, currentDepth);
  402. }
  403.  
  404. /**Returns the href wrapped with proxy.DuckDuckGo.com */
  405. function reverseDdgProxy(href) {
  406. var s = href;
  407. if (isZscalarUrl(href)) s = getOGZscalarUrl(href); // extra functionality:
  408. if (isDdgUrl(href)) {
  409. s = new URL(location.href).searchParams.get('u');
  410. }
  411. // https://proxy.duckduckgo.com/iu/?u=
  412. if (s && s[0]) {
  413. return decodeURIComponent(s[0]);
  414. } else {
  415. console.log('Was unable to reverseDDGProxy for URL:', href);
  416. return s;
  417. }
  418. }
  419.  
  420. unsafeWindow.regexBetween = function (precedingRegEx, betweenRegEx, proceedingRegEx, regexOptions) {
  421. return new RegExp(`(?<=(${precedingRegEx}))(${!betweenRegEx ? ".+?" : betweenRegEx})(?=(${proceedingRegEx}))`, regexOptions);
  422. };
  423. unsafeWindow.extend = typeof($) == 'undefined' ? null : $.extend;
  424.  
  425. function preloader(imgUrls) {
  426. console.log('imgs passed:', imgUrls);
  427. let imgObjs = [];
  428. // start preloading
  429. for (const url of imgUrls) {
  430. // create object
  431. let imageObj = new Image();
  432. imageObj.src = url;
  433. imageObj.onload = (function () {
  434. console.log('ImageLoaded:', this.src, this);
  435. });
  436. imgObjs.push(imageObj);
  437. }
  438. }
  439.  
  440. // http://code.jquery.com/jquery.js
  441. function includeJs(src) {
  442. const script = document.createElement('script');
  443. script.setAttribute('src', src);
  444. document.getElementsByTagName('head')[0].appendChild(script);
  445. return script;
  446. }
  447.  
  448. /**@WIP
  449. * @param {function, string} elementGetter a function to get the wanted element (or event a condition function)
  450. * that will be called to test if the element has appeared yet. (should return true only when the element appears)
  451. * @param callback the elementGetter will be passed as the first argument
  452. * @return {MutationObserver}
  453. */
  454. function waitForElement(elementGetter, callback) {
  455. const observerCallback = function (mutations, me) {
  456. function handleSuccess(node) {
  457. callback(node);
  458. me.disconnect();
  459. }
  460.  
  461. var node = (typeof(elementGetter) === 'function') ? elementGetter() :
  462. document.querySelector(elementGetter);
  463. try {
  464. if (node) {
  465. if (node.length) {
  466. for (const n of node)
  467. handleSuccess(n);
  468. } else if (node.length === undefined) {
  469. handleSuccess(node);
  470. }
  471. }
  472. } catch (e) {
  473. console.warn(e);
  474. }
  475. };
  476.  
  477. const observer = new MutationObserver(observerCallback);
  478. observerCallback(null, observer);
  479.  
  480. observer.observe(document.body, {
  481. childList: true
  482. , subtree: true
  483. , attributes: false
  484. , characterData: false
  485. });
  486. return observer;
  487. }
  488.  
  489. /**
  490. * cross-browser wheel delta
  491. * Returns the mousewheel scroll delta as -1 (wheelUp) or 1 (wheelDown) (cross-browser support)
  492. * @param {MouseWheelEvent} wheelEvent
  493. * @return {number} -1 or 1
  494. */
  495. unsafeWindow.getWheelDelta = function getWheelDelta(wheelEvent) {
  496. // cross-browser wheel delta
  497. wheelEvent = window.event || wheelEvent; // old IE support
  498. return Math.max(-1, Math.min(1, (wheelEvent.wheelDelta || -wheelEvent.detail)));
  499. };
  500. unsafeWindow.elementUnderMouse = function elementUnderMouse(wheelEvent) {
  501. return document.elementFromPoint(wheelEvent.clientX, wheelEvent.clientY);
  502. };
  503.  
  504. /** Create an element by typing it's inner HTML.
  505. For example: var myAnchor = createElement('<a href="https://example.com">Go to example.com</a>');
  506. * @param html
  507. * @param callback optional callback, invoked once the element is created, the element is passed.
  508. * @return {HTMLElement}
  509. */
  510. function createElement(html, callback) {
  511. const div = document.createElement('div');
  512. div.innerHTML = (html).trim();
  513. const element = div.firstElementChild;
  514. if (!!callback && callback.call)
  515. callback.call(null, element);
  516.  
  517. return element;
  518. }
  519. /* todo: remove, this thing is terrible and has no point */
  520. function matchSite(siteRegex) {
  521. let result = location.href.match(siteRegex);
  522. if (!!result) console.debug("Site matched regex: " + siteRegex);
  523. return result;
  524. }
  525. function siteSearchUrl(query) {
  526. if (query) {
  527. return gImgSearchURL + "site:" + encodeURIComponent(query.trim());
  528. }
  529. }
  530.  
  531. /**
  532. * removes all coded functionality to the element by removing it and reappending it's outerHTML
  533. */
  534. function clearElementFunctions(element) {
  535. const outerHTML = element.outerHTML;
  536. element.after(createElement(outerHTML));
  537. element.remove();
  538. }
  539. unsafeWindow.clearElementFunctions = clearElementFunctions;
  540.  
  541. /**abbreviation for querySelectorAll()
  542. * @param selector
  543. * @param node
  544. * @return {set<HTMLElement>} */
  545. function qa(selector, node = document) {
  546. return node.querySelectorAll(selector);
  547. }
  548. /**abbreviation for querySelector()
  549. * @param selector
  550. * @param node
  551. * @return {HTMLElement} */
  552. function q(selector, node = document) {
  553. return node.querySelector(selector);
  554. }
  555.  
  556. unsafeWindow.incrementUrl = incrementUrl;
  557. /** Returns a modified url as a string, either incremented(++) or decremented(--)
  558. * @param {string} href the input url you want to modify
  559. * @param {number} incrAmount (optional) the amount to increment.
  560. * Default: increment by 1 (++).
  561. * If the result in the url becomes negative, it will be overridden to 0.
  562. * @return {string} incremented/decremented url string */
  563. function incrementUrl(href, incrAmount) {
  564. var e, s;
  565. let IB = incrAmount ? incrAmount : 1;
  566.  
  567. function isDigit(c) {
  568. return ("0" <= c && c <= "9")
  569. }
  570. const tip = location.href.match(/([&?]|$).*$/)[0];
  571. let L = href.replace(tip, "");
  572. let LL = L.length;
  573. for (e = LL - 1; e >= 0; --e) if (isDigit(L.charAt(e))) {
  574. for (s = e - 1; s >= 0; --s) if (!isDigit(L.charAt(s))) break;
  575. break;
  576. }
  577. ++s;
  578. if (e < 0) return;
  579. let oldNum = L.substring(s, e + 1);
  580. let newNum = (parseInt(oldNum, 10) + IB);
  581. if (newNum < 0) newNum = 0;
  582. let newNumStr = "" + newNum;
  583. while (newNumStr.length < oldNum.length)
  584. newNumStr = "0" + newNumStr;
  585.  
  586. return (L.substring(0, s) + "" + newNumStr + "" + L.slice(e + 1) + "" + tip);
  587. }
  588.  
  589. unsafeWindow.printElementTextAttributes = printElementTextAttributes;
  590. function printElementTextAttributes(el) {
  591. console.log(
  592. 'innerText:', el.innerText,
  593. '\nOuterText:', el.outerHTML,
  594. '\nInnerHTML:', el.innerHTML,
  595. '\nouterHTML:', el.outerHTML
  596. );
  597. }
  598. function isZscalarUrl(zscalarUrl) {
  599. return /https:\/\/zscaler\.kfupm\.edu\.sa\/Default\.aspx\?url=/.test(zscalarUrl);
  600. }
  601. /**
  602. * @param zscalarUrl {string}
  603. * @returns {string} the original link that ZScalar is blocking
  604. */
  605. function getOGZscalarUrl(zscalarUrl) {
  606. if (!isZscalarUrl(zscalarUrl)) {
  607. return zscalarUrl;
  608. } // not a zscalar url
  609. zscalarUrl = zscalarUrl.trim();
  610. let x = decodeURIComponent(('' + zscalarUrl).substring(46, zscalarUrl.indexOf('&referer')));
  611. // let x = decodeURIComponent(('' + zscalarUrl).substring(46, zscalarUrl.indexOf('&referer')));
  612. console.debug('Extracted ZScalar original link:', x);
  613. return x;
  614. }
  615.  
  616. /*function loadScript(url, callback) {
  617. let script = document.createElement("script");
  618. script.type = "text/javascript";
  619. if (script.readyState) { //IE
  620. script.onreadystatechange = function () {
  621. if (script.readyState === "loaded" ||
  622. script.readyState === "complete") {
  623. script.onreadystatechange = null;
  624. callback();
  625. }
  626. };
  627. } else { //Others
  628. script.onload = function () {
  629. callback();
  630. };
  631. }
  632. script.src = url;
  633. document.getElementsByTagName("head")[0].appendChild(script);
  634. }*/
  635. function loadScript(url, callback, type) {
  636. if (!callback) callback = () => console.log('Script laoded:', url);
  637. // Adding the script tag to the head as suggested before
  638. var head = document.getElementsByTagName('head')[0];
  639. var script = document.createElement('script');
  640. if (!type) type = 'text/javascript';
  641. script.type = type;
  642. script.src = url;
  643.  
  644. // Then bind the event to the callback function.
  645. // There are several events for cross browser compatibility.
  646. script.onreadystatechange = callback;
  647. script.onload = callback;
  648.  
  649. // Fire the loading
  650. head.appendChild(script);
  651. return script;
  652. }
  653.  
  654. unsafeWindow.loadModule = loadModule;
  655. unsafeWindow.getElementsWithText = getElementsWithText;
  656. window.document.getElementsWithText = getElementsWithText;
  657. /**
  658. * Iterates through all HTMLElements and returns the ones that contains innerText matching the given regex/substr
  659. * @param textRegex
  660. * @param preliminarySelector a node selector to narrow down the search from the start
  661. * @return {[]}
  662. */
  663. function getElementsWithText(textRegex, preliminarySelector) {
  664. if (!textRegex) {
  665. console.error('must have an input value');
  666. return;
  667. }
  668. const matchingEls = [];
  669. const useSubstring = (typeof textRegex === 'string');
  670. function elementContainsText(element, regex) {
  671. return !(regex && element && element.innerText) ? false :
  672. (useSubstring ? element.innerText.indexOf(regex) > -1 : element.innerText.match(textRegex));
  673. }
  674.  
  675. for (const el of document.querySelectorAll(preliminarySelector || '*')) {
  676. if (elementContainsText(el, textRegex)) {
  677. var nothingContainsEl = true;
  678. for (var i = 0; i < matchingEls.length; i++)
  679. if (matchingEls[i] && matchingEls[i].contains(el) && !Object.is(matchingEls[i], el)) {
  680. matchingEls[i] = null; // that old parentEl is now gone, we only want the deepest node
  681. nothingContainsEl = false;
  682. }
  683. matchingEls.push(el);
  684. }
  685. }
  686. return matchingEls.filter(el => el != null);
  687. }
  688.  
  689. function loadModule(url, callback) {
  690. return loadScript(url, callback, 'module');
  691. }
  692. /**
  693. * Creates and adds an attributeNode to the element (if the element doesn't have that attribute)
  694. * sets the attribute value
  695. * @param node
  696. * @param attributeName
  697. * @param attributeValue
  698. */
  699. function createAndAddAttribute(node, attributeName, attributeValue) {
  700. if (!node) {
  701. console.error('Node is null, cannot add attribute.');
  702. return;
  703. }
  704.  
  705. if (!node.hasAttribute(attributeName)) {
  706. var attr = document.createAttribute(attributeName);
  707. attr.value = attributeValue;
  708. node.setAttributeNode(attr);
  709. }
  710. if (!!attributeValue) {
  711. node.setAttribute(attributeName, attributeValue);
  712. }
  713. }
  714.  
  715. /** Deal with relative URIs (URIs starting with "/" or "//") */
  716. function getAbsoluteURI(inputUrl) {
  717. let reconstructedUri = inputUrl
  718. .replace(new RegExp(`^//`), `${location.protocol}//`) // If string starts with "//", replace with protocol
  719. .replace(new RegExp(`^/`), `${getHostname(location.href)}/`);// convert relative uri (precede with hostname if URI starts with "/")
  720.  
  721. // add protocol if one is not found
  722. if (!/^https?/.test(reconstructedUri))
  723. reconstructedUri = `https://${reconstructedUri}`;
  724.  
  725. return reconstructedUri;
  726. }
  727.  
  728. /**
  729. * Returns true if the event target is an input element (such as a textfield).
  730. * This is useful when you want to remap letter keys only when you are not typing in a text field :)
  731. * @param event
  732. * @return {boolean}
  733. */
  734. function targetIsInput(event) {
  735. const ignores = document.getElementsByTagName('input');
  736. const target = event.target;
  737. for (let ignore of ignores)
  738. if (target === ignore || ignore.contains(target)) {
  739. // console.log('The target recieving the keycode is of type "input", so it will not recieve your keystroke', target);
  740. return true;
  741. }
  742. return false;
  743. }
  744.  
  745. /** Calls the callback function, passing to it the width and height: "callback(w, h)"
  746. * @param url
  747. * @param callback callback(width, height, url, imgNode, args)
  748. * @param imgNode
  749. * @param args gets passed to the callback
  750. */
  751. function onLoadDim(url, callback, imgNode, args) {
  752. var img = new Image();
  753. if (!url) {
  754. console.warn('Url is invalid');
  755. return;
  756. }
  757. if (typeof url !== "string") {
  758. url = !!url.src ? url.src : url.href;
  759. }
  760.  
  761. if (typeof callback === 'function') {
  762. img.addEventListener('load', function () {
  763. callback(this.naturalWidth, this.naturalHeight, url, imgNode, args);
  764. });
  765. } else {
  766. console.error('onLoad() callback passed should be of type "function".');
  767. }
  768. img.src = url;
  769. }
  770.  
  771. /**@deprecated Opens the url via xmlhttpRequest, then performs a callback giving it the document element*/
  772. function xmlRequestElement(url, callback) {
  773. if (typeof callback !== 'function') console.error("The callback is not a function", callback);
  774. const req = new XMLHttpRequest();
  775. req.open('GET', url);
  776. req.send();
  777. req.onreadystatechange = function () {
  778. if (req.readyState === req.DONE) {
  779. const pageHTML = req.responseText;
  780. const doc = document.createElement('html');
  781. doc.innerHTML = pageHTML;
  782.  
  783. console.log('Recieved document for page ', url + ":", doc);
  784.  
  785. callback(doc, url);
  786. }
  787. };
  788. }
  789. unsafeWindow.fetchDoc = fetchDoc;
  790. function fetchDoc(url, callback) {
  791. fetch(url, {
  792. mode: 'no-cors',
  793. method: 'get'
  794. }
  795. ).then((res) => res.text())
  796. .then((text) => {
  797. var doc = document.createElement('html');
  798. doc.innerHTML = text;
  799. if (callback && typeof callback === 'function')
  800. callback(doc);
  801. });
  802. }
  803. function testUrls(urls, successUrls) {
  804. successUrls = successUrls || new Set();
  805. for (const url of urls) fetch(url, {
  806. mode: 'no-cors',
  807. method: 'get'
  808. }).then((text) => {
  809. console.log('Sucessfully fetched url:', url);
  810. successUrls.add(url);
  811. }).catch((res) => {
  812. console.error("Failed to fetch url:", url);
  813. });
  814. }
  815. /**Opens the url, then performs a callback giving it the document element
  816. * @param {string} url
  817. * @param {function} callback passes: (doc, url, args) to the callback function when the response is complete
  818. * @param {object} args Options object.
  819. * "args": Arguments to pass to the callback (Array or Object type)
  820. * @returns returns the callback result
  821. */
  822. function fetchElement(url, callback, args) {
  823. if (typeof callback !== 'function') console.error('Callback is not a function.!');
  824. fetch(url).then(
  825. response => response.text() // .json(), etc.
  826. // same as function(response) {return response.text();}
  827. ).then(function (html) {
  828. var doc = document.implementation.createHTMLDocument('');
  829. doc.open();
  830. doc.write(html);
  831. doc.close();
  832. try {
  833. return callback(doc, url, args);
  834. } catch (e) {
  835. console.error(e);
  836. return (html);
  837. }
  838. }
  839. );
  840. }
  841.  
  842. function isDdgUrl(url) {
  843. return /^https:\/\/proxy\.duckduckgo\.com/.test(url);
  844. }
  845.  
  846. function unicodeToChar(text) {
  847. return text.replace(/\\u[\dA-F]{4}/gim,
  848. match => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)));
  849. }
  850.  
  851.  
  852. // don't make public because it conflicts with DDGP Unblocker script
  853.  
  854. /** A class containing static functions to manipulate the srcset attribute */
  855. unsafeWindow.SrcSet = class SrcSet {
  856. /**
  857. * @author https://github.com/sindresorhus/srcset/
  858. * @param arr
  859. * @return {*}
  860. */
  861. static deepUnique(arr) {
  862. return arr.sort().filter(function (el, i) {
  863. return JSON.stringify(el) !== JSON.stringify(arr[i - 1]);
  864. });
  865. }
  866. /**
  867. * @author https://github.com/sindresorhus/srcset/
  868. * @param str
  869. * @return {*}
  870. */
  871. static parse(str) {
  872. return this.deepUnique(str.split(',').map(function (el) {
  873. var ret = {};
  874.  
  875. el.trim().split(/\s+/).forEach(function (el, i) {
  876. if (i === 0) {
  877. return ret.url = el;
  878. }
  879.  
  880. var value = el.substring(0, el.length - 1);
  881. var postfix = el[el.length - 1];
  882. var intVal = parseInt(value, 10);
  883. var floatVal = parseFloat(value);
  884.  
  885. if (postfix === 'w' && /^[\d]+$/.test(value)) {
  886. ret.width = intVal;
  887. } else if (postfix === 'h' && /^[\d]+$/.test(value)) {
  888. ret.height = intVal;
  889. } else if (postfix === 'x' && !isNaN(floatVal)) {
  890. ret.density = floatVal;
  891. } else {
  892. throw new Error('Invalid srcset descriptor: ' + el + '.');
  893. }
  894. });
  895.  
  896. return ret;
  897. }));
  898. }
  899. static stringify(arr) {
  900. return (arr.map(function (el) {
  901. if (!el.url) {
  902. throw new Error('URL is required.');
  903. }
  904.  
  905. var ret = [el.url];
  906.  
  907. if (el.width) {
  908. ret.push(el.width + 'w');
  909. }
  910.  
  911. if (el.height) {
  912. ret.push(el.height + 'h');
  913. }
  914.  
  915. if (el.density) {
  916. ret.push(el.density + 'x');
  917. }
  918.  
  919. return ret.join(' ');
  920. })).join(', ');
  921. }
  922. };
  923.  
  924. unsafeWindow.cookieUtils = {
  925. setCookie: function setCookie(name, value, days) {
  926. var expires = "";
  927. if (days) {
  928. var date = new Date();
  929. date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
  930. expires = "; expires=" + date.toUTCString();
  931. }
  932. document.cookie = name + "=" + (value || "") + expires + "; path=/";
  933. return document.cookie;
  934. },
  935. getCookie: function getCookie(name) {
  936. var nameEQ = name + "=";
  937. var ca = document.cookie.split(';');
  938. for (var i = 0; i < ca.length; i++) {
  939. var c = ca[i];
  940. while (c.charAt(0) == ' ') c = c.substring(1, c.length);
  941. if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
  942. }
  943. return null;
  944. },
  945. eraseCookie: function eraseCookie(name) {
  946. document.cookie = name + '=; Max-Age=-99999999;';
  947. return document.cookie;
  948. }
  949. };
  950.  
  951. unsafeWindow.url2location = url2location;
  952. /**
  953. * Retrieves an object with parsed URL data
  954. * @param url
  955. * @return {
  956. * {port: string,
  957. * protocol: string,
  958. * href: string,
  959. * origin: string,
  960. * pathname: string,
  961. * hash: string,
  962. * search: string}
  963. * }
  964. */
  965. function url2location(url) {
  966. const a = document.createElement("a");
  967. a.href = url;
  968. return {
  969. port: a.port,
  970. protocol: a.protocol,
  971. href: a.href,
  972. origin: a.origin,
  973. pathname: a.pathname,
  974. hash: a.hash,
  975. search: a.search
  976. }
  977. }
  978. /** @param href
  979. * @param keepPrefix defaults to false
  980. * www.example.com
  981. * if true: example.com
  982. * if false: www.example.com
  983. * @returns {string} Returns the hostname of the site URL */
  984. function getHostname(href, keepPrefix) {
  985. const a = document.createElement("a");
  986. a.href = href;
  987. // if (keepPrefix) console.debug("getHostname href =", href);
  988. return a.hostname;
  989. }
  990. /** Inclomplete
  991. * @type {boolean} */
  992. unsafeWindow.freezeGif = freezeGif;
  993. function freezeGif(img, unfreeze) {
  994. function createElementAndCallback(type, callback) {
  995. const element = document.createElement(type);
  996. callback(element);
  997. return element;
  998. }
  999. var width = img.width,
  1000. height = img.height,
  1001. canvas = createElementAndCallback('canvas', function (clone) {
  1002. clone.width = width;
  1003. clone.height = height;
  1004. }),
  1005. attr,
  1006. i = 0;
  1007.  
  1008. var freeze = function () {
  1009. canvas.getContext('2d').drawImage(img, 0, 0, width, height);
  1010.  
  1011. for (i = 0; i < img.attributes.length; i++) {
  1012. attr = img.attributes[i];
  1013.  
  1014. if (attr.name !== '"') { // test for invalid attributes
  1015. canvas.setAttribute(attr.name, attr.value);
  1016. }
  1017. }
  1018. canvas.classList.add('freeze-gif');
  1019. canvas.style.position = 'absolute';
  1020.  
  1021. img.parentNode.insertBefore(canvas, img);
  1022. img.style.visibility = 'hidden';
  1023. // img.style.opacity = 0;
  1024. };
  1025.  
  1026. var unfreezeGif = function () {
  1027. console.log('unfreezing', img);
  1028. const freezeCanvas = img.closest('.freeze-gif');
  1029.  
  1030. if (!freezeCanvas) {
  1031. console.error('Couldn\'t find freezeCanvas while unfreezing this gif:', img);
  1032. } else {
  1033. freezeCanvas.style.visibility = 'hidden';
  1034. }
  1035. // img.style.opacity = 100;
  1036. img.style.visibility = 'visible';
  1037. };
  1038.  
  1039. if (unfreeze) {
  1040. unfreezeGif();
  1041. } else {
  1042. if (img.complete) {
  1043. freeze();
  1044. } else {
  1045. img.addEventListener('load', freeze, true);
  1046. }
  1047. }
  1048. }
  1049. function getIframeDoc(iframe) {
  1050. return iframe.contentDocument || iframe.contentWindow ? iframe.contentWindow.document : null;
  1051. }
  1052.  
  1053. unsafeWindow.removeClickListeners = removeClickListeners;
  1054. function removeClickListeners(selector) {
  1055. if (!!unsafeWindow.$) {
  1056. unsafeWindow.$(!selector ? "*" : selector)
  1057. .unbind("click")
  1058. .off("click")
  1059. .removeAttr("onclick");
  1060. } else {
  1061. console.warn('unsafeWindow.$ is not defined');
  1062. }
  1063. }
  1064. /**
  1065. * @WIP TODO: Complete this function
  1066. */
  1067. function removeEventListeners(eventTarget) {
  1068. var eventType = "click";
  1069. eventTarget = window;
  1070.  
  1071. for (const eventType in window.getEventListeners(eventTarget)) {
  1072. const eventListeners = getEventListeners(eventTarget);
  1073. if (eventListeners.hasOwnProperty(eventType))
  1074. for (const o of eventListeners[eventType]) {
  1075. console.log('before:', o);
  1076. o.listener = null;
  1077. console.log('after:', o);
  1078. }
  1079. }
  1080.  
  1081.  
  1082. // noinspection JSUnresolvedFunction
  1083. let listeners = eventTarget.getEventListeners(eventTarget);
  1084. listeners.forEach(function (listener) {
  1085. console.log('removing listener:', listener);
  1086. eventTarget.removeEventListener("click", listener, false);
  1087. });
  1088. }
  1089.  
  1090. unsafeWindow.removeDoubleSpaces = removeDoubleSpaces;
  1091. unsafeWindow.cleanGibberish = cleanGibberish;
  1092. unsafeWindow.isBase64ImageData = isBase64ImageData;
  1093. unsafeWindow.cleanDates = cleanDates;
  1094.  
  1095. unsafeWindow.downloadScripts = function downloadScripts() {
  1096. var scriptUrls = Array.from(document.querySelectorAll('script'))
  1097. .map(script => script.src ? script.src : window.URL.createObjectURL(new Blob([script.innerHTML], {type: 'text/plain'}))
  1098. );
  1099. zipFiles(scriptUrls);
  1100. };
  1101.  
  1102. unsafeWindow.escapeEncodedChars = escapeEncodedChars;
  1103. /** Escapes Unicode chars, and anything starting with \x, \u,
  1104. * @param text
  1105. * @return {*} */
  1106. function escapeEncodedChars(text) {
  1107. return text
  1108. .replace(/\\[u][\dA-F]{4}/gim, match => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)))
  1109. .replace(/\\[x][\dA-F]{2}/gim, match => String.fromCharCode(parseInt(match.replace(/\\x/g, ''), 16)));
  1110. }
  1111. /**
  1112. * Returns true if the string is an image data string
  1113. * @param str
  1114. * @returns {boolean}
  1115. */
  1116. function isBase64ImageData(str) {
  1117. return /^data:image\/.{1,5};base64/.test(str);
  1118. }
  1119. function removeDoubleSpaces(str) {
  1120. return !!str ? str.replace(/(\s{2,})/g, " ") : str;
  1121. }
  1122.  
  1123. function cleanDates(str) {
  1124. return !!str ? removeDoubleSpaces(str.replace(/\d*\.([^.]+)\.\d*/g, ' ')) : str;
  1125. }
  1126. function cleanGibberish(str, minWgr, debug = false) {
  1127. if (str) {
  1128. const gibberishRegex = /(\W{2,})|(\d{3,})|(\d+\w{1,5}\d+){2,}/g;
  1129. let noGibberish = removeDoubleSpaces(str.replace(gibberishRegex, " ")),
  1130. /**
  1131. * The minimum word2gibberish ratio to exit the loop
  1132. * @type {number|*}
  1133. */
  1134. minWgr = 0.4 || minWgr;
  1135. if (noGibberish.length < 3) return str;
  1136. /**
  1137. * WGR: Word to Gibberish Ratio (between 0 and 1)
  1138. * 0: No gibberish (Good)
  1139. * 1: 100% Gibberish (Bad)
  1140. * @type {number}
  1141. */
  1142. let wgr = (str.length - noGibberish.length) / str.length;
  1143. if (debug) console.debug(
  1144. 'cleanGibberish(' + str + ')' +
  1145. '\nOriginal:', str,
  1146. '\nNoGibberish:', noGibberish,
  1147. '\nRatio:', wgr
  1148. );
  1149.  
  1150. return wgr > minWgr ?
  1151. cleanGibberish(noGibberish, minWgr) :
  1152. (str.length > 3 ? str : "");
  1153. }
  1154. return "";
  1155. }
  1156. var getCssImage = (element) => !element ? null : element.style["background-image"].replace(/(['"]?\)$)|(^url\(["']?)/g, '');
  1157. unsafeWindow.getCssImages = () => Array.from(document.querySelectorAll('[style*="background-image"]')).map(getCssImage);
  1158.  
  1159. unsafeWindow.observeDocument = function observeDocument(callback, options) {
  1160. callback(document.body);
  1161. options = typeof(extend) !== 'function' ? {} : extend(options, {
  1162. singleCallbackPerMutation: false
  1163. });
  1164. new MutationObserver(
  1165. /** @param mutations */
  1166. function mutationCallback(mutations) {
  1167. for (const mutation of mutations) {
  1168. if (!mutation.addedNodes.length)
  1169. continue;
  1170. callback(mutation.target);
  1171. if (options.singleCallbackPerMutation === true) {
  1172. break;
  1173. }
  1174. }
  1175. }
  1176. ).observe(document.body, {
  1177. childList: true,
  1178. subtree: true,
  1179. attributes: true,
  1180. characterData: false
  1181. }
  1182. );
  1183. };
  1184. unsafeWindow.observeIframe = function observeIframe(iframe, observerInit, observerOptions, args) {
  1185. // older browsers don't get responsive iframe height, for now
  1186. if (!window.MutationObserver) return;
  1187. console.debug('Attaching an iframe observer...', iframe, '\n\n');
  1188. var iframeObserver = new MutationObserver(function (mutations, observer) {
  1189. console.debug(
  1190. 'Observed mutation in iframe:', iframe,
  1191. '\nmutations:', mutations
  1192. );
  1193. observerInit(mutations, observer, args);
  1194. });
  1195.  
  1196. var interval = setInterval(function () {
  1197. if (iframe.contentWindow && iframe.contentWindow.document) {
  1198. iframeObserver.observe(iframe.contentWindow.document, observerOptions || {
  1199. attributes: true,
  1200. subtree: true,
  1201. childList: true,
  1202. characterData: true
  1203. });
  1204. console.log('Successfully added observer to iframe!', iframe);
  1205.  
  1206. clearInterval(interval);
  1207. }
  1208. }, 100);
  1209. };
  1210.  
  1211. unsafeWindow.observeAllFrames = function observeAllFrames(callback) {
  1212. callback(document.body);
  1213. callback(document);
  1214. let mutationObserver = new MutationObserver(function (mutations) {
  1215. for (const mutation of mutations) {
  1216. if (mutation.addedNodes.length) {
  1217. callback(mutation.target);
  1218. }
  1219. }
  1220. });
  1221. const mutationOptions = {
  1222. childList: true, subtree: true,
  1223. attributes: true, characterData: true
  1224. };
  1225. mutationObserver.observe(document, mutationOptions);
  1226. for (const iframe of document.querySelectorAll('iframe')) {
  1227. callback(iframe.body);
  1228. mutationObserver.observe(iframe, mutationOptions);
  1229. }
  1230. };
  1231.  
  1232. /**
  1233. * @param {function} condition
  1234. * @param {function} action
  1235. * @param {number} interval
  1236. */
  1237. function waitFor(condition, action, interval) {
  1238. if (typeof condition === 'undefined') {
  1239. console.error('"condition" should be a function type:', condition);
  1240. return false;
  1241. }
  1242. if (typeof action === 'undefined') {
  1243. console.error('"condition" should be a function type:', action);
  1244. return false;
  1245. }
  1246. if (!interval) interval = 50;
  1247.  
  1248. var checkExist = setInterval(function () {
  1249. if (condition) {
  1250. console.log("Exists!");
  1251. clearInterval(checkExist);
  1252. action();
  1253. }
  1254. }, interval);
  1255. }
  1256.  
  1257. function downloadUsingXmlhttpRequest(url, opts) {
  1258. var imgUrl = url || "http://static.jsbin.com/images/dave.min.svg?4.1.4";
  1259. GM_xmlhttpRequest({
  1260. method: 'GET',
  1261. url: imgUrl,
  1262. onload: function (respDetails) {
  1263. var binResp = customBase64Encode(respDetails.responseText);
  1264.  
  1265. /*-- Here, we just demo that we have a valid base64 encoding
  1266. by inserting the image into the page.
  1267. We could just as easily AJAX-off the data instead.
  1268. */
  1269. var zImgPara = document.createElement('p');
  1270. var zTargetNode = document.querySelector("body *"); //1st child
  1271.  
  1272. zImgPara.innerHTML = 'Image: <img src="data:image/png;base64,'
  1273. + binResp + '">';
  1274. zTargetNode.parentNode.insertBefore(zImgPara, zTargetNode);
  1275. },
  1276. overrideMimeType: 'text/plain; charset=x-user-defined'
  1277. });
  1278.  
  1279.  
  1280. function customBase64Encode(inputStr) {
  1281. var
  1282. bbLen = 3,
  1283. enCharLen = 4,
  1284. inpLen = inputStr.length,
  1285. inx = 0,
  1286. jnx,
  1287. keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  1288. + "0123456789+/=",
  1289. output = "",
  1290. paddingBytes = 0;
  1291. var
  1292. bytebuffer = new Array(bbLen),
  1293. encodedCharIndexes = new Array(enCharLen);
  1294.  
  1295. while (inx < inpLen) {
  1296. for (jnx = 0; jnx < bbLen; ++jnx) {
  1297. /*--- Throw away high-order byte, as documented at:
  1298. https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
  1299. */
  1300. if (inx < inpLen) {
  1301. bytebuffer[jnx] = inputStr.charCodeAt(inx++) & 0xff;
  1302. } else {
  1303. bytebuffer[jnx] = 0;
  1304. }
  1305. }
  1306.  
  1307. /*--- Get each encoded character, 6 bits at a time.
  1308. index 0: first 6 bits
  1309. index 1: second 6 bits
  1310. (2 least significant bits from inputStr byte 1
  1311. + 4 most significant bits from byte 2)
  1312. index 2: third 6 bits
  1313. (4 least significant bits from inputStr byte 2
  1314. + 2 most significant bits from byte 3)
  1315. index 3: forth 6 bits (6 least significant bits from inputStr byte 3)
  1316. */
  1317. encodedCharIndexes[0] = bytebuffer[0] >> 2;
  1318. encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4);
  1319. encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6);
  1320. encodedCharIndexes[3] = bytebuffer[2] & 0x3f;
  1321.  
  1322. //--- Determine whether padding happened, and adjust accordingly.
  1323. paddingBytes = inx - (inpLen - 1);
  1324. switch (paddingBytes) {
  1325. case 1:
  1326. // Set last character to padding char
  1327. encodedCharIndexes[3] = 64;
  1328. break;
  1329. case 2:
  1330. // Set last 2 characters to padding char
  1331. encodedCharIndexes[3] = 64;
  1332. encodedCharIndexes[2] = 64;
  1333. break;
  1334. default:
  1335. break; // No padding - proceed
  1336. }
  1337.  
  1338. /*--- Now grab each appropriate character out of our keystring,
  1339. based on our index array and append it to the output string.
  1340. */
  1341. for (jnx = 0; jnx < enCharLen; ++jnx)
  1342. output += keyStr.charAt(encodedCharIndexes[jnx]);
  1343. }
  1344. return output;
  1345. }
  1346. }
  1347.  
  1348. unsafeWindow.iterateOverURLPattern = function iterateOverURLPattern(inputURL) {
  1349. inputURL = inputURL || '';
  1350.  
  1351. var bracketsContent = inputURL.match(/\[.+]/);
  1352. if (bracketsContent.length)
  1353. var [start, end] = bracketsContent[0].replace(/[\[\]]/g, '').split('-');
  1354.  
  1355. console.debug(
  1356. 'text in brackets:', bracketsContent,
  1357. '\nstart, end:', `["${start}", "${end}"]`
  1358. );
  1359.  
  1360. let urls = [];
  1361. for (var i = start; i <= end; i++) {
  1362. var newNum = "" + i;
  1363. while (newNum.length < start.length) newNum = "0" + newNum;
  1364. urls.push(inputURL.replace(/\[.+]/, newNum));
  1365. }
  1366. return urls;
  1367. };
  1368.  
  1369. /*
  1370. CSS for top navbars:
  1371.  
  1372. .fixed-position {
  1373. position: fixed;
  1374. top: 0px;
  1375. z-index: 16777271;
  1376. }
  1377.  
  1378. */
  1379.  
  1380.  
  1381. /*global self */
  1382.  
  1383. /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
  1384. /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */
  1385. /* FileSaver.js
  1386. * A saveAs() FileSaver implementation.
  1387. * 1.3.8
  1388. * 2018-03-22 14:03:47
  1389. *
  1390. * By Eli Grey, https://eligrey.com
  1391. * License: MIT
  1392. * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
  1393. *
  1394. */
  1395. // noinspection ThisExpressionReferencesGlobalObjectJS
  1396. var saveAs = saveAs || (function (view) {
  1397. "use strict";
  1398. // IE <10 is explicitly unsupported
  1399. if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
  1400. return;
  1401. }
  1402. var
  1403. doc = view.document
  1404. // only get URL when necessary in case Blob.js hasn't overridden it yet
  1405. ,
  1406. get_URL = function () {
  1407. return view.URL || view.webkitURL || view;
  1408. },
  1409. save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"),
  1410. can_use_save_link = "download" in save_link,
  1411. click = function (node) {
  1412. var event = new MouseEvent("click");
  1413. node.dispatchEvent(event);
  1414. },
  1415. is_safari = /constructor/i.test(view.HTMLElement) || view.safari,
  1416. is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent),
  1417. setImmediate = view.setImmediate || view.setTimeout,
  1418. throw_outside = function (ex) {
  1419. setImmediate(function () {
  1420. throw ex;
  1421. }, 0);
  1422. },
  1423. force_saveable_type = "application/octet-stream"
  1424. // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
  1425. ,
  1426. arbitrary_revoke_timeout = 1000 * 40 // in ms
  1427. ,
  1428. revoke = function (file) {
  1429. var revoker = function () {
  1430. if (typeof file === "string") { // file is an object URL
  1431. get_URL().revokeObjectURL(file);
  1432. } else { // file is a File
  1433. file.remove();
  1434. }
  1435. };
  1436. setTimeout(revoker, arbitrary_revoke_timeout);
  1437. },
  1438. dispatch = function (filesaver, event_types, event) {
  1439. event_types = [].concat(event_types);
  1440. var i = event_types.length;
  1441. while (i--) {
  1442. var listener = filesaver["on" + event_types[i]];
  1443. if (typeof listener === "function") {
  1444. try {
  1445. listener.call(filesaver, event || filesaver);
  1446. } catch (ex) {
  1447. throw_outside(ex);
  1448. }
  1449. }
  1450. }
  1451. },
  1452. auto_bom = function (blob) {
  1453. // prepend BOM for UTF-8 XML and text/* types (including HTML)
  1454. // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
  1455. if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
  1456. return new Blob([String.fromCharCode(0xFEFF), blob], {
  1457. type: blob.type
  1458. });
  1459. }
  1460. return blob;
  1461. },
  1462. FileSaver = function (blob, name, no_auto_bom) {
  1463. if (!no_auto_bom) {
  1464. blob = auto_bom(blob);
  1465. }
  1466. // First try a.download, then web filesystem, then object URLs
  1467. var
  1468. filesaver = this,
  1469. type = blob.type,
  1470. force = type === force_saveable_type,
  1471. object_url, dispatch_all = function () {
  1472. dispatch(filesaver, "writestart progress write writeend".split(" "));
  1473. }
  1474. // on any filesys errors revert to saving with object URLs
  1475. ,
  1476. fs_error = function () {
  1477. if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
  1478. // Safari doesn't allow downloading of blob urls
  1479. var reader = new FileReader();
  1480. reader.onloadend = function () {
  1481. var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
  1482. var popup = view.open(url, '_blank');
  1483. if (!popup) view.location.href = url;
  1484. url = undefined; // release reference before dispatching
  1485. filesaver.readyState = filesaver.DONE;
  1486. dispatch_all();
  1487. };
  1488. reader.readAsDataURL(blob);
  1489. filesaver.readyState = filesaver.INIT;
  1490. return;
  1491. }
  1492. // don't create more object URLs than needed
  1493. if (!object_url) {
  1494. object_url = get_URL().createObjectURL(blob);
  1495. }
  1496. if (force) {
  1497. view.location.href = object_url;
  1498. } else {
  1499. var opened = view.open(object_url, "_blank");
  1500. if (!opened) {
  1501. // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
  1502. view.location.href = object_url;
  1503. }
  1504. }
  1505. filesaver.readyState = filesaver.DONE;
  1506. dispatch_all();
  1507. revoke(object_url);
  1508. };
  1509. filesaver.readyState = filesaver.INIT;
  1510.  
  1511. if (can_use_save_link) {
  1512. object_url = get_URL().createObjectURL(blob);
  1513. setImmediate(function () {
  1514. save_link.href = object_url;
  1515. save_link.download = name;
  1516. click(save_link);
  1517. dispatch_all();
  1518. revoke(object_url);
  1519. filesaver.readyState = filesaver.DONE;
  1520. }, 0);
  1521. return;
  1522. }
  1523.  
  1524. fs_error();
  1525. },
  1526. FS_proto = FileSaver.prototype,
  1527. saveAs = function (blob, name, no_auto_bom) {
  1528. return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
  1529. };
  1530.  
  1531. // IE 10+ (native saveAs)
  1532. if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
  1533. return function (blob, name, no_auto_bom) {
  1534. name = name || blob.name || "download";
  1535.  
  1536. if (!no_auto_bom) {
  1537. blob = auto_bom(blob);
  1538. }
  1539. return navigator.msSaveOrOpenBlob(blob, name);
  1540. };
  1541. }
  1542.  
  1543. // todo: detect chrome extensions & packaged apps
  1544. //save_link.target = "_blank";
  1545.  
  1546. FS_proto.abort = function () {
  1547. };
  1548. FS_proto.readyState = FS_proto.INIT = 0;
  1549. FS_proto.WRITING = 1;
  1550. FS_proto.DONE = 2;
  1551.  
  1552. FS_proto.error =
  1553. FS_proto.onwritestart =
  1554. FS_proto.onprogress =
  1555. FS_proto.onwrite =
  1556. FS_proto.onabort =
  1557. FS_proto.onerror =
  1558. FS_proto.onwriteend =
  1559. null;
  1560.  
  1561. return saveAs;
  1562. }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this));
  1563. /*`self` is undefined in Firefox for Android content script context
  1564. while `this` is nsIContentFrameMessageManager
  1565. with an attribute `content` that corresponds to the window*/
  1566. if (typeof module !== "undefined" && module.exports) {
  1567. module.exports.saveAs = saveAs;
  1568. } else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
  1569. define([], function () {
  1570. return saveAs;
  1571. });
  1572. }
  1573. /* Example:
  1574. * var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
  1575. * saveAs(blob, "hello world.txt");
  1576. */
  1577. unsafeWindow.saveAs = saveAs;
  1578.  
  1579.  
  1580. /* mousetrap v1.6.2 craig.is/killing/mice */
  1581. /*global define:false */
  1582. /**
  1583. * Copyright 2012-2017 Craig Campbell
  1584. *
  1585. * Licensed under the Apache License, Version 2.0 (the "License");
  1586. * you may not use this file except in compliance with the License.
  1587. * You may obtain a copy of the License at
  1588. *
  1589. * http://www.apache.org/licenses/LICENSE-2.0
  1590. *
  1591. * Unless required by applicable law or agreed to in writing, software
  1592. * distributed under the License is distributed on an "AS IS" BASIS,
  1593. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1594. * See the License for the specific language governing permissions and
  1595. * limitations under the License.
  1596. *
  1597. * Mousetrap is a simple keyboard shortcut library for Javascript with
  1598. * no external dependencies
  1599. *
  1600. * @version 1.6.2
  1601. * @url craig.is/killing/mice
  1602. */
  1603. (function (window, document, undefined) {
  1604.  
  1605. // Check if mousetrap is used inside browser, if not, return
  1606. if (!window) {
  1607. return;
  1608. }
  1609.  
  1610. /**
  1611. * mapping of special keycodes to their corresponding keys
  1612. *
  1613. * everything in this dictionary cannot use keypress events
  1614. * so it has to be here to map to the correct keycodes for
  1615. * keyup/keydown events
  1616. *
  1617. * @type {Object}
  1618. */
  1619. var _MAP = {
  1620. 8: 'backspace',
  1621. 9: 'tab',
  1622. 13: 'enter',
  1623. 16: 'shift',
  1624. 17: 'ctrl',
  1625. 18: 'alt',
  1626. 20: 'capslock',
  1627. 27: 'esc',
  1628. 32: 'space',
  1629. 33: 'pageup',
  1630. 34: 'pagedown',
  1631. 35: 'end',
  1632. 36: 'home',
  1633. 37: 'left',
  1634. 38: 'up',
  1635. 39: 'right',
  1636. 40: 'down',
  1637. 45: 'ins',
  1638. 46: 'del',
  1639. 91: 'meta',
  1640. 93: 'meta',
  1641. 224: 'meta'
  1642. };
  1643.  
  1644. /**
  1645. * mapping for special characters so they can support
  1646. *
  1647. * this dictionary is only used incase you want to bind a
  1648. * keyup or keydown event to one of these keys
  1649. *
  1650. * @type {Object}
  1651. */
  1652. var _KEYCODE_MAP = {
  1653. 106: '*',
  1654. 107: '+',
  1655. 109: '-',
  1656. 110: '.',
  1657. 111: '/',
  1658. 186: ';',
  1659. 187: '=',
  1660. 188: ',',
  1661. 189: '-',
  1662. 190: '.',
  1663. 191: '/',
  1664. 192: '`',
  1665. 219: '[',
  1666. 220: '\\',
  1667. 221: ']',
  1668. 222: '\''
  1669. };
  1670.  
  1671. /**
  1672. * this is a mapping of keys that require shift on a US keypad
  1673. * back to the non shift equivelents
  1674. *
  1675. * this is so you can use keyup events with these keys
  1676. *
  1677. * note that this will only work reliably on US keyboards
  1678. *
  1679. * @type {Object}
  1680. */
  1681. var _SHIFT_MAP = {
  1682. '~': '`',
  1683. '!': '1',
  1684. '@': '2',
  1685. '#': '3',
  1686. '$': '4',
  1687. '%': '5',
  1688. '^': '6',
  1689. '&': '7',
  1690. '*': '8',
  1691. '(': '9',
  1692. ')': '0',
  1693. '_': '-',
  1694. '+': '=',
  1695. ':': ';',
  1696. '\"': '\'',
  1697. '<': ',',
  1698. '>': '.',
  1699. '?': '/',
  1700. '|': '\\'
  1701. };
  1702.  
  1703. /**
  1704. * this is a list of special strings you can use to map
  1705. * to modifier keys when you specify your keyboard shortcuts
  1706. *
  1707. * @type {Object}
  1708. */
  1709. var _SPECIAL_ALIASES = {
  1710. 'option': 'alt',
  1711. 'command': 'meta',
  1712. 'return': 'enter',
  1713. 'escape': 'esc',
  1714. 'plus': '+',
  1715. 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
  1716. };
  1717.  
  1718. /**
  1719. * variable to store the flipped version of _MAP from above
  1720. * needed to check if we should use keypress or not when no action
  1721. * is specified
  1722. *
  1723. * @type {Object|undefined}
  1724. */
  1725. var _REVERSE_MAP;
  1726.  
  1727. /**
  1728. * loop through the f keys, f1 to f19 and add them to the map
  1729. * programatically
  1730. */
  1731. for (var i = 1; i < 20; ++i) {
  1732. _MAP[111 + i] = 'f' + i;
  1733. }
  1734.  
  1735. /**
  1736. * loop through to map numbers on the numeric keypad
  1737. */
  1738. for (i = 0; i <= 9; ++i) {
  1739.  
  1740. // This needs to use a string cause otherwise since 0 is falsey
  1741. // mousetrap will never fire for numpad 0 pressed as part of a keydown
  1742. // event.
  1743. //
  1744. // @see https://github.com/ccampbell/mousetrap/pull/258
  1745. _MAP[i + 96] = i.toString();
  1746. }
  1747.  
  1748. /**
  1749. * cross browser add event method
  1750. *
  1751. * @param {Element|HTMLDocument} object
  1752. * @param {string} type
  1753. * @param {Function} callback
  1754. * @returns void
  1755. */
  1756. function _addEvent(object, type, callback) {
  1757. if (object.addEventListener) {
  1758. object.addEventListener(type, callback, false);
  1759. return;
  1760. }
  1761.  
  1762. try {
  1763. object.attachEvent('on' + type, callback);
  1764. } catch (e) {
  1765. console.error(e);
  1766. }
  1767. }
  1768.  
  1769. /**
  1770. * takes the event and returns the key character
  1771. *
  1772. * @param {Event} e
  1773. * @return {string}
  1774. */
  1775. function _characterFromEvent(e) {
  1776.  
  1777. // for keypress events we should return the character as is
  1778. if (e.type == 'keypress') {
  1779. var character = String.fromCharCode(e.which);
  1780.  
  1781. // if the shift key is not pressed then it is safe to assume
  1782. // that we want the character to be lowercase. this means if
  1783. // you accidentally have caps lock on then your key bindings
  1784. // will continue to work
  1785. //
  1786. // the only side effect that might not be desired is if you
  1787. // bind something like 'A' cause you want to trigger an
  1788. // event when capital A is pressed caps lock will no longer
  1789. // trigger the event. shift+a will though.
  1790. if (!e.shiftKey) {
  1791. character = character.toLowerCase();
  1792. }
  1793.  
  1794. return character;
  1795. }
  1796.  
  1797. // for non keypress events the special maps are needed
  1798. if (_MAP[e.which]) {
  1799. return _MAP[e.which];
  1800. }
  1801.  
  1802. if (_KEYCODE_MAP[e.which]) {
  1803. return _KEYCODE_MAP[e.which];
  1804. }
  1805.  
  1806. // if it is not in the special map
  1807.  
  1808. // with keydown and keyup events the character seems to always
  1809. // come in as an uppercase character whether you are pressing shift
  1810. // or not. we should make sure it is always lowercase for comparisons
  1811. return String.fromCharCode(e.which).toLowerCase();
  1812. }
  1813.  
  1814. /**
  1815. * checks if two arrays are equal
  1816. *
  1817. * @param {Array} modifiers1
  1818. * @param {Array} modifiers2
  1819. * @returns {boolean}
  1820. */
  1821. function _modifiersMatch(modifiers1, modifiers2) {
  1822. return modifiers1.sort().join(',') === modifiers2.sort().join(',');
  1823. }
  1824.  
  1825. /**
  1826. * takes a key event and figures out what the modifiers are
  1827. *
  1828. * @param {Event} e
  1829. * @returns {Array}
  1830. */
  1831. function _eventModifiers(e) {
  1832. var modifiers = [];
  1833.  
  1834. if (e.shiftKey) {
  1835. modifiers.push('shift');
  1836. }
  1837.  
  1838. if (e.altKey) {
  1839. modifiers.push('alt');
  1840. }
  1841.  
  1842. if (e.ctrlKey) {
  1843. modifiers.push('ctrl');
  1844. }
  1845.  
  1846. if (e.metaKey) {
  1847. modifiers.push('meta');
  1848. }
  1849.  
  1850. return modifiers;
  1851. }
  1852.  
  1853. /**
  1854. * prevents default for this event
  1855. *
  1856. * @param {Event} e
  1857. * @returns void
  1858. */
  1859. function _preventDefault(e) {
  1860. if (e.preventDefault) {
  1861. e.preventDefault();
  1862. return;
  1863. }
  1864.  
  1865. e.returnValue = false;
  1866. }
  1867.  
  1868. /**
  1869. * stops propogation for this event
  1870. *
  1871. * @param {Event} e
  1872. * @returns void
  1873. */
  1874. function _stopPropagation(e) {
  1875. if (e.stopPropagation) {
  1876. e.stopPropagation();
  1877. return;
  1878. }
  1879.  
  1880. e.cancelBubble = true;
  1881. }
  1882.  
  1883. /**
  1884. * determines if the keycode specified is a modifier key or not
  1885. *
  1886. * @param {string} key
  1887. * @returns {boolean}
  1888. */
  1889. function _isModifier(key) {
  1890. return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
  1891. }
  1892.  
  1893. /**
  1894. * reverses the map lookup so that we can look for specific keys
  1895. * to see what can and can't use keypress
  1896. *
  1897. * @return {Object}
  1898. */
  1899. function _getReverseMap() {
  1900. if (!_REVERSE_MAP) {
  1901. _REVERSE_MAP = {};
  1902. for (var key in _MAP) {
  1903.  
  1904. // pull out the numeric keypad from here cause keypress should
  1905. // be able to detect the keys from the character
  1906. if (key > 95 && key < 112) {
  1907. continue;
  1908. }
  1909.  
  1910. if (_MAP.hasOwnProperty(key)) {
  1911. _REVERSE_MAP[_MAP[key]] = key;
  1912. }
  1913. }
  1914. }
  1915. return _REVERSE_MAP;
  1916. }
  1917.  
  1918. /**
  1919. * picks the best action based on the key combination
  1920. *
  1921. * @param {string} key - character for key
  1922. * @param {Array} modifiers
  1923. * @param {string=} action passed in
  1924. */
  1925. function _pickBestAction(key, modifiers, action) {
  1926.  
  1927. // if no action was picked in we should try to pick the one
  1928. // that we think would work best for this key
  1929. if (!action) {
  1930. action = _getReverseMap()[key] ? 'keydown' : 'keypress';
  1931. }
  1932.  
  1933. // modifier keys don't work as expected with keypress,
  1934. // switch to keydown
  1935. if (action == 'keypress' && modifiers.length) {
  1936. action = 'keydown';
  1937. }
  1938.  
  1939. return action;
  1940. }
  1941.  
  1942. /**
  1943. * Converts from a string key combination to an array
  1944. *
  1945. * @param {string} combination like "command+shift+l"
  1946. * @return {Array}
  1947. */
  1948. function _keysFromString(combination) {
  1949. if (combination === '+') {
  1950. return ['+'];
  1951. }
  1952.  
  1953. combination = combination.replace(/\+{2}/g, '+plus');
  1954. return combination.split('+');
  1955. }
  1956.  
  1957. /**
  1958. * Gets info for a specific key combination
  1959. *
  1960. * @param {string} combination key combination ("command+s" or "a" or "*")
  1961. * @param {string=} action
  1962. * @returns {Object}
  1963. */
  1964. function _getKeyInfo(combination, action) {
  1965. var keys;
  1966. var key;
  1967. var i;
  1968. var modifiers = [];
  1969.  
  1970. // take the keys from this pattern and figure out what the actual
  1971. // pattern is all about
  1972. keys = _keysFromString(combination);
  1973.  
  1974. for (i = 0; i < keys.length; ++i) {
  1975. key = keys[i];
  1976.  
  1977. // normalize key names
  1978. if (_SPECIAL_ALIASES[key]) {
  1979. key = _SPECIAL_ALIASES[key];
  1980. }
  1981.  
  1982. // if this is not a keypress event then we should
  1983. // be smart about using shift keys
  1984. // this will only work for US keyboards however
  1985. if (action && action != 'keypress' && _SHIFT_MAP[key]) {
  1986. key = _SHIFT_MAP[key];
  1987. modifiers.push('shift');
  1988. }
  1989.  
  1990. // if this key is a modifier then add it to the list of modifiers
  1991. if (_isModifier(key)) {
  1992. modifiers.push(key);
  1993. }
  1994. }
  1995.  
  1996. // depending on what the key combination is
  1997. // we will try to pick the best event for it
  1998. action = _pickBestAction(key, modifiers, action);
  1999.  
  2000. return {
  2001. key: key,
  2002. modifiers: modifiers,
  2003. action: action
  2004. };
  2005. }
  2006.  
  2007. function _belongsTo(element, ancestor) {
  2008. if (element === null || element === document) {
  2009. return false;
  2010. }
  2011.  
  2012. if (element === ancestor) {
  2013. return true;
  2014. }
  2015.  
  2016. return _belongsTo(element.parentNode, ancestor);
  2017. }
  2018.  
  2019. function Mousetrap(targetElement) {
  2020. var self = this;
  2021.  
  2022. targetElement = targetElement || document;
  2023.  
  2024. if (!(self instanceof Mousetrap)) {
  2025. return new Mousetrap(targetElement);
  2026. }
  2027.  
  2028. /**
  2029. * element to attach key events to
  2030. *
  2031. * @type {Element}
  2032. */
  2033. self.target = targetElement;
  2034.  
  2035. /**
  2036. * a list of all the callbacks setup via Mousetrap.bind()
  2037. *
  2038. * @type {Object}
  2039. */
  2040. self._callbacks = {};
  2041.  
  2042. /**
  2043. * direct map of string combinations to callbacks used for trigger()
  2044. *
  2045. * @type {Object}
  2046. */
  2047. self._directMap = {};
  2048.  
  2049. /**
  2050. * keeps track of what level each sequence is at since multiple
  2051. * sequences can start out with the same sequence
  2052. *
  2053. * @type {Object}
  2054. */
  2055. var _sequenceLevels = {};
  2056.  
  2057. /**
  2058. * variable to store the setTimeout call
  2059. *
  2060. * @type {null|number}
  2061. */
  2062. var _resetTimer;
  2063.  
  2064. /**
  2065. * temporary state where we will ignore the next keyup
  2066. *
  2067. * @type {boolean|string}
  2068. */
  2069. var _ignoreNextKeyup = false;
  2070.  
  2071. /**
  2072. * temporary state where we will ignore the next keypress
  2073. *
  2074. * @type {boolean}
  2075. */
  2076. var _ignoreNextKeypress = false;
  2077.  
  2078. /**
  2079. * are we currently inside of a sequence?
  2080. * type of action ("keyup" or "keydown" or "keypress") or false
  2081. *
  2082. * @type {boolean|string}
  2083. */
  2084. var _nextExpectedAction = false;
  2085.  
  2086. /**
  2087. * resets all sequence counters except for the ones passed in
  2088. *
  2089. * @param {Object} doNotReset
  2090. * @returns void
  2091. */
  2092. function _resetSequences(doNotReset) {
  2093. doNotReset = doNotReset || {};
  2094.  
  2095. var activeSequences = false,
  2096. key;
  2097.  
  2098. for (key in _sequenceLevels) {
  2099. if (doNotReset[key]) {
  2100. activeSequences = true;
  2101. continue;
  2102. }
  2103. _sequenceLevels[key] = 0;
  2104. }
  2105.  
  2106. if (!activeSequences) {
  2107. _nextExpectedAction = false;
  2108. }
  2109. }
  2110.  
  2111. /**
  2112. * finds all callbacks that match based on the keycode, modifiers,
  2113. * and action
  2114. *
  2115. * @param {string} character
  2116. * @param {Array} modifiers
  2117. * @param {Event|Object} e
  2118. * @param {string=} sequenceName - name of the sequence we are looking for
  2119. * @param {string=} combination
  2120. * @param {number=} level
  2121. * @returns {Array}
  2122. */
  2123. function _getMatches(character, modifiers, e, sequenceName, combination, level) {
  2124. var i;
  2125. var callback;
  2126. var matches = [];
  2127. var action = e.type;
  2128.  
  2129. // if there are no events related to this keycode
  2130. if (!self._callbacks[character]) {
  2131. return [];
  2132. }
  2133.  
  2134. // if a modifier key is coming up on its own we should allow it
  2135. if (action == 'keyup' && _isModifier(character)) {
  2136. modifiers = [character];
  2137. }
  2138.  
  2139. // loop through all callbacks for the key that was pressed
  2140. // and see if any of them match
  2141. for (i = 0; i < self._callbacks[character].length; ++i) {
  2142. callback = self._callbacks[character][i];
  2143.  
  2144. // if a sequence name is not specified, but this is a sequence at
  2145. // the wrong level then move onto the next match
  2146. if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
  2147. continue;
  2148. }
  2149.  
  2150. // if the action we are looking for doesn't match the action we got
  2151. // then we should keep going
  2152. if (action != callback.action) {
  2153. continue;
  2154. }
  2155.  
  2156. // if this is a keypress event and the meta key and control key
  2157. // are not pressed that means that we need to only look at the
  2158. // character, otherwise check the modifiers as well
  2159. //
  2160. // chrome will not fire a keypress if meta or control is down
  2161. // safari will fire a keypress if meta or meta+shift is down
  2162. // firefox will fire a keypress if meta or control is down
  2163. if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
  2164.  
  2165. // when you bind a combination or sequence a second time it
  2166. // should overwrite the first one. if a sequenceName or
  2167. // combination is specified in this call it does just that
  2168. //
  2169. // @todo make deleting its own method?
  2170. var deleteCombo = !sequenceName && callback.combo == combination;
  2171. var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
  2172. if (deleteCombo || deleteSequence) {
  2173. self._callbacks[character].splice(i, 1);
  2174. }
  2175.  
  2176. matches.push(callback);
  2177. }
  2178. }
  2179.  
  2180. return matches;
  2181. }
  2182.  
  2183. /**
  2184. * actually calls the callback function
  2185. *
  2186. * if your callback function returns false this will use the jquery
  2187. * convention - prevent default and stop propogation on the event
  2188. *
  2189. * @param {Function} callback
  2190. * @param {Event} e
  2191. * @returns void
  2192. */
  2193. function _fireCallback(callback, e, combo, sequence) {
  2194.  
  2195. // if this event should not happen stop here
  2196. if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
  2197. return;
  2198. }
  2199.  
  2200. if (callback(e, combo) === false) {
  2201. _preventDefault(e);
  2202. _stopPropagation(e);
  2203. }
  2204. }
  2205.  
  2206. /**
  2207. * handles a character key event
  2208. *
  2209. * @param {string} character
  2210. * @param {Array} modifiers
  2211. * @param {Event} e
  2212. * @returns void
  2213. */
  2214. self._handleKey = function (character, modifiers, e) {
  2215. var callbacks = _getMatches(character, modifiers, e);
  2216. var i;
  2217. var doNotReset = {};
  2218. var maxLevel = 0;
  2219. var processedSequenceCallback = false;
  2220.  
  2221. // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
  2222. for (i = 0; i < callbacks.length; ++i) {
  2223. if (callbacks[i].seq) {
  2224. maxLevel = Math.max(maxLevel, callbacks[i].level);
  2225. }
  2226. }
  2227.  
  2228. // loop through matching callbacks for this key event
  2229. for (i = 0; i < callbacks.length; ++i) {
  2230.  
  2231. // fire for all sequence callbacks
  2232. // this is because if for example you have multiple sequences
  2233. // bound such as "g i" and "g t" they both need to fire the
  2234. // callback for matching g cause otherwise you can only ever
  2235. // match the first one
  2236. if (callbacks[i].seq) {
  2237.  
  2238. // only fire callbacks for the maxLevel to prevent
  2239. // subsequences from also firing
  2240. //
  2241. // for example 'a option b' should not cause 'option b' to fire
  2242. // even though 'option b' is part of the other sequence
  2243. //
  2244. // any sequences that do not match here will be discarded
  2245. // below by the _resetSequences call
  2246. if (callbacks[i].level != maxLevel) {
  2247. continue;
  2248. }
  2249.  
  2250. processedSequenceCallback = true;
  2251.  
  2252. // keep a list of which sequences were matches for later
  2253. doNotReset[callbacks[i].seq] = 1;
  2254. _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
  2255. continue;
  2256. }
  2257.  
  2258. // if there were no sequence matches but we are still here
  2259. // that means this is a regular match so we should fire that
  2260. if (!processedSequenceCallback) {
  2261. _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
  2262. }
  2263. }
  2264.  
  2265. // if the key you pressed matches the type of sequence without
  2266. // being a modifier (ie "keyup" or "keypress") then we should
  2267. // reset all sequences that were not matched by this event
  2268. //
  2269. // this is so, for example, if you have the sequence "h a t" and you
  2270. // type "h e a r t" it does not match. in this case the "e" will
  2271. // cause the sequence to reset
  2272. //
  2273. // modifier keys are ignored because you can have a sequence
  2274. // that contains modifiers such as "enter ctrl+space" and in most
  2275. // cases the modifier key will be pressed before the next key
  2276. //
  2277. // also if you have a sequence such as "ctrl+b a" then pressing the
  2278. // "b" key will trigger a "keypress" and a "keydown"
  2279. //
  2280. // the "keydown" is expected when there is a modifier, but the
  2281. // "keypress" ends up matching the _nextExpectedAction since it occurs
  2282. // after and that causes the sequence to reset
  2283. //
  2284. // we ignore keypresses in a sequence that directly follow a keydown
  2285. // for the same character
  2286. var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
  2287. if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
  2288. _resetSequences(doNotReset);
  2289. }
  2290.  
  2291. _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
  2292. };
  2293.  
  2294. /**
  2295. * handles a keydown event
  2296. *
  2297. * @param {Event} e
  2298. * @returns void
  2299. */
  2300. function _handleKeyEvent(e) {
  2301.  
  2302. // normalize e.which for key events
  2303. // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
  2304. if (typeof e.which !== 'number') {
  2305. e.which = e.keyCode;
  2306. }
  2307.  
  2308. var character = _characterFromEvent(e);
  2309.  
  2310. // no character found then stop
  2311. if (!character) {
  2312. return;
  2313. }
  2314.  
  2315. // need to use === for the character check because the character can be 0
  2316. if (e.type == 'keyup' && _ignoreNextKeyup === character) {
  2317. _ignoreNextKeyup = false;
  2318. return;
  2319. }
  2320.  
  2321. self.handleKey(character, _eventModifiers(e), e);
  2322. }
  2323.  
  2324. /**
  2325. * called to set a 1 second timeout on the specified sequence
  2326. *
  2327. * this is so after each key press in the sequence you have 1 second
  2328. * to press the next key before you have to start over
  2329. *
  2330. * @returns void
  2331. */
  2332. function _resetSequenceTimer() {
  2333. clearTimeout(_resetTimer);
  2334. _resetTimer = setTimeout(_resetSequences, 1000);
  2335. }
  2336.  
  2337. /**
  2338. * binds a key sequence to an event
  2339. *
  2340. * @param {string} combo - combo specified in bind call
  2341. * @param {Array} keys
  2342. * @param {Function} callback
  2343. * @param {string=} action
  2344. * @returns void
  2345. */
  2346. function _bindSequence(combo, keys, callback, action) {
  2347.  
  2348. // start off by adding a sequence level record for this combination
  2349. // and setting the level to 0
  2350. _sequenceLevels[combo] = 0;
  2351.  
  2352. /**
  2353. * callback to increase the sequence level for this sequence and reset
  2354. * all other sequences that were active
  2355. *
  2356. * @param {string} nextAction
  2357. * @returns {Function}
  2358. */
  2359. function _increaseSequence(nextAction) {
  2360. return function () {
  2361. _nextExpectedAction = nextAction;
  2362. ++_sequenceLevels[combo];
  2363. _resetSequenceTimer();
  2364. };
  2365. }
  2366.  
  2367. /**
  2368. * wraps the specified callback inside of another function in order
  2369. * to reset all sequence counters as soon as this sequence is done
  2370. *
  2371. * @param {Event} e
  2372. * @returns void
  2373. */
  2374. function _callbackAndReset(e) {
  2375. _fireCallback(callback, e, combo);
  2376.  
  2377. // we should ignore the next key up if the action is key down
  2378. // or keypress. this is so if you finish a sequence and
  2379. // release the key the final key will not trigger a keyup
  2380. if (action !== 'keyup') {
  2381. _ignoreNextKeyup = _characterFromEvent(e);
  2382. }
  2383.  
  2384. // weird race condition if a sequence ends with the key
  2385. // another sequence begins with
  2386. setTimeout(_resetSequences, 10);
  2387. }
  2388.  
  2389. // loop through keys one at a time and bind the appropriate callback
  2390. // function. for any key leading up to the final one it should
  2391. // increase the sequence. after the final, it should reset all sequences
  2392. //
  2393. // if an action is specified in the original bind call then that will
  2394. // be used throughout. otherwise we will pass the action that the
  2395. // next key in the sequence should match. this allows a sequence
  2396. // to mix and match keypress and keydown events depending on which
  2397. // ones are better suited to the key provided
  2398. for (var i = 0; i < keys.length; ++i) {
  2399. var isFinal = i + 1 === keys.length;
  2400. var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
  2401. _bindSingle(keys[i], wrappedCallback, action, combo, i);
  2402. }
  2403. }
  2404.  
  2405. /**
  2406. * binds a single keyboard combination
  2407. *
  2408. * @param {string} combination
  2409. * @param {Function} callback
  2410. * @param {string=} action
  2411. * @param {string=} sequenceName - name of sequence if part of sequence
  2412. * @param {number=} level - what part of the sequence the command is
  2413. * @returns void
  2414. */
  2415. function _bindSingle(combination, callback, action, sequenceName, level) {
  2416.  
  2417. // store a direct mapped reference for use with Mousetrap.trigger
  2418. self._directMap[combination + ':' + action] = callback;
  2419.  
  2420. // make sure multiple spaces in a row become a single space
  2421. combination = combination.replace(/\s+/g, ' ');
  2422.  
  2423. var sequence = combination.split(' ');
  2424. var info;
  2425.  
  2426. // if this pattern is a sequence of keys then run through this method
  2427. // to reprocess each pattern one key at a time
  2428. if (sequence.length > 1) {
  2429. _bindSequence(combination, sequence, callback, action);
  2430. return;
  2431. }
  2432.  
  2433. info = _getKeyInfo(combination, action);
  2434.  
  2435. // make sure to initialize array if this is the first time
  2436. // a callback is added for this key
  2437. self._callbacks[info.key] = self._callbacks[info.key] || [];
  2438.  
  2439. // remove an existing match if there is one
  2440. _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
  2441.  
  2442. // add this call back to the array
  2443. // if it is a sequence put it at the beginning
  2444. // if not put it at the end
  2445. //
  2446. // this is important because the way these are processed expects
  2447. // the sequence ones to come first
  2448. self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({
  2449. callback: callback,
  2450. modifiers: info.modifiers,
  2451. action: info.action,
  2452. seq: sequenceName,
  2453. level: level,
  2454. combo: combination
  2455. });
  2456. }
  2457.  
  2458. /**
  2459. * binds multiple combinations to the same callback
  2460. *
  2461. * @param {Array} combinations
  2462. * @param {Function} callback
  2463. * @param {string|undefined} action
  2464. * @returns void
  2465. */
  2466. self._bindMultiple = function (combinations, callback, action) {
  2467. for (var i = 0; i < combinations.length; ++i) {
  2468. _bindSingle(combinations[i], callback, action);
  2469. }
  2470. };
  2471.  
  2472. // start!
  2473. _addEvent(targetElement, 'keypress', _handleKeyEvent);
  2474. _addEvent(targetElement, 'keydown', _handleKeyEvent);
  2475. _addEvent(targetElement, 'keyup', _handleKeyEvent);
  2476. }
  2477.  
  2478. /**
  2479. * binds an event to mousetrap
  2480. *
  2481. * can be a single key, a combination of keys separated with +,
  2482. * an array of keys, or a sequence of keys separated by spaces
  2483. *
  2484. * be sure to list the modifier keys first to make sure that the
  2485. * correct key ends up getting bound (the last key in the pattern)
  2486. *
  2487. * @param {string|Array} keys
  2488. * @param {Function} callback
  2489. * @param {string=} action - 'keypress', 'keydown', or 'keyup'
  2490. * @returns void
  2491. */
  2492. Mousetrap.prototype.bind = function (keys, callback, action) {
  2493. var self = this;
  2494. keys = keys instanceof Array ? keys : [keys];
  2495. self._bindMultiple.call(self, keys, callback, action);
  2496. return self;
  2497. };
  2498.  
  2499. /**
  2500. * unbinds an event to mousetrap
  2501. *
  2502. * the unbinding sets the callback function of the specified key combo
  2503. * to an empty function and deletes the corresponding key in the
  2504. * _directMap dict.
  2505. *
  2506. * TODO: actually remove this from the _callbacks dictionary instead
  2507. * of binding an empty function
  2508. *
  2509. * the keycombo+action has to be exactly the same as
  2510. * it was defined in the bind method
  2511. *
  2512. * @param {string|Array} keys
  2513. * @param {string} action
  2514. * @returns void
  2515. */
  2516. Mousetrap.prototype.unbind = function (keys, action) {
  2517. var self = this;
  2518. return self.bind.call(self, keys, function () {
  2519. }, action);
  2520. };
  2521.  
  2522. /**
  2523. * triggers an event that has already been bound
  2524. *
  2525. * @param {string} keys
  2526. * @param {string=} action
  2527. * @returns void
  2528. */
  2529. Mousetrap.prototype.trigger = function (keys, action) {
  2530. var self = this;
  2531. if (self._directMap[keys + ':' + action]) {
  2532. self._directMap[keys + ':' + action]({}, keys);
  2533. }
  2534. return self;
  2535. };
  2536.  
  2537. /**
  2538. * resets the library back to its initial state. this is useful
  2539. * if you want to clear out the current keyboard shortcuts and bind
  2540. * new ones - for example if you switch to another page
  2541. *
  2542. * @returns void
  2543. */
  2544. Mousetrap.prototype.reset = function () {
  2545. var self = this;
  2546. self._callbacks = {};
  2547. self._directMap = {};
  2548. return self;
  2549. };
  2550.  
  2551. /**
  2552. * should we stop this event before firing off callbacks
  2553. *
  2554. * @param {Event} e
  2555. * @param {Element} element
  2556. * @return {boolean}
  2557. */
  2558. Mousetrap.prototype.stopCallback = function (e, element) {
  2559. var self = this;
  2560.  
  2561. // if the element has the class "mousetrap" then no need to stop
  2562. if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
  2563. return false;
  2564. }
  2565.  
  2566. if (_belongsTo(element, self.target)) {
  2567. return false;
  2568. }
  2569.  
  2570. // stop for input, select, and textarea
  2571. return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
  2572. };
  2573.  
  2574. /**
  2575. * exposes _handleKey publicly so it can be overwritten by extensions
  2576. */
  2577. Mousetrap.prototype.handleKey = function () {
  2578. var self = this;
  2579. return self._handleKey.apply(self, arguments);
  2580. };
  2581.  
  2582. /**
  2583. * allow custom key mappings
  2584. */
  2585. Mousetrap.addKeycodes = function (object) {
  2586. for (var key in object) {
  2587. if (object.hasOwnProperty(key)) {
  2588. _MAP[key] = object[key];
  2589. }
  2590. }
  2591. _REVERSE_MAP = null;
  2592. };
  2593.  
  2594. /**
  2595. * Init the global mousetrap functions
  2596. *
  2597. * This method is needed to allow the global mousetrap functions to work
  2598. * now that mousetrap is a constructor function.
  2599. */
  2600. Mousetrap.init = function () {
  2601. var documentMousetrap = Mousetrap(document);
  2602. for (var method in documentMousetrap) {
  2603. if (method.charAt(0) !== '_') {
  2604. Mousetrap[method] = (function (method) {
  2605. return function () {
  2606. return documentMousetrap[method].apply(documentMousetrap, arguments);
  2607. };
  2608. }(method));
  2609. }
  2610. }
  2611. };
  2612.  
  2613. Mousetrap.init();
  2614.  
  2615. // expose mousetrap to the global object
  2616. window.Mousetrap = Mousetrap;
  2617.  
  2618. // expose as a common js module
  2619. if (typeof module !== 'undefined' && module.exports) {
  2620. module.exports = Mousetrap;
  2621. }
  2622.  
  2623. // expose mousetrap as an AMD module
  2624. if (typeof define === 'function' && define.amd) {
  2625. define(function () {
  2626. return Mousetrap;
  2627. });
  2628. }
  2629. })(typeof window !== 'undefined' ? window : null, typeof window !== 'undefined' ? document : null);
  2630. unsafeWindow.Mousetrap = Mousetrap;
  2631.  
  2632. unsafeWindow.fetchSimilarHeaders = fetchSimilarHeaders;
  2633. function fetchSimilarHeaders(callback) {
  2634. var request = new XMLHttpRequest();
  2635. request.onreadystatechange = function () {
  2636. if (request.readyState === 4) {
  2637. //
  2638. // The following headers may often be similar
  2639. // to those of the original page request...
  2640. //
  2641. if (callback && typeof callback === 'function') {
  2642. callback(request.getAllResponseHeaders());
  2643. }
  2644. }
  2645. };
  2646.  
  2647. //
  2648. // Re-request the same page (document.location)
  2649. // We hope to get the same or similar response headers to those which
  2650. // came with the current page, but we have no guarantee.
  2651. // Since we are only after the headers, a HEAD request may be sufficient.
  2652. //
  2653. request.open('HEAD', document.location, true);
  2654. request.send(null);
  2655. }
  2656.  
  2657. String.prototype.escapeSpecialChars = function () {
  2658. return this.replace(/\\n/g, "\\n")
  2659. .replace(/\\'/g, "\\'")
  2660. .replace(/\\"/g, '\\"')
  2661. .replace(/\\&/g, "\\&")
  2662. .replace(/\\r/g, "\\r")
  2663. .replace(/\\t/g, "\\t")
  2664. .replace(/\\b/g, "\\b")
  2665. .replace(/\\f/g, "\\f");
  2666. };
  2667. function headers2Object(headers) {
  2668. if (!headers) return {};
  2669. const jsonParseEscape = function (str) {
  2670. return str.replace(/\n/g, "\\n")
  2671. .replace(/\'/g, "\\'")
  2672. .replace(/\"/g, '\\"')
  2673. .replace(/\&/g, "\\&")
  2674. .replace(/\r/g, "\\r")
  2675. .replace(/\t/g, "\\t")
  2676. .replace(/\f/g, "\\f");
  2677. };
  2678. var jsonStr = '{\n' +
  2679. headers.trim().split("\n").filter(line => line.length > 2)
  2680. .map(
  2681. line => " " + [line.slice(0, line.indexOf(':')), line.slice(line.indexOf(':') + 1)]
  2682. .map(part => '"' + jsonParseEscape(part.trim()) + '"').join(':')
  2683. )
  2684. .join(",\n") +
  2685. '\n}';
  2686. console.log('jsonStr:', jsonStr);
  2687. return JSON.parse(jsonStr);
  2688. }
  2689.  
  2690.  
  2691. unsafeWindow.fetchUsingProxy = fetchUsingProxy;
  2692. /**
  2693. * @param url
  2694. * Found from: https://stackoverflow.com/questions/43871637/no-access-control-allow-origin-header-is-present-on-the-requested-resource-whe
  2695. * @param callback
  2696. * @see https://cors-anywhere.herokuapp.com/
  2697. */
  2698. function fetchUsingProxy(url, callback) {
  2699. const proxyurl = "https://cors-anywhere.herokuapp.com/";
  2700. callback = callback || (contents => console.log(contents));
  2701. fetch(proxyurl + url) // https://cors-anywhere.herokuapp.com/https://example.com
  2702. .then(response => response.text())
  2703. .then(callback)
  2704. .catch(() => console.error(`Cant access ${url} response. Blocked by browser?`))
  2705. }
  2706.  
  2707.  
  2708. unsafeWindow.getUnusualWindowObjects = function getUnusualWindowObjects(compareWindow = window) {
  2709. const plainWindowKeylist = ["postMessage", "blur", "focus", "close", "frames", "self", "window", "parent", "opener", "top", "length", "closed", "location", "document", "origin", "name", "history", "locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar", "status", "frameElement", "navigator", "customElements", "external", "screen", "innerWidth", "innerHeight", "scrollX", "pageXOffset", "scrollY", "pageYOffset", "screenX", "screenY", "outerWidth", "outerHeight", "devicePixelRatio", "clientInformation", "screenLeft", "screenTop", "defaultStatus", "defaultstatus", "styleMedia", "onanimationend", "onanimationiteration", "onanimationstart", "onsearch", "ontransitionend", "onwebkitanimationend", "onwebkitanimationiteration", "onwebkitanimationstart", "onwebkittransitionend", "isSecureContext", "onabort", "onblur", "oncancel", "oncanplay", "oncanplaythrough", "onchange", "onclick", "onclose", "oncontextmenu", "oncuechange", "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied", "onended", "onerror", "onfocus", "oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", "onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onpause", "onplay", "onplaying", "onprogress", "onratechange", "onreset", "onresize", "onscroll", "onseeked", "onseeking", "onselect", "onstalled", "onsubmit", "onsuspend", "ontimeupdate", "ontoggle", "onvolumechange", "onwaiting", "onwheel", "onauxclick", "ongotpointercapture", "onlostpointercapture", "onpointerdown", "onpointermove", "onpointerup", "onpointercancel", "onpointerover", "onpointerout", "onpointerenter", "onpointerleave", "onafterprint", "onbeforeprint", "onbeforeunload", "onhashchange", "onlanguagechange", "onmessage", "onmessageerror", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onrejectionhandled", "onstorage", "onunhandledrejection", "onunload", "performance", "stop", "open", "alert", "confirm", "prompt", "print", "requestAnimationFrame", "cancelAnimationFrame", "requestIdleCallback", "cancelIdleCallback", "captureEvents", "releaseEvents", "getComputedStyle", "matchMedia", "moveTo", "moveBy", "resizeTo", "resizeBy", "getSelection", "find", "webkitRequestAnimationFrame", "webkitCancelAnimationFrame", "fetch", "btoa", "atob", "setTimeout", "clearTimeout", "setInterval", "clearInterval", "createImageBitmap", "scroll", "scrollTo", "scrollBy", "onappinstalled", "onbeforeinstallprompt", "crypto", "ondevicemotion", "ondeviceorientation", "ondeviceorientationabsolute", "indexedDB", "webkitStorageInfo", "sessionStorage", "localStorage", "chrome", "visualViewport", "speechSynthesis", "webkitRequestFileSystem", "webkitResolveLocalFileSystemURL", "openDatabase", "applicationCache", "caches", "global", "WebUIListener", "cr", "assert", "assertNotReached", "assertInstanceof", "$", "getSVGElement", "announceAccessibleMessage", "getUrlForCss", "parseQueryParams", "setQueryParam", "findAncestorByClass", "findAncestor", "swapDomNodes", "disableTextSelectAndDrag", "isRTL", "getRequiredElement", "queryRequiredElement", "appendParam", "createElementWithClassName", "ensureTransitionEndEvent", "scrollTopForDocument", "setScrollTopForDocument", "scrollLeftForDocument", "setScrollLeftForDocument", "HTMLEscape", "elide", "quoteString", "listenOnce", "hasKeyModifiers", "recomputeLayoutWidth", "ntp"];
  2710. const farisScriptKeylist = ["log", "JSZip", "URL_REGEX_STR", "IMAGE_URL_REGEX", "VID_URL_REGEX", "gImgSearchURL", "GIMG_REVERSE_SEARCH_URL", "setClipboard", "GM_setClipboard", "GM_xmlhttpRequest", "setLog", "matchSite", "createElement", "loadScript", "ddgProxy", "getOGZscalarUrl", "reverseDdgProxy", "isDdgUrl", "targetIsInput", "createAndAddAttribute", "getGImgReverseSearchURL", "toDdgProxy", "isIterable", "GM_setValue", "GM_getValue", "q", "qa", "siteSearchUrl", "getAbsoluteURI", "getHostname", "openAllLinks", "fetchElement", "xmlRequestElement", "onLoadDim", "addCss", "addJs", "observe", "gfycatPage2GifUrl", "preloader", "waitForElement", "includeJs", "disableStyles", "createAndGetNavbar", "setStyleInHTML", "nodeDepth", "regexBetween", "extend", "getWheelDelta", "elementUnderMouse", "clearElementFunctions", "getIncrementedUrl", "printElementTextAttributes", "loadModule", "getElementsWithText", "fetchDoc", "SrcSet", "cookieUtils", "url2location", "freezeGif", "removeClickListeners", "removeDoubleSpaces", "cleanGibberish", "isBase64ImageData", "cleanDates", "downloadScripts", "escapeEncodedChars", "getCssImages", "observeDocument", "observeIframe", "observeAllFrames", "iterateOverURLPattern", "saveAs", "Mousetrap", "fetchSimilarHeaders", "fetchUsingProxy", "getModKeys", "KeyEvent", "downloadSet", "storeDownloadHistory", "MAIN_DIRECTORY", "getDownloadCount", "setNameFilesByNumber", "download", "GM_download", "downloadBatch", "downloadImageBatch", "downloadImageWithCondition", "getFileExtension", "nameFile", "makeTextFile", "anchorClick", "saveByAnchor", "zipFiles", "zipImages", "vidkeysScriptLoaded"];
  2711. const referenceKeylist = new Set(plainWindowKeylist.concat(farisScriptKeylist)); // combine both lists
  2712.  
  2713. const unusualObjects = {};
  2714. // iterate over window keys, if this key isn't in the plainWindowKeylist, then add it to the unusuals list
  2715. for (const key of Object.keys(compareWindow)) {
  2716. if (!referenceKeylist.has(key)) {
  2717. unusualObjects[key] = compareWindow[key]; // add to the unusualObjects
  2718. }
  2719. }
  2720. return unusualObjects;
  2721. };
  2722.  
  2723. unsafeWindow.getModKeys = getModifierKeys;
  2724. unsafeWindow.KeyEvent = {
  2725. DOM_VK_BACKSPACE: 8,
  2726. DOM_VK_TAB: 9,
  2727. DOM_VK_ENTER: 13,
  2728. DOM_VK_SHIFT: 16,
  2729. DOM_VK_CTRL: 17,
  2730. DOM_VK_ALT: 18,
  2731. DOM_VK_PAUSE_BREAK: 19,
  2732. DOM_VK_CAPS_LOCK: 20,
  2733. DOM_VK_ESCAPE: 27,
  2734. DOM_VK_PGUP: 33, DOM_VK_PAGE_UP: 33,
  2735. DOM_VK_PGDN: 34, DOM_VK_PAGE_DOWN: 34,
  2736. DOM_VK_END: 35,
  2737. DOM_VK_HOME: 36,
  2738. DOM_VK_LEFT: 37, DOM_VK_LEFT_ARROW: 37,
  2739. DOM_VK_UP: 38, DOM_VK_UP_ARROW: 38,
  2740. DOM_VK_RIGHT: 39, DOM_VK_RIGHT_ARROW: 39,
  2741. DOM_VK_DOWN: 40, DOM_VK_DOWN_ARROW: 40,
  2742. DOM_VK_INSERT: 45,
  2743. DOM_VK_DEL: 46, DOM_VK_DELETE: 46,
  2744. DOM_VK_0: 48, DOM_VK_ALPHA0: 48,
  2745. DOM_VK_1: 49, DOM_VK_ALPHA1: 49,
  2746. DOM_VK_2: 50, DOM_VK_ALPHA2: 50,
  2747. DOM_VK_3: 51, DOM_VK_ALPHA3: 51,
  2748. DOM_VK_4: 52, DOM_VK_ALPHA4: 52,
  2749. DOM_VK_5: 53, DOM_VK_ALPHA5: 53,
  2750. DOM_VK_6: 54, DOM_VK_ALPHA6: 54,
  2751. DOM_VK_7: 55, DOM_VK_ALPHA7: 55,
  2752. DOM_VK_8: 56, DOM_VK_ALPHA8: 56,
  2753. DOM_VK_9: 57, DOM_VK_ALPHA9: 57,
  2754. DOM_VK_A: 65,
  2755. DOM_VK_B: 66,
  2756. DOM_VK_C: 67,
  2757. DOM_VK_D: 68,
  2758. DOM_VK_E: 69,
  2759. DOM_VK_F: 70,
  2760. DOM_VK_G: 71,
  2761. DOM_VK_H: 72,
  2762. DOM_VK_I: 73,
  2763. DOM_VK_J: 74,
  2764. DOM_VK_K: 75,
  2765. DOM_VK_L: 76,
  2766. DOM_VK_M: 77,
  2767. DOM_VK_N: 78,
  2768. DOM_VK_O: 79,
  2769. DOM_VK_P: 80,
  2770. DOM_VK_Q: 81,
  2771. DOM_VK_R: 82,
  2772. DOM_VK_S: 83,
  2773. DOM_VK_T: 84,
  2774. DOM_VK_U: 85,
  2775. DOM_VK_V: 86,
  2776. DOM_VK_W: 87,
  2777. DOM_VK_X: 88,
  2778. DOM_VK_Y: 89,
  2779. DOM_VK_Z: 90,
  2780. DOM_VK_LWIN: 91, DOM_VK_LEFT_WINDOW: 91,
  2781. DOM_VK_RWIN: 92, DOM_VK_RIGHT_WINDOW: 92,
  2782. DOM_VK_SELECT: 93,
  2783.  
  2784. DOM_VK_NUMPAD0: 96,
  2785. DOM_VK_NUMPAD1: 97,
  2786. DOM_VK_NUMPAD2: 98,
  2787. DOM_VK_NUMPAD3: 99,
  2788. DOM_VK_NUMPAD4: 100,
  2789. DOM_VK_NUMPAD5: 101,
  2790. DOM_VK_NUMPAD6: 102,
  2791. DOM_VK_NUMPAD7: 103,
  2792. DOM_VK_NUMPAD8: 104,
  2793. DOM_VK_NUMPAD9: 105,
  2794. DOM_VK_MULTIPLY: 106,
  2795.  
  2796. DOM_VK_ADD: 107,
  2797. DOM_VK_SUBTRACT: 109,
  2798. DOM_VK_DECIMAL_POINT: 110,
  2799. DOM_VK_DIVIDE: 111,
  2800. DOM_VK_F1: 112,
  2801. DOM_VK_F2: 113,
  2802. DOM_VK_F3: 114,
  2803. DOM_VK_F4: 115,
  2804. DOM_VK_F5: 116,
  2805. DOM_VK_F6: 117,
  2806. DOM_VK_F7: 118,
  2807. DOM_VK_F8: 119,
  2808. DOM_VK_F9: 120,
  2809. DOM_VK_F10: 121,
  2810. DOM_VK_F11: 122,
  2811. DOM_VK_F12: 123,
  2812. DOM_VK_NUM_LOCK: 144,
  2813. DOM_VK_SCROLL_LOCK: 145,
  2814. DOM_VK_SEMICOLON: 186,
  2815. DOM_VK_EQUALS: 187, DOM_VK_EQUAL_SIGN: 187,
  2816. DOM_VK_COMMA: 188,
  2817. DOM_VK_DASH: 189,
  2818. DOM_VK_PERIOD: 190,
  2819. DOM_VK_FORWARD_SLASH: 191,
  2820. DOM_VK_GRAVE_ACCENT: 192,
  2821. DOM_VK_OPEN_BRACKET: 219,
  2822. DOM_VK_BACK_SLASH: 220,
  2823. DOM_VK_CLOSE_BRACKET: 221,
  2824. DOM_VK_SINGLE_QUOTE: 222
  2825. };
  2826. /**
  2827. * Order of key strokes in naming convention: Ctrl > Shift > Alt > Meta
  2828. * @param keyEvent
  2829. * @returns {{CTRL_ONLY: boolean, SHIFT_ONLY: boolean, ALT_ONLY: boolean, META_ONLY: boolean, NONE: boolean}}
  2830. */
  2831. function getModifierKeys(keyEvent) {
  2832. /** @type {{CTRL_ONLY: boolean, SHIFT_ONLY: boolean, ALT_ONLY: boolean, NONE: boolean}} */
  2833. return {
  2834. CTRL_SHIFT: keyEvent.ctrlKey && !keyEvent.altKey && keyEvent.shiftKey && !keyEvent.metaKey,
  2835. CTRL_ALT: keyEvent.ctrlKey && keyEvent.altKey && !keyEvent.shiftKey && !keyEvent.metaKey,
  2836. ALT_SHIFT: !keyEvent.ctrlKey && keyEvent.altKey && keyEvent.shiftKey && !keyEvent.metaKey,
  2837. CTRL_ONLY: keyEvent.ctrlKey && !keyEvent.altKey && !keyEvent.shiftKey && !keyEvent.metaKey,
  2838. CTRL_ALT_SHIFT: keyEvent.ctrlKey && keyEvent.altKey && keyEvent.shiftKey && !keyEvent.metaKey,
  2839.  
  2840. SHIFT_ONLY: !keyEvent.ctrlKey && !keyEvent.altKey && keyEvent.shiftKey && !keyEvent.metaKey,
  2841. ALT_ONLY: !keyEvent.ctrlKey && keyEvent.altKey && !keyEvent.shiftKey && !keyEvent.metaKey,
  2842. META_ONLY: !keyEvent.ctrlKey && !keyEvent.altKey && !keyEvent.shiftKey && keyEvent.metaKey,
  2843.  
  2844. NONE: !keyEvent.ctrlKey && !keyEvent.shiftKey && !keyEvent.altKey && !keyEvent.metaKey,
  2845.  
  2846. targetIsInput: (function targetIsInput() {
  2847. const ignores = document.getElementsByTagName('input');
  2848. const target = keyEvent.target;
  2849. for (let ignore of ignores)
  2850. if (target === ignore || ignore.contains(target)) {
  2851. // console.log('The target recieving the keycode is of type "input", so it will not recieve your keystroke', target);
  2852. return true;
  2853. }
  2854. return false;
  2855. })()
  2856. };
  2857. }
  2858.  
  2859. function publicizeSymbols(...parameters) {
  2860. for (const parameter of parameters) {
  2861. unsafeWindow[parameter] = parameter;
  2862. }
  2863. }
  2864.  
  2865. function mapObject(o) {
  2866. var map = new Map();
  2867. for (const key in (o)) {
  2868. if (o.hasOwnProperty(key))
  2869. map.set(key, o[key]);
  2870. }
  2871. return map;
  2872. }
  2873. function getObjOfType(targetInstance, parentObj) {
  2874. var list = [];
  2875. for (const o in parentObj) if (o instanceof targetInstance) {
  2876. return o;
  2877. }
  2878. return list;
  2879. }
  2880. function getNestedMembers(parentObject, targetType, list) {
  2881. if (!parentObject) {
  2882. console.error("parentObject is not defined:", parent);
  2883. return;
  2884. }
  2885. list = list || [];
  2886. for (const member in parentObject) {
  2887.  
  2888. const typeofObj = typeof member;
  2889.  
  2890. if (typeofObj === "object") {
  2891. getNestedMembers(member, targetType, list);
  2892. } else if (typeofObj !== 'undefined') {
  2893. if (targetType && typeofObj !== targetType)
  2894. continue;
  2895. list.push(member);
  2896. }
  2897. }
  2898. return list;
  2899. }