YouTube Embedder

Convert links and optionally URL text which points to a YouTube video, into embedded YouTube frame. For URL text, it can be converted to a link only.

目前为 2018-05-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Embedder
  3. // @namespace https://greasyfork.org/en/users/85671-jcunews
  4. // @description Convert links and optionally URL text which points to a YouTube video, into embedded YouTube frame. For URL text, it can be converted to a link only.
  5. // @author jcunews
  6. // @version 1.0.3
  7. // @license GNU AGPLv3
  8. // @match *://*/*
  9. // @exclude *://www.youtube.com/embed/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function(xe, fe, ce, rx, trx, trxg, hrx, lo, qu) {
  14.  
  15. //===== CONFIGURATION BEGIN =====
  16.  
  17. //The minimum width & height of the container element where the link or URL text is found, in order to apply the conversion.
  18. var minimumWidth = 256;
  19. var minimumHeight = 144;
  20.  
  21. //Embedded YouTube frame width in pixels (should be multiplication of 16).
  22. //If zero, the width is the container element width of the link or URL text, and the height is calculated using 16:9 ratio (widescreen video dimension).
  23. //If the final width is smaller than the minimum width setting, and if the source is an URL text, it will be converted to a link instead.
  24. var frameWidth = 0;
  25.  
  26. //Enable URL text to YouTube frame converter (aside from links).
  27. var convertUrlText = true;
  28.  
  29. //Convert to link only, when on these domains.
  30. var convertToLinkOnlyDomains = /somesite\.info|subnet\d+\.other\.net/i;
  31.  
  32. //Delay between processing each node scanning queue. Lower value means faster scanning, but more CPU intensive.
  33. //Higher value means lesser CPU usage, but also means slower scanning.
  34. var queueDelay = 50; //in milliseconds. 1000ms = 1 second.
  35.  
  36. //===== CONFIGURATION END =====
  37.  
  38. function linkToFrame(node, m, pn, w, h, a) {
  39. if (node.attributes["ytebd_nocovert"] || !node.offsetWidth || !node.offsetHeight) return;
  40. if (!lo && (m = node.href.match(rx))) {
  41. m = "https://www.youtube.com/embed/" + m[1];
  42. pn = node.parentNode;
  43. while (pn) {
  44. if (pn.scrollHeight > pn.offsetHeight) break;
  45. pn = pn.parentNode;
  46. }
  47. h = ((pn || node.parentNode).offsetHeight * 0.9) >> 0;
  48. w = frameWidth > 0 ? frameWidth : node.parentNode.offsetWidth;
  49. if ((pn = (w / 16 * 9) >> 0) > h) {
  50. w = (h / 9 * 16) >> 0;
  51. } else h = pn;
  52. if ((w >= minimumWidth) && (h >= minimumHeight)) {
  53. c = document.createElement("IFRAME");
  54. c.src = m;
  55. c.allowFullscreen = true;
  56. c.referrerPolicy = "no-referrer";
  57. c.style.border = "none";
  58. c.width = w;
  59. c.height = h;
  60. c.setAttribute("ytebd_nocovert", "1");
  61. node.replaceWith(c);
  62. } else if (w && h) node.setAttribute("ytebd_nocovert", "1");
  63. } else node.setAttribute("ytebd_nocovert", "1");
  64. }
  65.  
  66. function processNode(node, c, m, w, h, pn) {
  67. switch (node.nodeType) {
  68. case Node.ELEMENT_NODE:
  69. case Node.DOCUMENT_NODE:
  70. if ((node.attributes && node.attributes["ytebd_nocovert"])) break;
  71. if (node.nodeName === "A") {
  72. linkToFrame(node);
  73. node = null;
  74. }
  75. if (node && (xe.indexOf(node.nodeName) < 0)) {
  76. if (fe.indexOf(node.nodeName) >= 0) {
  77. m = (m = node.src.match(/:\/\/(.*?)\//)) && (m[1] === location.hostname);
  78. } else m = true;
  79. if (m && ((node.nodeName !== "A") || convertUrlText)) {
  80. node.childNodes.forEach(queue);
  81. }
  82. }
  83. break;
  84. case Node.TEXT_NODE:
  85. if (convertUrlText && (m = node.nodeValue.match(trx))) {
  86. w = node.nodeValue;
  87. h = -1;
  88. a = [];
  89. while (c = trxg.exec(w)) {
  90. if (c.index > 0) a.push(w.substring(h, c.index));
  91. a.push(c);
  92. h = c.index + c[0].length;
  93. }
  94. if ((h > 0) && (h < w.length)) a.push(w.substr(h));
  95. for (c = a.length - 1; c >= 0; c--) {
  96. if (!Array.isArray(a[c]) && !a[c].replace(/^\s+|\s+$/g, "")) a.splice(c, 1);
  97. }
  98. if (node.parentNode.nodeName === "A") {
  99. if (a.length === 1) {
  100. node.href = "https://www.youtube.com/embed/" + a[0][1];
  101. node.rel = "nofollow noopener noreferrer";
  102. node.target = "_blank";
  103. }
  104. a = null;
  105. }
  106. if (a) {
  107. pn = node.parentNode;
  108. c = node.nextSibling;
  109. a.forEach(function(v, i, n) {
  110. if (!Array.isArray(v)) {
  111. n = document.createTextNode(v);
  112. } else {
  113. n = document.createElement("A");
  114. n.textContent = v[0];
  115. n.title = v[1];
  116. n.href = "https://www.youtube.com/embed/" + v[1];
  117. n.rel = "nofollow noopener noreferrer";
  118. n.target = "_blank";
  119. }
  120. if (i > 0) {
  121. pn.insertBefore(n, c);
  122. } else {
  123. node.replaceWith(n);
  124. }
  125. });
  126. }
  127. }
  128. }
  129. }
  130.  
  131. function processQueue() {
  132. setTimeout(function(node) {
  133. processNode(node);
  134. if (qu.length) setTimeout(processQueue, queueDelay);
  135. }, 50, qu.splice(0, 1)[0]);
  136. }
  137.  
  138. function queue(node) {
  139. qu.push(node);
  140. if (qu.length === 1) setTimeout(processQueue, queueDelay);
  141. }
  142.  
  143. xe = ["BUTTON", "INPUT", "SCRIPT", "SELECT", "STYLE"];
  144. fe = ["FRAME", "IFRAME"];
  145. rx = /^(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})/i;
  146. trx = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/i;
  147. trxg = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/gi;
  148. hrx = /^(?:(?:www\.)?youtube\.com|youtu\.be)$/i;
  149. lo = convertToLinkOnlyDomains.test(location.hostname);
  150. qu = [];
  151.  
  152. if (document.body) queue(document.body);
  153.  
  154. (new MutationObserver(function(records) {
  155. records.forEach(function(record) {
  156. if (record.type === "childList") {
  157. record.addedNodes.forEach(queue);
  158. } else queue(record.target);
  159. });
  160. })).observe(document.body, {attributes: true, attributeFilter: ["class", "style"], childList: true, subtree: true});
  161.  
  162. })();