1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 10:32:10 +02:00

Compare commits

...

38 Commits
1.4.2 ... 1.5

Author SHA1 Message Date
09d39df15a Release 1.5 2016-11-15 00:19:24 +01:00
1f9db3bda6 Rewrite folder write permission check to hopefully make it more reliable 2016-11-14 23:32:45 +01:00
b7104c8828 Remove privilege requirement from update & portable installer, handle updater privileges within TweetDuck 2016-11-14 20:53:56 +01:00
5da02b4092 Make the portable installer fully autonomous 2016-11-14 20:52:11 +01:00
802f1e3042 Refactor Process.Start uses (missing using statement, use WindowsUtils for elevation) 2016-11-14 19:39:26 +01:00
66db0df45a Add WindowsUtils.StartProcess for easier elevated process starting 2016-11-14 19:38:36 +01:00
650c2e2eb7 Remove redundant null check from WindowsUtils 2016-11-14 18:54:58 +01:00
6ab3754129 Update write permission check to use the storage folder 2016-11-14 14:06:15 +01:00
7dca0a8cab Add plugin config migration to the new data folder 2016-11-14 14:01:22 +01:00
7cd0b4ad54 Fix highlighted tweets staying in context menu after logging out of TweetDeck 2016-11-14 10:35:58 +01:00
97acb41eee Fix console errors caused by running browser scripts even outside of TweetDeck website 2016-11-14 10:35:42 +01:00
b916b9726e Add a method to check if a frame has a TweetDeck URL to BrowserUtils 2016-11-14 10:34:52 +01:00
32d3990ace Rewrite plugin data export and combined file stream identifiers, add missing plugin warning 2016-11-14 10:15:21 +01:00
cb1fd109cc Prevent missing plugin folders from causing a crash 2016-11-14 10:12:22 +01:00
0e68dd6185 Fix Configure button in PluginControl causing issues with mixed slash types 2016-11-14 09:47:56 +01:00
fb6502bc65 Rename plugin data folder to TD_Plugins for consistency 2016-11-14 09:39:48 +01:00
c7e7403781 Update plugin config to use the data folder instead of plugin root 2016-11-14 06:14:38 +01:00
bf224408a3 Rewrite PluginBridge to accommodate the functions to the new plugin folder handling 2016-11-14 06:14:14 +01:00
84b7078873 Assign a data folder to each plugin and add new folder handling functions 2016-11-14 06:09:05 +01:00
89e5943d8f Add a PluginFolder enum and a plugin data root path to Program 2016-11-14 05:43:30 +01:00
b78c4cb8f0 Move PluginEnvironment and PluginGroup to a separate Enums package 2016-11-14 05:08:18 +01:00
976ba074a8 Add a warning for outdated config in reply-account plugin 2016-11-13 15:18:10 +01:00
5205d59b96 Rewrite custom selector in reply-account plugin to fix and futureproof TweetDeck changes 2016-11-13 15:06:51 +01:00
e8394b9c08 Add browser console logging to debug output 2016-11-13 13:45:10 +01:00
9cd00239f9 Fix update installer creating Start Menu entry in portable mode (applied to 1.4.3) 2016-10-23 00:48:23 +02:00
b6b26142f8 Release 1.4.3 2016-10-22 22:13:15 +02:00
4ee99376fd Add a portable installer that uses the full installer with a custom flag 2016-10-22 21:49:31 +02:00
b0261342ff Add portable functionality to update installer 2016-10-22 21:11:48 +02:00
87fd2a521e Fix quoted tweet link in notification window not resetting
Closes #75
2016-10-21 06:50:34 +02:00
334793c6f6 Add full installer error/abort handling to the update installer
- Will abort and cleanup if full installer fails
- Fixed uninstallation files getting deleted if the full installer could
not be started
2016-10-20 18:48:35 +02:00
3e70d991bb Fix installer unable to run TD when TweetDuck.exe requires admin privileges 2016-10-20 18:45:10 +02:00
feec96fc5c Pass installation path to the updater to allow portability
Closes #77
2016-10-20 18:23:48 +02:00
765984709e Make sure TweetDeck uninstaller runs elevated, add safety nets and shield icons to buttons 2016-10-20 04:23:48 +02:00
c7c9931f68 Add an extension method to add UAC shield to a button 2016-10-18 16:21:08 +02:00
ae64573510 Remove old debug.log and ChromeDWriteFontCache on update install 2016-10-18 16:05:34 +02:00
d675af5aa4 Rename the debug log again to TD_Console.txt for consistency 2016-10-18 16:04:31 +02:00
9480d17cfc Change CEF debug file to jsconsole.log in storage path 2016-10-18 15:55:25 +02:00
5ac1df2283 Fix LockManager not finding correct process in debug 2016-10-18 15:48:55 +02:00
35 changed files with 494 additions and 198 deletions

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@ x86/
[Oo]bj/ [Oo]bj/
bld/* bld/*
!bld/gen_full.iss !bld/gen_full.iss
!bld/gen_port.iss
!bld/gen_upd.iss !bld/gen_upd.iss
!bld/Resources !bld/Resources

View File

@@ -58,7 +58,7 @@ namespace TweetDck.Configuration{
Process foundProcess = Process.GetProcessById(pid); Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){ using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){ if (foundProcess.ProcessName == currentProcess.ProcessName || foundProcess.MainWindowTitle == Program.BrandName){
LockingProcess = foundProcess; LockingProcess = foundProcess;
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Controls{ namespace TweetDck.Core.Controls{
static class ControlExtensions{ static class ControlExtensions{
@@ -35,6 +36,12 @@ namespace TweetDck.Core.Controls{
} }
} }
public static void SetElevated(this Button button){
button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
}
public static void EnableMultilineShortcuts(this TextBox textBox){ public static void EnableMultilineShortcuts(this TextBox textBox){
textBox.KeyDown += (sender, args) => { textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){ if (args.Control && args.KeyCode == Keys.A){

View File

@@ -8,8 +8,10 @@ using TweetDck.Core.Other;
using TweetDck.Resources; using TweetDck.Resources;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using System.Drawing; using System.Drawing;
using TweetDck.Core.Utils;
using TweetDck.Updates; using TweetDck.Updates;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
namespace TweetDck.Core{ namespace TweetDck.Core{
@@ -52,6 +54,10 @@ namespace TweetDck.Core{
LifeSpanHandler = new LifeSpanHandler() LifeSpanHandler = new LifeSpanHandler()
}; };
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += Browser_LoadingStateChanged; this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
this.browser.FrameLoadEnd += Browser_FrameLoadEnd; this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification)); this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
@@ -113,7 +119,7 @@ namespace TweetDck.Core{
} }
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain){ if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(e.Frame, "code.js");
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){ if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){

View File

@@ -9,6 +9,7 @@ using TweetDck.Core.Handling;
using TweetDck.Resources; using TweetDck.Resources;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core{ namespace TweetDck.Core{
sealed partial class FormNotification : Form{ sealed partial class FormNotification : Form{
@@ -62,6 +63,7 @@ namespace TweetDck.Core{
public bool FreezeTimer { get; set; } public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; } public bool ContextMenuOpen { get; set; }
public string CurrentUrl { get; private set; } public string CurrentUrl { get; private set; }
public string CurrentQuotedTweetUrl { get; set; }
public EventHandler Initialized; public EventHandler Initialized;
private bool isInitialized; private bool isInitialized;
@@ -99,6 +101,10 @@ namespace TweetDck.Core{
LifeSpanHandler = new LifeSpanHandler() LifeSpanHandler = new LifeSpanHandler()
}; };
#if DEBUG
browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged; browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd; browser.FrameLoadEnd += Browser_FrameLoadEnd;
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this)); browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
@@ -277,6 +283,7 @@ namespace TweetDck.Core{
private void LoadTweet(TweetNotification tweet){ private void LoadTweet(TweetNotification tweet){
CurrentUrl = tweet.Url; CurrentUrl = tweet.Url;
CurrentQuotedTweetUrl = string.Empty; // load from JS
timerProgress.Stop(); timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue); totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);

View File

@@ -37,6 +37,11 @@ namespace TweetDck.Core.Handling{
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet; lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet; lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!BrowserUtils.IsTweetDeckWebsite(frame)){
lastHighlightedTweet = string.Empty;
lastHighlightedQuotedTweet = string.Empty;
}
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser"); model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");

View File

@@ -7,7 +7,7 @@ namespace TweetDck.Core.Handling{
private const int MenuSkipTweet = 26600; private const int MenuSkipTweet = 26600;
private const int MenuFreeze = 26601; private const int MenuFreeze = 26601;
private const int MenuCopyTweetUrl = 26602; private const int MenuCopyTweetUrl = 26602;
private const int MenuCopyTweetEmbeddedUrl = 26603; private const int MenuCopyQuotedTweetUrl = 26603;
private readonly FormNotification form; private readonly FormNotification form;
private readonly bool enableCustomMenu; private readonly bool enableCustomMenu;
@@ -29,8 +29,8 @@ namespace TweetDck.Core.Handling{
if (!string.IsNullOrEmpty(form.CurrentUrl)){ if (!string.IsNullOrEmpty(form.CurrentUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){ if (!string.IsNullOrEmpty(form.CurrentQuotedTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address"); model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
model.AddSeparator(); model.AddSeparator();
@@ -61,8 +61,8 @@ namespace TweetDck.Core.Handling{
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText); Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
return true; return true;
case MenuCopyTweetEmbeddedUrl: case MenuCopyQuotedTweetUrl:
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText); Clipboard.SetText(form.CurrentQuotedTweetUrl, TextDataFormat.UnicodeText);
return true; return true;
} }

View File

@@ -11,7 +11,6 @@ namespace TweetDck.Core.Handling{
public static string LastRightClickedLink = string.Empty; public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty; public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty; public static string LastHighlightedQuotedTweet = string.Empty;
public static string NotificationTweetEmbedded = string.Empty;
public static string ClipboardImagePath = string.Empty; public static string ClipboardImagePath = string.Empty;
private readonly FormBrowser form; private readonly FormBrowser form;
@@ -81,8 +80,8 @@ namespace TweetDck.Core.Handling{
}); });
} }
public void SetNotificationTweetEmbedded(string link){ public void SetNotificationQuotedTweet(string link){
form.InvokeSafe(() => NotificationTweetEmbedded = link); notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
} }
public void OpenSettingsMenu(){ public void OpenSettingsMenu(){

View File

@@ -6,6 +6,7 @@ using System.Windows.Forms;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Controls; using TweetDck.Plugins.Controls;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
namespace TweetDck.Core.Other{ namespace TweetDck.Core.Other{

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace TweetDck.Core.Other.Settings.Export{ namespace TweetDck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{ class CombinedFileStream : IDisposable{
public const char KeySeparator = '/'; public const char KeySeparator = '|';
private readonly Stream stream; private readonly Stream stream;
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
this.stream = stream; this.stream = stream;
} }
public void WriteFile(string[] identifier, string path){
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
}
public void WriteFile(string identifier, string path){ public void WriteFile(string identifier, string path){
byte[] name = Encoding.UTF8.GetBytes(identifier); byte[] name = Encoding.UTF8.GetBytes(identifier);
@@ -77,6 +81,13 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
} }
public string[] KeyValue{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
}
}
private readonly byte[] contents; private readonly byte[] contents;
public Entry(string identifier, byte[] contents){ public Entry(string identifier, byte[] contents){

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core.Other.Settings.Export{ namespace TweetDck.Core.Other.Settings.Export{
sealed class ExportManager{ sealed class ExportManager{
@@ -26,33 +27,14 @@ namespace TweetDck.Core.Other.Settings.Export{
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
stream.WriteFile("config", Program.ConfigFilePath); stream.WriteFile("config", Program.ConfigFilePath);
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){ foreach(Plugin plugin in plugins.Plugins){
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator); foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
try{
if (split.Length < 3){ stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
continue; }catch(ArgumentOutOfRangeException e){
} MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
else if (split.Length == 3){
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
continue;
} }
} }
try{
stream.WriteFile("plugin.off"+path.Relative, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){
try{
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
} }
if (includeSession){ if (includeSession){
@@ -72,6 +54,7 @@ namespace TweetDck.Core.Other.Settings.Export{
public bool Import(){ public bool Import(){
try{ try{
bool updatedPlugins = false; bool updatedPlugins = false;
HashSet<string> missingPlugins = new HashSet<string>();
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){ using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
CombinedFileStream.Entry entry; CombinedFileStream.Entry entry;
@@ -83,28 +66,24 @@ namespace TweetDck.Core.Other.Settings.Export{
Program.ReloadConfig(); Program.ReloadConfig();
break; break;
case "plugin.off": case "plugin.data":
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]); string[] value = entry.KeyValue;
if (Directory.Exists(root)){ entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
if (plugins.IsPluginInstalled(value[0])){
updatedPlugins = true; updatedPlugins = true;
} }
else{
missingPlugins.Add(value[0]);
}
break; break;
case "plugin.usr":
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
updatedPlugins = true;
break;
case "cookies": case "cookies":
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Profile", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath)); entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
// okay to and restart, 'cookies' is always the last entry
IsRestarting = true; IsRestarting = true;
Program.Restart(new string[]{ "-importcookies" });
} }
break; break;
@@ -112,7 +91,14 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
} }
if (updatedPlugins){ if (missingPlugins.Count > 0){
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
if (IsRestarting){
Program.Restart(new string[]{ "-importcookies" });
}
else if (updatedPlugins){
plugins.Reload(); plugins.Reload();
} }
@@ -138,10 +124,10 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{ return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath, Full = fullPath,
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
}); }) : Enumerable.Empty<PathInfo>();
} }
private class PathInfo{ private class PathInfo{

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
namespace TweetDck.Core.Utils{ namespace TweetDck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
@@ -27,7 +28,7 @@ namespace TweetDck.Core.Utils{
} }
public static void OpenExternalBrowser(string url){ // TODO implement mailto public static void OpenExternalBrowser(string url){ // TODO implement mailto
Process.Start(url); using(Process.Start(url)){}
} }
public static string GetFileNameFromUrl(string url){ public static string GetFileNameFromUrl(string url){
@@ -47,5 +48,15 @@ namespace TweetDck.Core.Utils{
client.DownloadFileAsync(new Uri(url), target); client.DownloadFileAsync(new Uri(url), target);
} }
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
#if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
}
#endif
} }
} }

View File

@@ -15,6 +15,7 @@ namespace TweetDck.Core.Utils{
public const int MOUSEEVENTF_RIGHTUP = 0x10; public const int MOUSEEVENTF_RIGHTUP = 0x10;
public const int SB_HORZ = 0; public const int SB_HORZ = 0;
public const int BCM_SETSHIELD = 0x160C;
public const int WH_MOUSE_LL = 14; public const int WH_MOUSE_LL = 14;
public const int WH_MOUSEWHEEL = 0x020A; public const int WH_MOUSEWHEEL = 0x020A;

View File

@@ -1,5 +1,5 @@
using System.IO; using System.Diagnostics;
using System.Linq; using System.IO;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
@@ -7,27 +7,32 @@ namespace TweetDck.Core.Utils{
static class WindowsUtils{ static class WindowsUtils{
public static bool CheckFolderPermission(string path, FileSystemRights right){ public static bool CheckFolderPermission(string path, FileSystemRights right){
try{ try{
AuthorizationRuleCollection rules = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier)); AuthorizationRuleCollection collection = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(NTAccount));
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity == null || identity.Groups == null){ foreach(FileSystemAccessRule rule in collection){
return false; if ((rule.FileSystemRights & right) == right){
} return true;
bool accessAllow = false, accessDeny = false;
foreach(FileSystemAccessRule rule in rules.Cast<FileSystemAccessRule>().Where(rule => identity.Groups.Contains(rule.IdentityReference) && (right & rule.FileSystemRights) == right)){
switch(rule.AccessControlType){
case AccessControlType.Allow: accessAllow = true; break;
case AccessControlType.Deny: accessDeny = true; break;
} }
} }
return accessAllow && !accessDeny; return false;
} }
catch{ catch{
return false; return false;
} }
} }
public static Process StartProcess(string file, string arguments, bool runElevated){
ProcessStartInfo processInfo = new ProcessStartInfo{
FileName = file,
Arguments = arguments
};
if (runElevated){
processInfo.Verb = "runas";
}
return Process.Start(processInfo);
}
} }
} }

View File

@@ -8,6 +8,7 @@ using System.Windows.Forms;
using Microsoft.Win32; using Microsoft.Win32;
using TweetDck.Core.Other; using TweetDck.Core.Other;
using System.Drawing; using System.Drawing;
using TweetDck.Core.Controls;
namespace TweetDck.Migration{ namespace TweetDck.Migration{
static class MigrationManager{ static class MigrationManager{
@@ -41,8 +42,9 @@ namespace TweetDck.Migration{
Button btnMigrate = formQuestion.AddButton("Migrate"); Button btnMigrate = formQuestion.AddButton("Migrate");
Button btnMigrateAndPurge = formQuestion.AddButton("Migrate && Purge"); Button btnMigrateAndPurge = formQuestion.AddButton("Migrate && Purge");
btnMigrateAndPurge.Location = new Point(btnMigrateAndPurge.Location.X-18, btnMigrateAndPurge.Location.Y); btnMigrateAndPurge.Location = new Point(btnMigrateAndPurge.Location.X-20, btnMigrateAndPurge.Location.Y);
btnMigrateAndPurge.Width += 18; btnMigrateAndPurge.Width += 20;
btnMigrateAndPurge.SetElevated();
if (formQuestion.ShowDialog() == DialogResult.OK){ if (formQuestion.ShowDialog() == DialogResult.OK){
decision = formQuestion.ClickedButton == btnMigrateAndPurge ? MigrationDecision.MigratePurge : decision = formQuestion.ClickedButton == btnMigrateAndPurge ? MigrationDecision.MigratePurge :
@@ -85,10 +87,20 @@ namespace TweetDck.Migration{
} }
else if (!Program.UserConfig.IgnoreUninstallCheck){ else if (!Program.UserConfig.IgnoreUninstallCheck){
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck"); string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
if (guid != null){
const string prompt = "TweetDeck is still installed on your computer, do you want to uninstall it?";
if (guid != null && MessageBox.Show("TweetDeck is still installed on your computer, do you want to uninstall it?", "Uninstall TweetDeck", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ using(FormMessage formQuestion = new FormMessage("Uninstall TweetDeck", prompt, MessageBoxIcon.Question)){
MigrationUtils.RunUninstaller(guid, 0); formQuestion.AddButton("No");
CleanupTweetDeck();
Button btnYes = formQuestion.AddButton("Yes");
btnYes.SetElevated();
if (formQuestion.ShowDialog() == DialogResult.OK && formQuestion.ClickedButton == btnYes && MigrationUtils.RunUninstaller(guid, 0)){
CleanupTweetDeck();
}
}
} }
Program.UserConfig.IgnoreUninstallCheck = true; Program.UserConfig.IgnoreUninstallCheck = true;
@@ -155,8 +167,8 @@ namespace TweetDck.Migration{
// uninstall in the background // uninstall in the background
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck"); string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
if (guid != null){ if (guid != null && !MigrationUtils.RunUninstaller(guid, 5000)){
MigrationUtils.RunUninstaller(guid, 5000); return;
} }
// registry cleanup // registry cleanup

View File

@@ -2,18 +2,25 @@
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.Win32; using Microsoft.Win32;
using TweetDck.Core.Utils;
namespace TweetDck.Migration{ namespace TweetDck.Migration{
static class MigrationUtils{ static class MigrationUtils{
public static void RunUninstaller(string guid, int timeout){ public static bool RunUninstaller(string guid, int timeout){
Process uninstaller = Process.Start("msiexec.exe", "/x "+guid+" /quiet /qn"); try{
Process uninstaller = WindowsUtils.StartProcess("msiexec.exe", "/x "+guid+" /quiet /qn", true);
if (uninstaller != null){ if (uninstaller != null){
if (timeout > 0){ if (timeout > 0){
uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem
}
uninstaller.Close();
} }
uninstaller.Close(); return true;
}catch(Exception){
return false;
} }
} }

View File

@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
} }
private void btnOpenConfig_Click(object sender, EventArgs e){ private void btnOpenConfig_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath+"\"")){} using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
} }
private void btnToggleState_Click(object sender, EventArgs e){ private void btnToggleState_Click(object sender, EventArgs e){

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins.Enums{
[Flags] [Flags]
enum PluginEnvironment{ enum PluginEnvironment{
None = 0, None = 0,

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Plugins.Enums{
enum PluginFolder{
Root, Data
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDck.Plugins{ namespace TweetDck.Plugins.Enums{
enum PluginGroup{ enum PluginGroup{
Official, Custom Official, Custom
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
class Plugin{ class Plugin{
@@ -26,29 +27,30 @@ namespace TweetDck.Plugins{
public bool HasConfig{ public bool HasConfig{
get{ get{
return ConfigFile.Length > 0 && GetFullPathIfSafe(ConfigFile).Length > 0; return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
} }
} }
public string ConfigPath{ public string ConfigPath{
get{ get{
return HasConfig ? Path.Combine(path, ConfigFile) : string.Empty; return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
} }
} }
public bool HasDefaultConfig{ public bool HasDefaultConfig{
get{ get{
return ConfigDefault.Length > 0 && GetFullPathIfSafe(ConfigDefault).Length > 0; return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
} }
} }
public string DefaultConfigPath{ public string DefaultConfigPath{
get{ get{
return HasDefaultConfig ? Path.Combine(path, ConfigDefault) : string.Empty; return HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
} }
} }
private readonly string path; private readonly string pathRoot;
private readonly string pathData;
private readonly string identifier; private readonly string identifier;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){ private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
{ "NAME", "" }, { "NAME", "" },
@@ -64,8 +66,13 @@ namespace TweetDck.Plugins{
private bool? canRun; private bool? canRun;
private Plugin(string path, PluginGroup group){ private Plugin(string path, PluginGroup group){
this.path = path; string name = Path.GetFileName(path);
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path); System.Diagnostics.Debug.Assert(name != null);
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
this.identifier = group.GetIdentifierPrefix()+name;
this.Group = group; this.Group = group;
this.Environments = PluginEnvironment.None; this.Environments = PluginEnvironment.None;
} }
@@ -74,7 +81,26 @@ namespace TweetDck.Plugins{
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath; string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){ if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = GetPluginFolder(PluginFolder.Data);
if (!Directory.Exists(dataFolder)){ // config migration
string originalFile = Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigFile);
if (File.Exists(originalFile)){
try{
Directory.CreateDirectory(dataFolder);
File.Copy(originalFile, configPath, false);
File.Delete(originalFile); // will fail without write perms in program folder, ignore if so
}catch{
// ignore
}
return;
}
}
try{ try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false); File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e); Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
@@ -85,18 +111,27 @@ namespace TweetDck.Plugins{
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){ if (Environments.HasFlag(environment)){
string file = environment.GetScriptFile(); string file = environment.GetScriptFile();
return file != null ? Path.Combine(path, file) : string.Empty; return file != null ? Path.Combine(pathRoot, file) : string.Empty;
} }
else{ else{
return string.Empty; return string.Empty;
} }
} }
public string GetFullPathIfSafe(string relativePath){ public string GetPluginFolder(PluginFolder folder){
string fullPath = Path.Combine(path, relativePath); switch(folder){
case PluginFolder.Root: return pathRoot;
case PluginFolder.Data: return pathData;
default: return string.Empty;
}
}
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
string rootFolder = GetPluginFolder(folder);
string fullPath = Path.Combine(rootFolder, relativePath);
try{ try{
string folderPathName = new DirectoryInfo(path).FullName; string folderPathName = new DirectoryInfo(rootFolder).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath); DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){ while(currentInfo.Parent != null){
@@ -124,7 +159,7 @@ namespace TweetDck.Plugins{
public override bool Equals(object obj){ public override bool Equals(object obj){
Plugin plugin = obj as Plugin; Plugin plugin = obj as Plugin;
return plugin != null && plugin.path.Equals(path); return plugin != null && plugin.identifier.Equals(identifier);
} }
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){ public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
@@ -18,35 +19,26 @@ namespace TweetDck.Plugins{
fileCache.Clear(); fileCache.Clear();
} }
private string GetFullPathIfSafe(int token, string path){ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
Plugin plugin = manager.GetPluginFromToken(token); Plugin plugin = manager.GetPluginFromToken(token);
return plugin == null ? string.Empty : plugin.GetFullPathIfSafe(path); string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
}
public void WriteFile(int token, string path, string contents){
string fullPath = GetFullPathIfSafe(token, path);
if (fullPath == string.Empty){
throw new Exception("File path has to be relative to the plugin folder.");
}
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[fullPath] = contents;
}
public string ReadFile(int token, string path, bool cache){
string fullPath = GetFullPathIfSafe(token, path);
if (fullPath.Length == 0){ if (fullPath.Length == 0){
throw new Exception("File path has to be relative to the plugin folder."); switch(folder){
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder.");
case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder.");
default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
}
} }
else{
return fullPath;
}
}
private string ReadFileUnsafe(string fullPath, bool readCached){
string cachedContents; string cachedContents;
if (cache && fileCache.TryGetValue(fullPath, out cachedContents)){ if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
return cachedContents; return cachedContents;
} }
@@ -59,25 +51,39 @@ namespace TweetDck.Plugins{
} }
} }
public void DeleteFile(int token, string path){ // Public methods
string fullPath = GetFullPathIfSafe(token, path);
public void WriteFile(int token, string path, string contents){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[fullPath] = contents;
}
public string ReadFile(int token, string path, bool cache){
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
}
public void DeleteFile(int token, string path){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
if (fullPath.Length == 0){
throw new Exception("File path has to be relative to the plugin folder.");
}
fileCache.Remove(fullPath); fileCache.Remove(fullPath);
File.Delete(fullPath); File.Delete(fullPath);
} }
public bool CheckFileExists(int token, string path){ public bool CheckFileExists(int token, string path){
string fullPath = GetFullPathIfSafe(token, path); return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
}
if (fullPath.Length == 0){ public string ReadFileRoot(int token, string path){
throw new Exception("File path has to be relative to the plugin folder."); return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
} }
return File.Exists(fullPath); public bool CheckFileExistsRoot(int token, string path){
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
} }
} }
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using CefSharp; using CefSharp;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
using TweetDck.Resources; using TweetDck.Resources;
@@ -34,6 +35,10 @@ namespace TweetDck.Plugins{
this.Bridge = new PluginBridge(this); this.Bridge = new PluginBridge(this);
} }
public bool IsPluginInstalled(string identifier){
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
}
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){ public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
return plugins.Where(plugin => plugin.Group == group); return plugins.Where(plugin => plugin.Group == group);
} }
@@ -103,6 +108,10 @@ namespace TweetDck.Plugins{
} }
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
string error; string error;
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error); Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);

View File

@@ -1,4 +1,5 @@
using System.Text; using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
static class PluginScriptGenerator{ static class PluginScriptGenerator{

View File

@@ -23,8 +23,8 @@ namespace TweetDck{
public const string BrowserSubprocess = BrandName+".Browser.exe"; public const string BrowserSubprocess = BrandName+".Browser.exe";
public const string VersionTag = "1.4.2"; public const string VersionTag = "1.5";
public const string VersionFull = "1.4.2.0"; public const string VersionFull = "1.5.0.0";
public static readonly Version Version = new Version(VersionTag); public static readonly Version Version = new Version(VersionTag);
@@ -35,6 +35,7 @@ namespace TweetDck{
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath(); public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6)); public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg"); public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt"); private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
@@ -56,8 +57,8 @@ namespace TweetDck{
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
if (!WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData)){ if (!WindowsUtils.CheckFolderPermission(StoragePath, FileSystemRights.WriteData)){
MessageBox.Show(BrandName+" does not have write permissions to the program folder. If it is installed in Program Files, please run it as Administrator.", "Administrator Required", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return; return;
} }
@@ -120,6 +121,7 @@ namespace TweetDck{
Locale = Args.GetValue("-locale", "en"), Locale = Args.GetValue("-locale", "en"),
CachePath = StoragePath, CachePath = StoragePath,
BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe", BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe",
LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
#if !DEBUG #if !DEBUG
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
#endif #endif
@@ -147,7 +149,9 @@ namespace TweetDck{
if (mainForm.UpdateInstallerPath != null){ if (mainForm.UpdateInstallerPath != null){
ExitCleanup(); ExitCleanup();
Process.Start(mainForm.UpdateInstallerPath, "/SP- /SILENT /NOICONS /CLOSEAPPLICATIONS"); bool runElevated = !IsPortable || !WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData);
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\""+(IsPortable ? " /PORTABLE=1" : ""), runElevated); // ProgramPath has a trailing backslash
Application.Exit(); Application.Exit();
} }
} }

View File

@@ -50,7 +50,9 @@ namespace TweetDck{
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
}; };
btnOpenLog.Click += (sender, args) => Process.Start(logFile); btnOpenLog.Click += (sender, args) => {
using(Process.Start(logFile)){}
};
form.AddActionControl(btnOpenLog); form.AddActionControl(btnOpenLog);

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex chylex
[version] [version]
1.1 1.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -14,8 +14,25 @@ enabled(){
if (configuration.useAdvancedSelector){ if (configuration.useAdvancedSelector){
if (configuration.customSelector){ if (configuration.customSelector){
var column = TD.controller.columnManager.get(data.element.closest("section.column").attr("data-column")); if (configuration.customSelector.toString().startsWith("function (column){")){
query = configuration.customSelector(column); $TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
return;
}
var section = data.element.closest("section.column");
var column = TD.controller.columnManager.get(section.attr("data-column"));
var header = $("h1.column-title", section);
var columnTitle = header.children(".column-head-title").text();
var columnAccount = header.children(".attribution").text();
try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
}catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return;
}
} }
else{ else{
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing"); $TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");

View File

@@ -34,42 +34,43 @@
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'. * The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior. * If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
* *
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object, open your browser, nagivate to TweetDeck,
* log in, and run the following code in your browser console, which will return an object containing all of the column objects mapped to their IDs:
* TD.controller.columnManager.getAll()
*
* The example below shows how to extract the column type, title, and account from the object.
* Column type is prefixed with col_, and may be one of the following:
* *
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list, * col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity, * col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown * col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
* *
* Some of these appear to be unused (for example, Home columns are 'col_timeline' instead of 'col_home'). * If you want to see your current column types, run this in your browser console:
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType());
* *
* If you want to see your column types, run this in your browser console: *
* Object.values(TD.controller.columnManager.getAll()).forEach(obj => console.log(obj.getColumnType())); * The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
* * some contain specific information (for example, Search columns contain the search query).
* You can also get the jQuery column object using: $("section.column[data-column='"+column.ui.state.columnKey+"']") *
*
* The 'account' parameter is the account name displayed next to the column title (including the @).
* This parameter is empty for some columns (such as Messages, or Notifications for all accounts) or can
* contain other text (for example, the Scheduled column contains the string 'All accounts').
*
*
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
* run the following code in your browser console, which will return an array containing all of your
* current column objects in order:
* TD.controller.columnManager.getAllOrdered()
* *
*/ */
useAdvancedSelector: false, useAdvancedSelector: false,
/*customSelector: function(column){ /*customSelector: function(type, title, account, column){
var titleObj = $(column.getTitleHTML()); if (type === "col_search" && title === "TweetDuck"){
var columnType = column.getColumnType(); // col_timeline
var columnTitle = titleObj.siblings(".column-head-title").text(); // Home
var columnAccount = titleObj.siblings(".attribution").text(); // @chylexmc
if (columnType === "col_search" && columnTitle === "TweetDuck"){
// This is a search column that looks for 'TweetDuck' in the tweets, // This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account // search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead. // so this forces the @TryTweetDuck account to be used instead.
return "@TryTweetDuck"; return "@TryTweetDuck";
} }
else if (columnType === "col_timeline" && columnAccount === "@chylexcz"){ else if (type === "col_timeline" && account === "@chylexcz"){
// This is a Home column of my test account @chylexcz, // This is a Home column of my test account @chylexcz,
// but I want to reply to tweets from my official account. // but I want to reply to tweets from my official account.
return "@chylexmc"; return "@chylexmc";

View File

@@ -114,7 +114,7 @@
var account = embedded[0].getElementsByClassName("account-link"); var account = embedded[0].getElementsByClassName("account-link");
if (account.length === 0)return; if (account.length === 0)return;
$TD.setNotificationTweetEmbedded(account[0].getAttribute("href")+"/status/"+tweetId); $TD.setNotificationQuotedTweet(account[0].getAttribute("href")+"/status/"+tweetId);
})(); })();
// //

View File

@@ -8,8 +8,8 @@
$TDP.checkFileExists(token, fileNameUser).then(exists => { $TDP.checkFileExists(token, fileNameUser).then(exists => {
var fileName = exists ? fileNameUser : fileNameDefault; var fileName = exists ? fileNameUser : fileNameDefault;
$TDP.readFile(token, fileName, true).then(contents => { (exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
var obj; var obj;
try{ try{

View File

@@ -206,12 +206,13 @@
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs"> <Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
<DependentUpon>PluginListFlowLayout.cs</DependentUpon> <DependentUpon>PluginListFlowLayout.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Plugins\Enums\PluginFolder.cs" />
<Compile Include="Plugins\Plugin.cs" /> <Compile Include="Plugins\Plugin.cs" />
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" /> <Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
<Compile Include="Plugins\PluginBridge.cs" /> <Compile Include="Plugins\PluginBridge.cs" />
<Compile Include="Plugins\PluginConfig.cs" /> <Compile Include="Plugins\PluginConfig.cs" />
<Compile Include="Plugins\PluginEnvironment.cs" /> <Compile Include="Plugins\Enums\PluginEnvironment.cs" />
<Compile Include="Plugins\PluginGroup.cs" /> <Compile Include="Plugins\Enums\PluginGroup.cs" />
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" /> <Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
<Compile Include="Plugins\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" /> <Compile Include="Plugins\PluginScriptGenerator.cs" />

View File

@@ -23,6 +23,7 @@ OutputBaseFilename={#MyAppName}
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico SetupIconFile=.\Resources\icon.ico
Uninstallable=TDIsUninstallable
UninstallDisplayName={#MyAppName} UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName} UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma Compression=lzma
@@ -41,11 +42,11 @@ Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignorever
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll" Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll"
[Icons] [Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run] [Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent
[InstallDelete] [InstallDelete]
Type: files; Name: "{app}\td-log.txt" Type: files; Name: "{app}\td-log.txt"
@@ -56,11 +57,15 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache" Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
[Code] [Code]
var UpdatePath: String;
function TDGetNetFrameworkVersion: Cardinal; forward; function TDGetNetFrameworkVersion: Cardinal; forward;
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. } { Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
if TDGetNetFrameworkVersion() >= 379893 then if TDGetNetFrameworkVersion() >= 379893 then
begin begin
Result := True; Result := True;
@@ -76,6 +81,21 @@ begin
Result := True; Result := True;
end; end;
{ Set the installation path if updating. }
procedure InitializeWizard();
begin
if (UpdatePath <> '') then
begin
WizardForm.DirEdit.Text := UpdatePath;
end;
end;
{ Skip the install path selection page if running from an update installer. }
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
end;
{ Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. } { Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. }
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var ProfileDataFolder: String; var ProfileDataFolder: String;
@@ -96,6 +116,12 @@ begin
end; end;
end; end;
{ Returns true if the installer should create uninstallation entries (i.e. not running in full update mode). }
function TDIsUninstallable: Boolean;
begin
Result := (UpdatePath = '')
end;
{ Return DWORD value containing the build version of .NET Framework. } { Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal; function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal; var FrameworkVersion: Cardinal;

92
bld/gen_port.iss Normal file
View File

@@ -0,0 +1,92 @@
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "TweetDuck"
#define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com"
#define MyAppExeName "TweetDuck.exe"
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
[Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
OutputBaseFilename={#MyAppName}.Portable
VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico
Uninstallable=no
UsePreviousAppDir=no
PrivilegesRequired=lowest
Compression=lzma
SolidCompression=yes
InternalCompressLevel=max
MinVersion=0,6.1
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll"
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
[Code]
function TDGetNetFrameworkVersion: Cardinal; forward;
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
function InitializeSetup: Boolean;
begin
if TDGetNetFrameworkVersion() >= 379893 then
begin
Result := True;
Exit;
end;
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin
Result := False;
Exit;
end;
Result := True;
end;
{ Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal;
begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
begin
Result := FrameworkVersion;
Exit;
end;
Result := 0;
end;
{ Create a 'makeportable' file if running in portable mode. }
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do
begin
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
begin
break;
end;
end;
end;
end;

View File

@@ -25,8 +25,10 @@ OutputBaseFilename={#MyAppName}.Update
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico SetupIconFile=.\Resources\icon.ico
Uninstallable=TDIsUninstallable
UninstallDisplayName={#MyAppName} UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName} UninstallDisplayIcon={app}\{#MyAppExeName}
PrivilegesRequired=lowest
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
InternalCompressLevel=max InternalCompressLevel=max
@@ -42,10 +44,10 @@ Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignorever
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat" Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat"
[Icons] [Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
[Run] [Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
[InstallDelete] [InstallDelete]
Type: files; Name: "{app}\*.xml" Type: files; Name: "{app}\*.xml"
@@ -54,6 +56,8 @@ Type: files; Name: "{app}\d3dcompiler_43.dll"
Type: files; Name: "{app}\devtools_resources.pak" Type: files; Name: "{app}\devtools_resources.pak"
Type: files; Name: "{app}\CefSharp.BrowserSubprocess.exe" Type: files; Name: "{app}\CefSharp.BrowserSubprocess.exe"
Type: files; Name: "{app}\td-log.txt" Type: files; Name: "{app}\td-log.txt"
Type: files; Name: "{app}\debug.log"
Type: files; Name: "{localappdata}\{#MyAppName}\ChromeDWriteFontCache"
[UninstallDelete] [UninstallDelete]
Type: files; Name: "{app}\*.*" Type: files; Name: "{app}\*.*"
@@ -63,22 +67,34 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache" Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
[Code] [Code]
function TDIsUninstallable: Boolean; forward;
function TDFindUpdatePath: String; forward;
function TDGetNetFrameworkVersion: Cardinal; forward; function TDGetNetFrameworkVersion: Cardinal; forward;
function TDGetAppVersionClean: String; forward; function TDGetAppVersionClean: String; forward;
function TDIsMatchingCEFVersion(InstallPath: String): Boolean; forward; function TDIsMatchingCEFVersion: Boolean; forward;
function TDPrepareFullDownloadIfNeeded: Boolean; forward;
procedure TDExecuteFullDownload; forward; procedure TDExecuteFullDownload; forward;
var IsPortable: Boolean;
var UpdatePath: String;
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. Prepare full download package if required. } { Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. Prepare full download package if required. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
if not TDPrepareFullDownloadIfNeeded() then IsPortable := ExpandConstant('{param:PORTABLE}') = '1'
UpdatePath := TDFindUpdatePath()
if UpdatePath = '' then
begin begin
MsgBox('{#MyAppName} installation could not be found on your system.', mbCriticalError, MB_OK); MsgBox('{#MyAppName} installation could not be found on your system.', mbCriticalError, MB_OK);
Result := False; Result := False;
Exit; Exit;
end; end;
if not TDIsMatchingCEFVersion() then
begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/{#MyAppName}.exe', ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
end;
if TDGetNetFrameworkVersion() >= 379893 then if TDGetNetFrameworkVersion() >= 379893 then
begin begin
Result := True; Result := True;
@@ -94,9 +110,11 @@ begin
Result := True; Result := True;
end; end;
{ Prepare download plugin if there are any files to download. } { Prepare download plugin if there are any files to download, and set the installation path. }
procedure InitializeWizard(); procedure InitializeWizard();
begin begin
WizardForm.DirEdit.Text := UpdatePath;
if idpFilesCount <> 0 then if idpFilesCount <> 0 then
begin begin
idpDownloadAfter(wpReady); idpDownloadAfter(wpReady);
@@ -128,13 +146,44 @@ procedure CurStepChanged(CurStep: TSetupStep);
begin begin
if CurStep = ssInstall then if CurStep = ssInstall then
begin begin
DeleteFile(ExpandConstant('{app}\unins000.dat'));
DeleteFile(ExpandConstant('{app}\unins000.exe'));
TDExecuteFullDownload(); TDExecuteFullDownload();
if TDIsUninstallable() then
begin
DeleteFile(ExpandConstant('{app}\unins000.dat'));
DeleteFile(ExpandConstant('{app}\unins000.exe'));
end;
end; end;
end; end;
{ Returns true if the installer should create uninstallation entries (i.e. not running in portable mode). }
function TDIsUninstallable: Boolean;
begin
Result := not IsPortable
end;
{ Returns a validated installation path (including trailing backslash) using the /UPDATEPATH parameter or installation info in registry. Returns empty string on failure. }
function TDFindUpdatePath: String;
var Path: String;
begin
Path := ExpandConstant('{param:UPDATEPATH}')
if (Path = '') and not IsPortable and not RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppID}}_is1', 'InstallLocation', Path) then
begin
Result := ''
Exit
end;
if not FileExists(Path+'{#MyAppExeName}') then
begin
Result := ''
Exit
end;
Result := Path
end;
{ Return DWORD value containing the build version of .NET Framework. } { Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal; function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal; var FrameworkVersion: Cardinal;
@@ -150,11 +199,11 @@ begin
end; end;
{ Return whether the version of the installed libcef.dll library matches internal one. } { Return whether the version of the installed libcef.dll library matches internal one. }
function TDIsMatchingCEFVersion(InstallPath: String): Boolean; function TDIsMatchingCEFVersion: Boolean;
var CEFVersion: String; var CEFVersion: String;
begin begin
Result := (GetVersionNumbersString(InstallPath+'\libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0)) Result := (GetVersionNumbersString(UpdatePath+'libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0))
end; end;
{ Return a cleaned up form of the app version string (removes all .0 suffixes). } { Return a cleaned up form of the app version string (removes all .0 suffixes). }
@@ -180,25 +229,6 @@ begin
Result := CleanVersion; Result := CleanVersion;
end; end;
{ Prepare the full package installer to run if the CEF version is not matching. Return False if the app is not installed. }
function TDPrepareFullDownloadIfNeeded: Boolean;
var InstallPath: String;
begin
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppID}}_is1', 'InstallLocation', InstallPath) then
begin
Result := False;
Exit;
end;
if not TDIsMatchingCEFVersion(InstallPath) then
begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/{#MyAppName}.exe', ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
end;
Result := True;
end;
{ Run the full package installer if downloaded. } { Run the full package installer if downloaded. }
procedure TDExecuteFullDownload; procedure TDExecuteFullDownload;
var InstallFile: String; var InstallFile: String;
@@ -212,10 +242,18 @@ begin
WizardForm.ProgressGauge.Style := npbstMarquee; WizardForm.ProgressGauge.Style := npbstMarquee;
try try
if not Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
if ResultCode <> 0 then
begin
DeleteFile(InstallFile);
Abort();
Exit;
end;
end else
begin
MsgBox('Could not run the full installer, please visit {#MyAppURL} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK); MsgBox('Could not run the full installer, please visit {#MyAppURL} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
DeleteFile(InstallFile);
DeleteFile(InstallFile);
Abort(); Abort();
Exit; Exit;
end; end;