TweetDeck Image Assistant

Download/Share Images Faster

当前为 2017-05-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name TweetDeck Image Assistant
  3. // @namespace http://ejew.in/
  4. // @version 0.7
  5. // @description Download/Share Images Faster
  6. // @author EntranceJew
  7. // @match https://tweetdeck.twitter.com/*
  8. // @require https://cdn.rawgit.com/eligrey/FileSaver.js/5ed507ef8aa53d8ecfea96d96bc7214cd2476fd2/FileSaver.min.js
  9. // @noframes
  10. // @grant none
  11. // ==/UserScript==
  12. /*
  13. 0.7 - apparently getting gif sources works most reliably inside callbacks
  14. 0.6 - hotfix to prevent redundant page reloading with stream-media seek methods
  15. 0.5 - video links no longer destroy links, ctrl+click the timestamp to copy the tweet link, ctrl+click the link icon to prepare multi-image tweets for discord
  16. 0.4 - changed download icon, added copy links button, videos now don't flash their preview, videos no longer close your draft tweets panel
  17. 0.3 - gif support wasn't that hard
  18. 0.2 - removed debug prints, updated mimes, added video download link, instant-spice now grabs videos
  19. 0.1 - initial version
  20. */
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. var tool_icon = '<li class="tweet-action-item pull-left margin-r--13 margin-l--1">';
  26. tool_icon += '<a class="js-show-tip tweet-action position-rel" href="#" rel="download" title="" data-original-title="Download">';
  27. tool_icon += '<i class="icon icon-attachment icon-attachment-toggle txt-center"></i> <span class="is-vishidden"> Download </span>';
  28. tool_icon += '</a> </li>';
  29.  
  30. var link_icon = '<li class="tweet-action-item clipboard pull-left margin-r--13 margin-l--1">';
  31. link_icon += '<a class="js-show-tip tweet-action position-rel" href="#" rel="hotlink" title="" data-original-title="Hotlink">';
  32. link_icon += '<i class="icon icon-link icon-link-toggle txt-center"></i> <span class="is-vishidden"> Hotlink </span>';
  33. link_icon += '</a> </li>';
  34.  
  35. var mime_db = {
  36. jpeg: "image/jpeg",
  37. jpg: "image/jpeg",
  38. gif: "image/gif",
  39. webp: "image/webp",
  40. mp4: "video/mp4",
  41. m3u8: "application/x-mpegURL",
  42. undefined: "text/plain"
  43. };
  44.  
  45. function clipboard_data(html, el) {
  46. var tmpEl;
  47. if (typeof el !== "undefined") {
  48. // you may want some specific styling for your content - then provide a custom DOM node with classes, inline styles or whatever you want
  49. // just omit that argument for a default div container
  50. tmpEl = el;
  51. } else {
  52. tmpEl = document.createElement("div");
  53.  
  54. // since we remove the element immediately we'd actually not have to style it - but IE 11 prompts us to confirm the clipboard interaction and until you click the confirm button, the element would show. so: still extra stuff for IE, as usual.
  55. tmpEl.style.opacity = 0;
  56. tmpEl.style.position = "absolute";
  57. tmpEl.style.pointerEvents = "none";
  58. tmpEl.style.zIndex = -1;
  59. }
  60.  
  61. // fill it with your HTML
  62. tmpEl.innerHTML = html;
  63.  
  64. // append the temporary node to the DOM
  65. document.body.appendChild(tmpEl);
  66.  
  67. // select the newly added node
  68. var range = document.createRange();
  69. range.selectNode(tmpEl);
  70. window.getSelection().addRange(range);
  71.  
  72. // copy
  73. document.execCommand("copy");
  74.  
  75. // and remove the element immediately
  76. document.body.removeChild(tmpEl);
  77. }
  78.  
  79. // http://stackoverflow.com/a/2091331
  80. function getQueryVariable(str, variable) {
  81. var query = str.substring(1);
  82. var vars = query.split('&');
  83. for (var i = 0; i < vars.length; i++) {
  84. var pair = vars[i].split('=');
  85. if (decodeURIComponent(pair[0]) == variable) {
  86. return decodeURIComponent(pair[1]);
  87. }
  88. }
  89. console.log('Query variable %s not found', variable);
  90. }
  91.  
  92. function detect_mime(url){
  93. return mime_db[ /(?:\.([^.]+))?$/.exec(url)[1] ];
  94. }
  95.  
  96. function get_img_data( url, on_load ) {
  97. var xhr = new XMLHttpRequest();
  98. xhr.open("GET", url);
  99. xhr.responseType = "blob";
  100. xhr.onload = on_load;
  101. xhr.send();
  102. }
  103.  
  104. function download_now( url ){
  105. if( url.length ){
  106. get_img_data( url, function( e ){
  107. var img_name = url.substring( url.lastIndexOf('/')+1 );
  108. var the_blob = new Blob([this.response], {type: detect_mime(url)});
  109. saveAs( the_blob, img_name.replace(/:orig$/, "") );
  110. });
  111. }
  112. }
  113.  
  114. function nice_url( url, replacement ){
  115. replacement = replacement || ":orig";
  116. var bg = url;
  117. bg = bg.replace('url(','').replace(')','').replace(/\"/gi, "");
  118. bg = bg.replace(/:thumb$/, "");
  119. bg = bg.replace(/:small$/, "");
  120. bg = bg.replace(/:medium$/, "");
  121. bg = bg.replace(/:large$/, "");
  122. return bg;
  123. }
  124.  
  125. setInterval(function(){
  126. $('.stream-item:not([data-ejew])').each(function(){
  127. var grand_dad = $( this );
  128.  
  129. /*
  130. // for appending to the dropdown menu if we wanted that
  131. var tool_bar = grand_dad.find('.js-dropdown-content > ul');
  132. tool_bar.prepend('<li class="is-selectable"><a href="#" data-action="ejew">Spice it up</a></li>');
  133. */
  134.  
  135. // find all the images and store their links in data
  136. var sources = [];
  137. var media_type = 'idk';
  138. if( grand_dad.find('.is-video').length ){
  139. media_type = 'video';
  140. sources.push( function( e ){
  141. var anchor = grand_dad.find('.js-media-image-link');
  142. var o_target = anchor.attr('target');
  143. var o_src = anchor.attr('src');
  144. anchor.attr('target', '');
  145. anchor.attr('src', '#');
  146. anchor.click();
  147.  
  148. var embeds = $('.js-embeditem');
  149. while( !embeds.length ){
  150. embeds = $('.js-embeditem');
  151. }
  152.  
  153. var vid_url = '';
  154. embeds.each(function(){
  155. var iframe_src = $( this ).find( 'iframe' ).attr('src');
  156. vid_url = getQueryVariable( iframe_src, 'video_url' );
  157. $('.mdl-dismiss .icon-close').click();
  158. });
  159.  
  160. anchor.attr('target', o_target);
  161. anchor.attr('src', o_src);
  162.  
  163. if( vid_url.length ){
  164. return vid_url;
  165. }
  166. });
  167. } else if( grand_dad.find('.is-gif').length ){
  168. media_type = 'gif';
  169. sources.push( function(){
  170. return grand_dad.find('video.js-media-gif').attr('src');
  171. });
  172. } else {
  173. grand_dad.find('.js-media-image-link, .js-media .media-image').each( function(i, el){
  174. sources.push( nice_url( $( el ).css('background-image'), ":orig" ) );
  175. });
  176. if( sources.length ){
  177. media_type = 'image';
  178. }
  179. }
  180. var orig_link = grand_dad.find("a.txt-small.no-wrap[rel=\"url\"]");
  181. orig_link.on('click', function(e){
  182. if( e.ctrlKey ){
  183. e.preventDefault();
  184. clipboard_data( $( this ).attr("href") );
  185. }
  186. });
  187. grand_dad.data('ejew-sources', sources);
  188. grand_dad.data('direct-url', orig_link.attr("href"));
  189.  
  190. var new_link = $( link_icon );
  191. new_link.on('click', function(e){
  192. var sources = grand_dad.data('ejew-sources');
  193. for( var i = 0; i < sources.length; i++ ){
  194. if( typeof( sources[i] ) != "string" ){
  195. sources[i] = sources[i]( this );
  196. }
  197. }
  198.  
  199. var the_url = grand_dad.data('direct-url');
  200. if( e.ctrlKey && sources.length > 1){
  201. sources[0] = the_url;
  202. }
  203.  
  204. if( sources.length ){
  205. clipboard_data( sources.join("\n") );
  206. } else {
  207. clipboard_data( the_url );
  208. }
  209. });
  210.  
  211. // make an instance of the toolbar button
  212. var new_tool = $( tool_icon );
  213. new_tool.on('click', function(e){
  214. var sources = grand_dad.data('ejew-sources');
  215. for( var i = 0; i < sources.length; i++ ){
  216. var source = sources[i];
  217. if( typeof( source ) != "string" ){
  218. source = source( this );
  219. }
  220.  
  221. download_now( source );
  222. }
  223. });
  224.  
  225. // attach
  226. var attachment_point = grand_dad.find('ul.tweet-actions > li:nth-last-child(2)');
  227. attachment_point.before( new_tool );
  228. attachment_point.before( new_link );
  229.  
  230. // prevent loading up this element again
  231. grand_dad.attr('data-ejew', 'in');
  232. });
  233.  
  234. // make it so that you can copy image source from previews
  235. $('img.media-img:not([data-ejew])').each(function(){
  236. $( this ).attr('src', nice_url( $( this ).attr('src') ) );
  237. $( this ).attr('data-ejew', 'in');
  238. });
  239.  
  240. // provide a download source link in zoomable previews for videos
  241. $('.js-embeditem:not([data-ejew])').each(function(){
  242. var iframe_src = $( this ).find( 'iframe' ).attr('src');
  243. var vid_url = getQueryVariable( iframe_src, 'video_url' );
  244. var dl_link = $( '<a href="#">Download Source</a>' );
  245. dl_link.on('click', function(){
  246. download_now( vid_url );
  247. });
  248. $(".med-origlink").after( dl_link );
  249. $( this ).attr('data-ejew', 'in');
  250. });
  251. }, 300);
  252. })();