- // ==UserScript==
- // @name TweetDeck Image Assistant
- // @namespace http://ejew.in/
- // @version 1.0
- // @description Download/Share Images Faster
- // @author EntranceJew
- // @match https://tweetdeck.twitter.com/*
- // @require https://cdn.rawgit.com/eligrey/FileSaver.js/5ed507ef8aa53d8ecfea96d96bc7214cd2476fd2/FileSaver.min.js
- // @require https://cdn.rawgit.com/kamranahmedse/jquery-toast-plugin/1105577ed71ef368f8aa3d96295857643dca43d7/dist/jquery.toast.min.js
- // @noframes
- // @resource toastCSS https://cdn.rawgit.com/kamranahmedse/jquery-toast-plugin/1105577ed71ef368f8aa3d96295857643dca43d7/dist/jquery.toast.min.css
- // @grant GM_addStyle
- // @grant GM_getResourceText
- // ==/UserScript==
- /*
- 1.0 - fixed issue with logic in setting default image resolution
- 0.9 - toast notifications, better clipboard access methods, better image sources, ctrl+click like/rt/download to follow from column owner, fixed errors in previewer
- 0.8 - added t.co link unmasking
- 0.7 - apparently getting gif sources works most reliably inside callbacks
- 0.6 - hotfix to prevent redundant page reloading with stream-media seek methods
- 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
- 0.4 - changed download icon, added copy links button, videos now don't flash their preview, videos no longer close your draft tweets panel
- 0.3 - gif support wasn't that hard
- 0.2 - removed debug prints, updated mimes, added video download link, instant-spice now grabs videos
- 0.1 - initial version
- */
-
- (function() {
- 'use strict';
-
- GM_addStyle( GM_getResourceText("toastCSS") );
-
- var toast_prototype = {
- text: "Don't forget to star the repository if you like it.", // Text that is to be shown in the toast
- heading: 'Note', // Optional heading to be shown on the toast
- icon: 'success', // Type of toast icon
- showHideTransition: 'slide', // fade, slide or plain
- allowToastClose: true, // Boolean value true or false
- hideAfter: 1000, // false to make it sticky or number representing the miliseconds as time after which toast needs to be hidden
- stack: 32, // false if there should be only one toast at a time or a number representing the maximum number of toasts to be shown at a time
- position: 'bottom-left', // bottom-left or bottom-right or bottom-center or top-left or top-right or top-center or mid-center or an object representing the left, right, top, bottom values
-
-
-
- textAlign: 'left', // Text alignment i.e. left, right or center
- loader: true, // Whether to show loader or not. True by default
- loaderBg: '#9EC600', // Background color of the toast loader
- beforeShow: function () {}, // will be triggered before the toast is shown
- afterShown: function () {}, // will be triggered after the toat has been shown
- beforeHide: function () {}, // will be triggered before the toast gets hidden
- afterHidden: function () {} // will be triggered after the toast has been hidden
- };
-
- function toast( heading, text, icon ){
- return $.toast(jQuery.extend(true, toast_prototype, {
- heading: heading,
- text: text,
- icon: icon
- }));
- }
-
- var toolbar_size = 6;
- var tool_icon_width = (1 / toolbar_size) * 100;
-
- GM_addStyle( ".tweet-detail-action-item, .without-tweet-drag-handles .tweet-detail-action-item { width: " + tool_icon_width + "% !important; }" );
-
- var tool_icon = '<li class="tweet-action-item pull-left margin-r--13 margin-l--1">';
- tool_icon += '<a class="js-show-tip tweet-action position-rel" href="#" rel="download" title="" data-original-title="Download">';
- tool_icon += '<i class="icon icon-attachment icon-attachment-toggle txt-center"></i> <span class="is-vishidden"> Download </span>';
- tool_icon += '</a> </li>';
-
- var link_icon = '<li class="tweet-action-item clipboard pull-left margin-r--13 margin-l--1">';
- link_icon += '<a class="js-show-tip tweet-action position-rel" href="#" rel="hotlink" title="" data-original-title="Hotlink">';
- link_icon += '<i class="icon icon-link icon-link-toggle txt-center"></i> <span class="is-vishidden"> Hotlink </span>';
- link_icon += '</a> </li>';
-
- var mime_db = {
- jpeg: "image/jpeg",
- jpg: "image/jpeg",
- gif: "image/gif",
- webp: "image/webp",
- mp4: "video/mp4",
- m3u8: "application/x-mpegURL",
- undefined: "text/plain"
- };
-
- function clipboard_data( text ){
- var tc = $('.compose-text-container .js-compose-text');
- var orig = tc.val();
- var active = document.activeElement;
- tc.val( text );
- tc[0].focus();
- tc[0].setSelectionRange( 0, text.length );
- document.execCommand("copy");
- tc.val( orig );
- active.focus();
- toast("Copied <em>" + text.split(/\r*\n/).length + "</em> Lines!", text, "info");
- }
-
- // http://stackoverflow.com/a/2091331
- function getQueryVariable(str, variable) {
- var query = str.substring(1);
- var vars = query.split('&');
- for (var i = 0; i < vars.length; i++) {
- var pair = vars[i].split('=');
- if (decodeURIComponent(pair[0]) == variable) {
- return decodeURIComponent(pair[1]);
- }
- }
- console.log('Query variable %s not found', variable);
- }
-
- function detect_mime(url){
- return mime_db[ /(?:\.([^.]+))?$/.exec(url)[1] ];
- }
-
- function get_img_data( url, on_load ) {
- var xhr = new XMLHttpRequest();
- xhr.open("GET", url);
- xhr.responseType = "blob";
- xhr.onload = on_load;
- xhr.send();
- }
-
- function download_now( url ){
- if( url.length ){
- get_img_data( url, function( e ){
- var img_name = url.substring( url.lastIndexOf('/')+1 );
- var the_blob = new Blob([this.response], {type: detect_mime(url)});
- var save_file_name = img_name.replace(/:orig$/, "");
- saveAs( the_blob, save_file_name );
- if( save_file_name.endsWith('mp4') ){
- toast("Downloaded <em>1</em> Video!", save_file_name, "info");
- }
- });
- }
- }
-
- function nice_url( url, replacement ){
- if( replacement === "" ){
- // whatever
- } else if( !replacement ){
- replacement = ":orig";
- }
- var bg = url;
- bg = bg.replace('url(','').replace(')','').replace(/\"/gi, "");
- bg = bg.replace(/:thumb$/, replacement);
- bg = bg.replace(/:small$/, replacement);
- bg = bg.replace(/:medium$/, replacement);
- bg = bg.replace(/:large$/, replacement);
- return bg;
- }
-
- // danger: this could potentially lockup if the element isn't guaranteed to appear.
- function lock_find( selector, context ){
- var results = $( selector, context );
- while( !results.length ){
- results = $( selector, context );
- }
- return results;
- }
-
- // we have to do literal jungle japes in order to get to the follow button from here
- // strap in
- function follow_tweet( selector ){
- selector.find('ul.tweet-actions i.icon-more').click();
- var column_owner = selector.parents('.column-panel').find('h1.column-title span.attribution').text();
- var more = lock_find('.js-dropdown.dropdown-menu a[data-action="followOrUnfollow"]', selector);
- more.parent('li.is-selectable').addClass('is-selected');
- more.click();
- var follow_container = lock_find('div.js-modal-panel');
- var column_owner_follow = null;
-
- // entrancejew only follows from his third account
- // entrancejew also refuses to implement settings yet
- if( column_owner == "@EntranceJew" ){
- column_owner_follow = lock_find('div.js-follow-from:nth-child(3)', follow_container);
- } else {
- follow_container.find('.js-from-username').each(function(){
- var this_name = $( this ).text();
- if( this_name.includes( column_owner ) ){
- column_owner_follow = $( this ).parent('.js-follow-from');
- }
- });
- }
-
- var follow_button = null;
- var follow_seeker = setInterval(function(){
- follow_button = column_owner_follow.find('.js-action-follow[class*=" s-"]');
- if( follow_button.length ){
- if( follow_button.hasClass('s-not-following') ){
- var user_to_follow = $('.mdl-header-title a[rel="user"]').text();
- follow_button.find('button').click();
- toast("Followed <em>1</em> Users!", user_to_follow, "info");
- } else if( !follow_button.hasClass('s-following') ){
- var attrs = follow_button.attr('class');
- toast("I'm Confused!", "What is a <em>" + attrs + "</em>?", "error");
- }
- follow_container.find('.icon-close').click();
- clearInterval(follow_seeker);
- }
- },50);
- }
-
- setInterval(function(){
- $('.stream-item:not([data-ejew])').each(function(){
- var grand_dad = $( this );
-
- /*
- // for appending to the dropdown menu if we wanted that
- var tool_bar = grand_dad.find('.js-dropdown-content > ul');
- tool_bar.prepend('<li class="is-selectable"><a href="#" data-action="ejew">Spice it up</a></li>');
- */
-
- // find all the images and store their links in data
- var sources = [];
- var media_type = 'idk';
- if( grand_dad.find('.is-video').length ){
- media_type = 'video';
- sources.push( function( e ){
- var anchor = grand_dad.find('.js-media-image-link');
- var o_target = anchor.attr('target');
- var o_src = anchor.attr('src');
- anchor.attr('target', '');
- anchor.attr('src', '#');
- anchor.click();
-
- var embeds = lock_find('.js-embeditem');
-
- var vid_url = '';
- embeds.each(function(){
- var iframe_src = $( this ).find( 'iframe' ).attr('src');
- if( iframe_src ){
- vid_url = getQueryVariable( iframe_src, 'video_url' );
- }
- $('.mdl-dismiss .icon-close').click();
- });
-
- anchor.attr('target', o_target);
- anchor.attr('src', o_src);
-
- if( vid_url.length ){
- return vid_url;
- }
- });
- } else if( grand_dad.find('.is-gif').length ){
- media_type = 'gif';
- sources.push( function(){
- return grand_dad.find('video.js-media-gif').attr('src');
- });
- } else {
- grand_dad.find('.js-media-image-link, .js-media .media-image').each( function(i, el){
- sources.push( nice_url( $( el ).css('background-image') ) );
- });
- if( sources.length ){
- media_type = 'image';
- }
- }
- var orig_link = grand_dad.find("a.txt-small.no-wrap[rel=\"url\"]");
- orig_link.on('click', function(e){
- if( e.ctrlKey ){
- e.preventDefault();
- clipboard_data( $( this ).attr("href") );
- }
- });
- grand_dad.data('ejew-sources', sources);
- grand_dad.data('direct-url', orig_link.attr("href"));
-
- // enhance stock buttons with auto-follow
- grand_dad.find('.icon-retweet').on('click', function(e){
- if( e.ctrlKey ){
- follow_tweet( grand_dad );
- }
- });
- grand_dad.find('.icon-favorite').on('click', function(e){
- if( e.ctrlKey ){
- follow_tweet( grand_dad );
- }
- });
-
- // add more buttons
- var new_link = $( link_icon );
- new_link.on('click', function(e){
- var sources = grand_dad.data('ejew-sources');
- for( var i = 0; i < sources.length; i++ ){
- if( typeof( sources[i] ) != "string" ){
- sources[i] = sources[i]( this );
- }
- }
-
- var the_url = grand_dad.data('direct-url');
- if( e.ctrlKey && sources.length > 1){
- sources[0] = the_url;
- }
-
- if( sources.length ){
- clipboard_data( sources.join("\n") );
- } else {
- clipboard_data( the_url );
- }
- });
-
- // make an instance of the toolbar button
- var new_tool = $( tool_icon );
- new_tool.on('click', function(e){
- var sources = grand_dad.data('ejew-sources');
- for( var i = 0; i < sources.length; i++ ){
- var source = sources[i];
- if( typeof( source ) != "string" ){
- source = source( this );
- }
- download_now( source );
- }
-
- if( sources.length > 1 || !sources[0].endsWith("mp4") ){
- toast("Downloaded <em>" + sources.length + "</em> Images!", sources.join("\n"), "info");
- }
-
- if( e.ctrlKey ){
- follow_tweet( grand_dad );
- }
- });
-
- // attach
- var attachment_point = grand_dad.find('ul.tweet-actions > li:nth-last-child(2)');
- attachment_point.before( new_tool );
- attachment_point.before( new_link );
-
- // prevent loading up this element again
- grand_dad.attr('data-ejew', 'in');
- });
-
- // unmask t.co links
- var links_to_unmask = $('a[href^="https://t.co/"][data-full-url]');
- links_to_unmask.each(function(){
- $( this ).attr('href', $( this ).data('full-url') );
- });
- if( links_to_unmask.length > 0 ){
- toast("Unmasked <em>" + links_to_unmask.length + "</em> Links!", "<em>That's a lot!</em>", "info");
- }
-
- // make it so that you can copy image source from previews
- $('img.media-img:not([data-ejew])').each(function(){
- $( this ).attr('src', nice_url( $( this ).attr('src'), "" ) );
- $( this ).attr('data-ejew', 'in');
- });
-
- // provide a download source link in zoomable previews for videos
- $('.js-embeditem:not([data-ejew])').each(function(){
- var iframe_src = $( this ).find( 'iframe' ).attr('src');
- if( iframe_src ){
- var vid_url = getQueryVariable( iframe_src, 'video_url' );
- var dl_link = $( '<a href="#">Download Source</a>' );
- dl_link.on('click', function(){
- download_now( vid_url );
- });
- $(".med-origlink").after( dl_link );
- }
- $( this ).attr('data-ejew', 'in');
- });
- }, 300);
- })();