From b35e4d4d01e559553196a317fc75e11677e97397 Mon Sep 17 00:00:00 2001
From: chylex <contact@chylex.com>
Date: Fri, 21 Jul 2017 12:14:15 +0200
Subject: [PATCH] Add "Save all images as..." context menu option for tweets
 with multiple images

---
 Core/Bridge/TweetDeckBridge.cs   |  5 ++++-
 Core/Handling/ContextMenuBase.cs | 25 +++++++++++++++++------
 Core/Utils/TwitterUtils.cs       | 35 +++++++++++++++++++++++++-------
 Resources/Scripts/code.js        | 14 +++++++------
 4 files changed, 59 insertions(+), 20 deletions(-)

diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs
index 13cd422d..5903f0ea 100644
--- a/Core/Bridge/TweetDeckBridge.cs
+++ b/Core/Bridge/TweetDeckBridge.cs
@@ -10,9 +10,11 @@ sealed class TweetDeckBridge{
         public static string LastRightClickedImage = string.Empty;
         public static string LastHighlightedTweet = string.Empty;
         public static string LastHighlightedQuotedTweet = string.Empty;
+        public static string[] LastHighlightedTweetImages = new string[0];
 
         public static void ResetStaticProperties(){
             LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
+            LastHighlightedTweetImages = new string[0];
         }
 
         private readonly FormBrowser form;
@@ -43,10 +45,11 @@ public void SetLastRightClickedImage(string link){
             form.InvokeAsyncSafe(() => LastRightClickedImage = link);
         }
 
-        public void SetLastHighlightedTweet(string link, string quotedLink){
+        public void SetLastHighlightedTweet(string link, string quotedLink, string imageList){
             form.InvokeAsyncSafe(() => {
                 LastHighlightedTweet = link;
                 LastHighlightedQuotedTweet = quotedLink;
+                LastHighlightedTweetImages = imageList.Split(';');
             });
         }
 
diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs
index aa174b36..edeb0d0e 100644
--- a/Core/Handling/ContextMenuBase.cs
+++ b/Core/Handling/ContextMenuBase.cs
@@ -25,12 +25,15 @@ private static string GetImage(IContextMenuParams parameters){
         private const int MenuOpenLinkUrl = 26500;
         private const int MenuCopyLinkUrl = 26501;
         private const int MenuCopyUsername = 26502;
-        private const int MenuOpenImage = 26503;
-        private const int MenuSaveImage = 26504;
-        private const int MenuCopyImageUrl = 26505;
+        private const int MenuOpenImageUrl = 26503;
+        private const int MenuCopyImageUrl = 26504;
+        private const int MenuSaveImage = 26505;
+        private const int MenuSaveAllImages = 26506;
         private const int MenuOpenDevTools = 26599;
 
         private readonly Form form;
+        
+        private string[] lastHighlightedTweetImageList;
 
         protected ContextMenuBase(Form form){
             this.form = form;
@@ -38,6 +41,7 @@ protected ContextMenuBase(Form form){
 
         public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
             bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
+            lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
 
             if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
                 if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){
@@ -54,9 +58,14 @@ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser bro
             }
 
             if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
-                model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
-                model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
+                model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
                 model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
+                model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
+
+                if (lastHighlightedTweetImageList.Length > 1){
+                    model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
+                }
+
                 model.AddSeparator();
             }
         }
@@ -71,7 +80,7 @@ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser br
                     SetClipboardText(GetLink(parameters));
                     break;
 
-                case MenuOpenImage:
+                case MenuOpenImageUrl:
                     BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
                     break;
 
@@ -79,6 +88,10 @@ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser br
                     TwitterUtils.DownloadImage(GetImage(parameters), ImageQuality);
                     break;
 
+                case MenuSaveAllImages:
+                    TwitterUtils.DownloadImages(lastHighlightedTweetImageList, ImageQuality);
+                    break;
+
                 case MenuCopyImageUrl:
                     SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
                     break;
diff --git a/Core/Utils/TwitterUtils.cs b/Core/Utils/TwitterUtils.cs
index d3391b1f..5a469ef4 100644
--- a/Core/Utils/TwitterUtils.cs
+++ b/Core/Utils/TwitterUtils.cs
@@ -1,4 +1,5 @@
-using CefSharp;
+using System;
+using CefSharp;
 using System.Drawing;
 using System.IO;
 using System.Windows.Forms;
@@ -46,22 +47,42 @@ public static string GetImageLink(string url, ImageQuality quality){
                 return url;
             }
         }
-
+        
         public static void DownloadImage(string url, ImageQuality quality){
-            string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(url));
+            DownloadImages(new string[]{ url }, quality);
+        }
+
+        public static void DownloadImages(string[] urls, ImageQuality quality){
+            if (urls.Length == 0){
+                return;
+            }
+
+            string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(urls[0]));
             string ext = Path.GetExtension(file); // includes dot
             
             using(SaveFileDialog dialog = new SaveFileDialog{
                 AutoUpgradeEnabled = true,
-                OverwritePrompt = true,
+                OverwritePrompt = urls.Length == 1,
                 Title = "Save image",
                 FileName = file,
-                Filter = string.IsNullOrEmpty(ext) ? "Image (unknown)|*.*" : $"Image (*{ext})|*{ext}"
+                Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
             }){
                 if (dialog.ShowDialog() == DialogResult.OK){
-                    BrowserUtils.DownloadFileAsync(GetImageLink(url, quality), dialog.FileName, null, ex => {
+                    void OnFailure(Exception ex){
                         FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
-                    });
+                    }
+
+                    if (urls.Length == 1){
+                        BrowserUtils.DownloadFileAsync(GetImageLink(urls[0], quality), dialog.FileName, null, OnFailure);
+                    }
+                    else{
+                        string pathBase = Path.ChangeExtension(dialog.FileName, null);
+                        string pathExt = Path.GetExtension(dialog.FileName);
+
+                        for(int index = 0; index < urls.Length; index++){
+                            BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), pathBase+(index+1)+pathExt, null, OnFailure);
+                        }
+                    }
                 }
             }
         }
diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js
index bf0e2e9d..635c162a 100644
--- a/Resources/Scripts/code.js
+++ b/Resources/Scripts/code.js
@@ -363,12 +363,12 @@
       return !!highlightedColumnObj;
     };
     
-    var updateHighlightedTweet = function(ele, obj, link, embeddedLink){
+    var updateHighlightedTweet = function(ele, obj, link, embeddedLink, imageList){
       highlightedTweetEle = ele;
       highlightedTweetObj = obj;
       
       if (lastTweet !== link){
-        $TD.setLastHighlightedTweet(link, embeddedLink);
+        $TD.setLastHighlightedTweet(link, embeddedLink, imageList);
         lastTweet = link;
       }
     };
@@ -398,16 +398,18 @@
           if (tweet.chirpType === TD.services.ChirpBase.TWEET){
             var link = tweet.getChirpURL();
             var embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
-
-            updateHighlightedTweet(me, tweet, link || "", embedded || "");
+            var images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
+            // TODO maybe handle embedded images too?
+            
+            updateHighlightedTweet(me, tweet, link || "", embedded || "", images);
           }
           else{
-            updateHighlightedTweet(me, tweet, "", "");
+            updateHighlightedTweet(me, tweet, "", "", "");
           }
         }
       }
       else if (e.type === "mouseleave"){
-        updateHighlightedTweet(null, null, "", "");
+        updateHighlightedTweet(null, null, "", "", "");
       }
     });
   })();