Embed Me!

Embed video, images from links.

目前为 2020-01-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Embed Me!
  3. // @version 0.2.1
  4. // @description Embed video, images from links.
  5. // @license MIT
  6. // @author eight04 <eight04@gmail.com>
  7. // @homepageURL https://github.com/eight04/embed-me
  8. // @supportURL https://github.com/eight04/embed-me/issues
  9. // @namespace eight04.blogspot.com
  10. // @grant GM_addStyle
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15. // @grant GM_xmlhttpRequest
  16. // @require https://greasyfork.org/scripts/371339-gm-webextpref/code/GM_webextPref.js?version=705415
  17. // @connect gfycat.com
  18. // @connect soundcloud.com
  19. // @connect www.youtube.com
  20. // @noframes true
  21. // @include *
  22. // ==/UserScript==
  23.  
  24. var fumen = {
  25. name: "Fumen",
  26. global: true,
  27. getPatterns: function() {
  28. return [
  29. /(?:fumen\.zui\.jp|harddrop\.com\/fumen)[^?]*\?(v115@.*)/i
  30. ];
  31. },
  32. getEmbedFunction: function() {
  33. return function (data, url, text, node) {
  34. var image = new Image;
  35. image.title = text;
  36. image.className = "embed-me-fumen";
  37. image.src = `https://fumen-svg-server--eight041.repl.co/?data=${encodeURIComponent(data)}`;
  38. node = node.cloneNode(false);
  39. node.appendChild(image);
  40. return node;
  41. };
  42. }
  43. };
  44.  
  45. var gfycat = {
  46. name: "Gfycat",
  47. domains: ["gfycat.com"],
  48. getPatterns: function() {
  49. return [
  50. /gfycat\.com\/([A-Z]\w*)$/i
  51. ];
  52. },
  53. getEmbedFunction: function() {
  54. return function(name, url, text, node, replace) {
  55. GM_xmlhttpRequest({
  56. method: "GET",
  57. url: "https://gfycat.com/cajax/get/" + name,
  58. onload: function(response) {
  59. var res = JSON.parse(response.responseText);
  60. if (res.error) {
  61. return;
  62. }
  63. var video = document.createElement("video");
  64. video.autoplay = true;
  65. video.loop = true;
  66. video.src = res.gfyItem.mp4Url;
  67. video.title = text;
  68. replace(video);
  69. }
  70. });
  71. };
  72. }
  73. };
  74.  
  75. var image = {
  76. name: "Image",
  77. global: true,
  78. getPatterns: function() {
  79. return [
  80. /^[^?#]+\.(?:jpg|png|gif|jpeg)(?:$|[?#])/i
  81. ];
  82. },
  83. getEmbedFunction: function() {
  84. GM_addStyle(".embed-me-image { max-width: 90%; }");
  85. return function(url, text, node) {
  86. var image = new Image;
  87. image.title = text;
  88. image.className = "embed-me-image";
  89. image.src = url;
  90. node = node.cloneNode(false);
  91. node.appendChild(image);
  92. return node;
  93. };
  94. }
  95. };
  96.  
  97. var imgur = {
  98. name: "Imgur gifv",
  99. domains: ["i.imgur.com", "imgur.com"],
  100. getPatterns: function() {
  101. return [
  102. /imgur\.com\/(\w+)(\.gifv|$)/i
  103. ];
  104. },
  105. getEmbedFunction: function() {
  106. GM_addStyle('.imgur-embed-iframe-pub { box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.10); border: 1px solid #ddd; border-radius: 2px; margin: 10px 0; width: 540px; overflow: hidden; }');
  107.  
  108. window.addEventListener("message", function(e){
  109. if (e.origin.indexOf("imgur.com") < 0) {
  110. return;
  111. }
  112.  
  113. var data = JSON.parse(e.data),
  114. id = data.href.match(/imgur\.com\/(\w+)\//)[1],
  115. css = '.imgur-embed-iframe-pub-' + id + '-' + data.context + '-540 { height: ' + data.height + 'px!important; width: 540px!important; }';
  116.  
  117. GM_addStyle(css);
  118. });
  119.  
  120. return function(id) {
  121. var iframe = document.createElement("iframe");
  122. iframe.className = "imgur-embed-iframe-pub imgur-embed-iframe-pub-" + id + "-true-540";
  123. iframe.scrolling = "no";
  124. iframe.src = "//imgur.com/" + id + "/embed?w=540&ref=" + location.href + "#embed-me";
  125. return iframe;
  126. };
  127. }
  128. };
  129.  
  130. var soundcloud = {
  131. name: "SoundCloud",
  132. domains: ["soundcloud.com"],
  133. getPatterns: function() {
  134. return [
  135. /soundcloud\.com\/[\w-]+\/[\w-]+(?:\?|$)/i
  136. ];
  137. },
  138. getEmbedFunction: function(){
  139. return function(url, text, node, replace) {
  140. GM_xmlhttpRequest({
  141. method: "GET",
  142. url: "https://soundcloud.com/oembed?format=json&url=" + url,
  143. onload: function(response) {
  144. if (!response.responseText) {
  145. return;
  146. }
  147. var html = JSON.parse(response.responseText).html;
  148. var container = document.createElement("div");
  149. container.innerHTML = html;
  150. replace(container);
  151. }
  152. });
  153. };
  154. }
  155. };
  156.  
  157. var twitch = {
  158. name: "Twitch",
  159. domains: ["www.twitch.tv"],
  160. getPatterns: function() {
  161. return [
  162. /twitch\.tv\/(\w+)\/v\/(\d+)/i
  163. ];
  164. },
  165. getEmbedFunction: function() {
  166. return function (user, id) {
  167. var container = document.createElement("div");
  168. container.innerHTML = '<object bgcolor="#000000" data="http://www.twitch.tv/swflibs/TwitchPlayer.swf" height="378" id="clip_embed_player_flash" type="application/x-shockwave-flash" width="620"><param name="movie" value="http://www.twitch.tv/swflibs/TwitchPlayer.swf" /><param name="allowScriptAccess" value="always" /><param name="allowNetworking" value="all" /><param name="allowFullScreen" value="true" /><param name="flashvars" value="channel=' + user + '&amp;auto_play=false&amp;autoplay=false&amp;autostart=false&amp;start_volume=25&amp;videoId=v' + id + '" /></object><br /><a href="http://www.twitch.tv/' + user + '" style="padding:2px 0px 4px; display:block; width: 320px; font-weight:normal; font-size:10px; text-decoration:underline;">Watch live video from ' + user + ' on Twitch</a>';
  169. return container;
  170. };
  171. }
  172. };
  173.  
  174. var video = {
  175. name: "Video",
  176. global: true,
  177. getPatterns: function() {
  178. return [
  179. /^[^?#]+\.(?:mp4|webm|ogv|mov)(?:$|[?#])/i
  180. ];
  181. },
  182. getEmbedFunction: function() {
  183. return function (url, text) {
  184. var video = document.createElement("video");
  185. video.controls = true;
  186. video.title = text;
  187. video.src = url;
  188. return video;
  189. };
  190. }
  191. };
  192.  
  193. var youtube = {
  194. name: "Youtube",
  195. domains: [
  196. "www.youtube.com",
  197. "youtu.be"
  198. ],
  199. getPatterns: function() {
  200. return [
  201. /youtube\.com\/watch\?.*?v=([^&]+)/i,
  202. /youtu\.be\/([^?]+)/i,
  203. /youtube\.com\/embed\/([^?#]+)/,
  204. /youtube\.com\/v\/([^?#]+)/
  205. ];
  206. },
  207. getEmbedFunction: function() {
  208. return function(id, url, text, node, replace) {
  209. url = "https://www.youtube.com/watch?v=" + id;
  210. GM_xmlhttpRequest({
  211. method: "GET",
  212. url: "https://www.youtube.com/oembed?format=json&url=" + url,
  213. onload: function(response) {
  214. var html = JSON.parse(response.responseText).html,
  215. container = document.createElement("div");
  216.  
  217. container.innerHTML = html;
  218. replace(container);
  219. }
  220. });
  221. };
  222. }
  223. };
  224.  
  225. var modules = [fumen,gfycat,image,imgur,soundcloud,twitch,video,youtube];
  226.  
  227. /* global GM_webextPref */
  228.  
  229. const pref = GM_webextPref({
  230. default: {
  231. simple: true,
  232. excludes: "",
  233. ...Object.fromEntries(modules.map(m => [m.name, true]))
  234. },
  235. body: [
  236. {
  237. label: "Ignore complex anchor",
  238. type: "checkbox",
  239. key: "simple"
  240. },
  241. {
  242. label: "Excludes these urls (regexp per line)",
  243. type: "textarea",
  244. key: "excludes"
  245. },
  246. ...modules.map(module => ({
  247. label: module.name,
  248. key: module.name,
  249. type: "checkbox"
  250. }))
  251. ],
  252. getNewScope: () => location.hostname
  253. });
  254. const globalMods = [];
  255. const index = {};
  256. let excludedUrl = null;
  257.  
  258. pref.ready().then(() => {
  259. pref.on("change", change => {
  260. if (change.excludes != null) {
  261. updateExclude();
  262. }
  263. });
  264. updateExclude();
  265. for (const mod of modules) {
  266. if (mod.global) {
  267. globalMods.push(mod);
  268. } else {
  269. var i;
  270. for (i = 0; i < mod.domains.length; i++) {
  271. index[mod.domains[i]] = mod;
  272. }
  273. }
  274. }
  275. observeDocument(function(node){
  276. var links = node.querySelectorAll("a[href]"), i;
  277. for (i = 0; i < links.length; i++) {
  278. embed(links[i]);
  279. }
  280. });
  281. });
  282.  
  283. function validParent(node) {
  284. var cache = node;
  285. while (node != document.documentElement) {
  286. if (node.INVALID || node.className.indexOf("embed-me") >= 0) {
  287. cache.INVALID = true;
  288. return false;
  289. }
  290. if (!node.parentNode) {
  291. return false;
  292. }
  293. if (node.VALID) {
  294. break;
  295. }
  296. node = node.parentNode;
  297. }
  298. cache.VALID = true;
  299. return true;
  300. }
  301.  
  302. function valid(node) {
  303. if (!validParent(node)) {
  304. return false;
  305. }
  306. if (node.nodeName != "A" || !node.href) {
  307. return false;
  308. }
  309. if (pref.get("simple") && (node.childNodes.length != 1 || node.childNodes[0].nodeType != 3)) {
  310. return false;
  311. }
  312. if (excludedUrl && excludedUrl.test(node.href)) {
  313. return false;
  314. }
  315. return true;
  316. }
  317.  
  318. function getPatterns(mod) {
  319. if (!mod.getPatterns) {
  320. return [];
  321. }
  322. if (!mod.patterns) {
  323. mod.patterns = mod.getPatterns();
  324. }
  325. return mod.patterns;
  326. }
  327.  
  328. function getEmbedFunction(mod) {
  329. if (!mod.embedFunction) {
  330. mod.embedFunction = mod.getEmbedFunction();
  331. }
  332. return mod.embedFunction;
  333. }
  334.  
  335. function callEmbedFunc(node, params, func) {
  336. var replace = function (newNode) {
  337. if (!node.parentNode) {
  338. // The node was detached from DOM tree
  339. return;
  340. }
  341. newNode.classList.add("embed-me");
  342. node.parentNode.replaceChild(newNode, node);
  343. };
  344. params.push(node.href, node.textContent, node, replace);
  345. var result = func.apply(null, params);
  346. if (result) {
  347. replace(result);
  348. }
  349. }
  350.  
  351. function embed(node) {
  352. if (!valid(node)) {
  353. return;
  354. }
  355. // Never process same element twice
  356. node.INVALID = true;
  357.  
  358. var mods = [], mod, patterns, match, i, j;
  359.  
  360. if (node.hostname in index) {
  361. mods.push(index[node.hostname]);
  362. }
  363.  
  364. mods = mods.concat(globalMods).filter(mod => pref.get(mod.name));
  365.  
  366. for (j = 0; j < mods.length; j++) {
  367. mod = mods[j];
  368. patterns = getPatterns(mod);
  369.  
  370. for (i = 0; i < patterns.length; i++) {
  371. if ((match = patterns[i].exec(node.href))) {
  372. callEmbedFunc(node, Array.prototype.slice.call(match, 1), getEmbedFunction(mod));
  373. return;
  374. }
  375. }
  376. }
  377. }
  378.  
  379. function observeDocument(callback) {
  380.  
  381. setTimeout(callback, 0, document.body);
  382.  
  383. new MutationObserver(function(mutations){
  384. var i;
  385. for (i = 0; i < mutations.length; i++) {
  386. if (!mutations[i].addedNodes.length) {
  387. continue;
  388. }
  389. callback(mutations[i].target);
  390. }
  391. }).observe(document.body, {
  392. childList: true,
  393. subtree: true
  394. });
  395. }
  396.  
  397. function updateExclude() {
  398. const excludes = pref.get("excludes").trim();
  399. excludedUrl = excludes && new RegExp(excludes.split(/\s*\n\s*/).join("|"), "i");
  400. }