diff --git a/DML.AppCore/DML.AppCore.csproj b/DML.AppCore/DML.AppCore.csproj new file mode 100644 index 0000000..6d9fa23 --- /dev/null +++ b/DML.AppCore/DML.AppCore.csproj @@ -0,0 +1,17 @@ + + + + netstandard1.4 + + + + + + + + + + + + + diff --git a/DML.Client/DML.Client.csproj b/DML.Client/DML.Client.csproj new file mode 100644 index 0000000..4b6dd43 --- /dev/null +++ b/DML.Client/DML.Client.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.4 + + + + + + + + + + + diff --git a/DML.Client/DMLClient.cs b/DML.Client/DMLClient.cs new file mode 100644 index 0000000..691f563 --- /dev/null +++ b/DML.Client/DMLClient.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; + +namespace DML.Client +{ + public static class DMLClient + { + public static DiscordSocketClient Client { get; set; } = new DiscordSocketClient(new DiscordSocketConfig(){DefaultRetryMode = RetryMode.RetryRatelimit|RetryMode.RetryTimeouts}); + + public static async Task Login(string token) + { + await Client.LoginAsync(TokenType.User, token); + await Client.StartAsync(); + await Task.Delay(1000); + + while (Client.LoginState == LoginState.LoggingIn || Client.ConnectionState == ConnectionState.Connecting) + { + // wait + } + + return Client.LoginState == LoginState.LoggedIn && Client.ConnectionState == ConnectionState.Connected; + } + } +} diff --git a/DML.Core/Classes/Job.cs b/DML.Core/Classes/Job.cs new file mode 100644 index 0000000..2f283e2 --- /dev/null +++ b/DML.Core/Classes/Job.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; + +namespace DML.Core.Classes +{ + public class Job + { + public int Id { get; set; } + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public double KnownTimestamp { get; set; } = 0; + private double StopTimestamp { get; set; } = 0; + private bool IsValid { get; set; } = true; + + internal void Store() + { + Debug("Storing job to database..."); + Trace("Getting jobs collection..."); + var jobDb = DML.Core.Core.Database.GetCollection("jobs"); + + Trace("Adding new value..."); + + if (jobDb.Find(x => x.ChannelId == ChannelId && x.GuildId == GuildId).Any()) + { + jobDb.Update(this); + } + else + { + jobDb.Insert(this); + } + } + + internal void Delete() + { + Debug("Deleting job from database..."); + Trace("Getting jobs collection..."); + var jobDb = DML.Core.Core.Database.GetCollection("jobs"); + + Trace("Deleting value..."); + jobDb.Delete(Id); + } + + private SocketGuild FindServerById(ulong id) + { + Trace($"Trying to find server by Id: {id}"); + return (from s in DML.Core.Core.Client.Guilds where s.Id == id select s).FirstOrDefault(); + } + + private SocketTextChannel FindChannelById(SocketGuild server, ulong id) + { + Trace($"Trying to find channel in {server} by id: {id}"); + return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); + } + + internal async Task> Scan() + { + Debug($"Starting scan of guild {GuildId} channel {ChannelId}..."); + var result = new List(); + + var limit = 100; + var lastId = ulong.MaxValue; + var isFirst = true; + var finished = false; + + var guild = FindServerById(GuildId); + var channel = FindChannelById(guild, ChannelId); + + if (Math.Abs(StopTimestamp) < 0.4) + StopTimestamp = KnownTimestamp; + Trace("Initialized scanning parameters."); + + while (!finished) + { + Trace("Entering scanning loop..."); + SocketMessage[] messages; + + Trace($"Downloading next {limit} messages..."); + if (isFirst) + { + messages = await channel.GetMessagesAsync(limit).ToArray() as SocketMessage[]; + } + else + { + messages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray() as SocketMessage[]; + } + Trace($"Downloaded {messages.Length} messages."); + + isFirst = false; + + foreach (var m in messages) + { + if (!IsValid) + return null; + + Debug($"Processing message {m.Id}"); + if (m.Id < lastId) + { + Trace($"Updating lastId ({lastId}) to {m.Id}"); + lastId = m.Id; + } + + if (SweetUtils.DateTimeToUnixTimeStamp(m.CreatedAt.UtcDateTime) <= StopTimestamp) + { + Debug("Found a message with a known timestamp...Stopping scan."); + finished = true; + continue; + } + + Trace($"Message {m.Id} has {m.Attachments.Count} attachments."); + if (m.Attachments.Count > 0) + { + result.Add(m); + DML.Core.Core.Scheduler.TotalAttachments++; + Trace($"Added message {m.Id}"); + } + Debug($"Finished message {m.Id}"); + + DML.Core.Core.Scheduler.MessagesScanned++; + } + + finished = finished || messages.Length < limit; + } + Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}."); + + Trace("Sorting messages..."); + result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime)); + + if (result.Count > 0) + { + Trace("Updating StopTimestamp for next scan..."); + StopTimestamp = SweetUtils.DateTimeToUnixTimeStamp(result[result.Count - 1].CreatedAt.UtcDateTime); + } + + Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}."); + + return result; + } + + internal void Stop() + { + IsValid = false; + } + + internal static IEnumerable RestoreJobs() + { + Debug("Restoring jobs..."); + Trace("Getting jobs collection..."); + var jobDb = DML.Core.Core.Database.GetCollection("jobs"); + + Trace("Creating new empty job list"); + return jobDb.FindAll(); + } + } +} diff --git a/DML.Core/Classes/JobScheduler.cs b/DML.Core/Classes/JobScheduler.cs new file mode 100644 index 0000000..21d3305 --- /dev/null +++ b/DML.Core/Classes/JobScheduler.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Discord.WebSocket; + +namespace DML.Core.Classes +{ + internal class JobScheduler + { + private ulong messageScanned = 0; + private ulong totalAttachments = 0; + private ulong attachmentsDownloaded = 0; + + private bool Run { get; set; } = false; + internal List JobList { get; set; } = new List(); + internal Dictionary> RunningJobs = new Dictionary>(); + internal int RunningThreads { get; set; } = 0; + + internal ulong MessagesScanned + { + get + { + lock (this) + { + return messageScanned; + } + } + set + { + lock (this) + { + messageScanned = value; + } + } + } + + internal ulong TotalAttachments + { + get + { + lock (this) + { + return totalAttachments; + } + } + set + { + lock (this) + { + totalAttachments = value; + } + } + } + + internal ulong AttachmentsDownloaded + { + get + { + lock (this) + { + return attachmentsDownloaded; + } + } + set + { + lock (this) + { + attachmentsDownloaded = value; + } + } + } + + internal ulong AttachmentsToDownload => TotalAttachments - AttachmentsDownloaded; + + internal void Stop() + { + Run = false; + } + + internal void Start() + { + Run = true; + + Task.Run(async () => + { + Info("Started JobScheduler..."); + while (Run) + { + Debug("Entering job list handler loop..."); + //foreach (var job in JobList) + for (var i = JobList.Count - 1; i >= 0; i--) + { + var job = JobList[i]; + Debug($"Checking job {job}"); + var hasJob = false; + + Trace("Locking scheduler..."); + lock (this) + { + Trace("Checking if job is already performed..."); + hasJob = RunningJobs.ContainsKey(job.Id); + } + Trace("Unlocked scheduler."); + + if (!hasJob) + { + Debug("Job is not performed yet...Performing job..."); + var queue = new Queue(); + + Trace("Locking scheduler..."); + lock (this) + { + Trace("Adding job to running jobs."); + RunningJobs.Add(job.Id, queue); + } + Trace("Unlocked scheduler."); + + Trace("Issuing job message scan..."); + var messages = await job.Scan(); + + if (messages == null) + continue; + + Trace($"Adding {messages.Count} messages to queue..."); + foreach (var msg in messages) + { + queue.Enqueue(msg); + } + Trace($"Added {queue.Count} messages to queue."); + + if (messages.Count != queue.Count) + Warn("Not all messages have been added into the queue."); + + var startedDownload = false; + + while (!startedDownload) + { + Debug("Entering loop to check thread availability"); + Trace("Locking scheduler..."); + lock (this) + { + Trace($"Checking thread limit. Running: {RunningThreads}, Max: {DML.Core.Core.Settings.ThreadLimit}"); + if (RunningThreads >= DML.Core.Core.Settings.ThreadLimit) + continue; + + RunningThreads++; + startedDownload = true; + } + Trace("Unlocked scheduler."); + } + + Trace("Start downloading job async."); + Task.Run(() => WorkQueue(job.Id)); // do not await to work parallel + } + } + } + }); + } + + private void WorkQueue(int jobId) + { + try + { + Debug("Beginning job download..."); + Trace("Finding job..."); + var job = (from j in JobList where j.Id == jobId select j).FirstOrDefault(); + + if (job == null) + { + Warn($"Associating job not found! JobId: {jobId}"); + return; + } + Trace("Found job."); + + Queue queue; + Trace("Locking scheduler..."); + lock (this) + { + Trace("Finiding queue..."); + if (!RunningJobs.TryGetValue(jobId, out queue)) + { + Warn($"Queue for job {jobId} not found!"); + return; + } + Trace("Queue found."); + } + Trace("Unlocked scheduler."); + + Debug($"Messages to process for job {jobId}: {queue.Count}"); + while (queue.Count > 0) + { + Trace("Locking scheduler..."); + lock (this) + { + Trace("Checking if still a job..."); + if (!RunningJobs.ContainsKey(jobId)) + { + Warn($"Queue for job {jobId} not found!"); + return; + } + Trace("Continue working..."); + } + Trace("Unlocked scheduler."); + + Trace("Dequeueing message..."); + var message = queue.Dequeue(); + + Debug($"Attachments for message {message.Id}: {message.Attachments.Count}"); + foreach (var a in message.Attachments) + { + try + { + var fileName = Path.Combine(DML.Core.Core.Settings.OperatingFolder, DML.Core.Core.Settings.FileNameScheme); + + Trace("Replacing filename placeholders..."); + + var extensionRequired = !fileName.EndsWith("%name%"); + + var serverName = "unknown"; + + var socketTextChannel = message.Channel as SocketTextChannel; + if (socketTextChannel != null) + { + serverName = socketTextChannel.Guild.Name.Replace(":", "").Replace("/", "") + .Replace("\\", ""); + } + + fileName = + fileName.Replace("%guild%", serverName) + .Replace("%channel%", message.Channel.Name) + .Replace("%timestamp%", SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime).ToString()) + .Replace("%name%", a.Filename) + .Replace("%id%", a.Id.ToString()); + + if (extensionRequired) + fileName += Path.GetExtension(a.Filename); + + Trace($"Detemined file name: {fileName}."); + + + if (File.Exists(fileName) && new FileInfo(fileName).Length == a.Size) + { + Debug($"{fileName} already existing with its estimated size. Skipping..."); + continue; + } + + Trace("Determining directory..."); + var fileDirectory = Path.GetDirectoryName(fileName); + + if (!Directory.Exists(fileDirectory)) + { + Info($"Directory {fileDirectory} does not exist. Creating directory..."); + Directory.CreateDirectory(fileDirectory); + Debug("Created directory."); + } + + var wc = new WebClient(); + Debug($"Starting downloading of attachment {a.Id}..."); + + wc.DownloadFile(new Uri(a.Url), fileName); + Debug($"Downloaded attachment {a.Id}."); + + Trace("Updating known timestamp for job..."); + job.KnownTimestamp = SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime); + job.Store(); + } + finally + { + AttachmentsDownloaded++; + } + } + } + } + finally + { + Trace("Locking scheduler..."); + lock (this) + { + Trace($"Removing {jobId} from running jobs..."); + RunningJobs.Remove(jobId); + Trace("Decreasing thread count..."); + RunningThreads--; + } + Trace("Unlocked scheduler."); + } + } + } +} diff --git a/DML.Core/Classes/Settings.cs b/DML.Core/Classes/Settings.cs new file mode 100644 index 0000000..0466c97 --- /dev/null +++ b/DML.Core/Classes/Settings.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; + +namespace DML.Core.Classes +{ + internal class Settings + { + public int Id { get; } = 1; // using always unique ID + public string Email { get; set; } + public string Password { get; set; } + public string LoginToken { get; set; } + public bool UseUserData { get; set; } = false; + public bool SavePassword { get; set; } = false; + public LogLevel ApplicactionLogLevel { get; set; } = LogLevel.Info | LogLevel.Warn | LogLevel.Error; + public string OperatingFolder { get; set; } + public string FileNameScheme { get; set; } = @"%guild%\%channel%\%id%"; + public bool SkipExistingFiles { get; set; } = true; + public int ThreadLimit { get; set; } = 50; + + internal void Store() + { + Trace("Getting settings collection..."); + var settingsDB = DML.Core.Core.Database.GetCollection("settings"); + + Debug("Storing settings to database..."); + + if (settingsDB.Exists(_setting => _setting.Id == Id)) + { + Trace("Updating existing value..."); + settingsDB.Update(this); + } + else + { + Trace("Adding new value..."); + settingsDB.Insert(this); + } + } + } +} diff --git a/DML.Core/Core.cs b/DML.Core/Core.cs new file mode 100644 index 0000000..ffd7e14 --- /dev/null +++ b/DML.Core/Core.cs @@ -0,0 +1,260 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Net; +using Discord.WebSocket; +using DML.Core.Classes; + +namespace DML.Core +{ + public static class Core + { + internal static DiscordSocketClient Client { get; set; } + internal static LiteDatabase Database { get; set; } + internal static Settings Settings { get; set; } + internal static JobScheduler Scheduler { get; } = new JobScheduler(); + + internal static string DataDirectory + => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader"); + + public static async Task Run(string[] paramStrings) + { + try + { + var splash = new FrmInternalSplash(); + splash.Show(); + System.Windows.Forms.Application.DoEvents(); + + Info("Starting up Discord Media Loader application..."); + var useTrace = false; +#if DEBUG + //temporary add debug log level if debugging... + GlobalLogLevel |= LogLevel.Debug; + Debug("Running in debug configuartion. Added log level debug."); +#endif + + Debug($"Parameters: {string.Join(", ", paramStrings)}"); + if (paramStrings.Contains("--trace") || paramStrings.Contains("-t")) + { + useTrace = true; + GlobalLogLevel |= LogLevel.Trace; + Trace("Trace parameter found. Added log level trace."); + } + + Debug($"Application data folder: {DataDirectory}"); + + Trace("Checking application data folder..."); + if (!Directory.Exists(DataDirectory)) + { + Debug("Creating application data folder..."); + Directory.CreateDirectory(DataDirectory); + Trace("Creating application data folder."); + } + + Trace("Initializing profile optimizations..."); + ProfileOptimization.SetProfileRoot(System.Windows.Forms.Application.UserAppDataPath); + ProfileOptimization.StartProfile("profile.opt"); + Trace("Finished initializing profile optimizations."); + + Trace("Trying to identify log memory..."); + var logMemory = DefaultLogMemory as ArchivableConsoleLogMemory; + if (logMemory != null) + { + var logFolder = Path.Combine(DataDirectory, "logs"); + if (!Directory.Exists(logFolder)) + { + Debug("Creating log folder..."); + Directory.CreateDirectory(logFolder); + Trace("Created log folder."); + } + + + var logFile = Path.Combine(logFolder, + SweetUtils.LegalizeFilename($"{DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern)}.log.zip")); + + Trace($"Setting log file: {logFile}"); + logMemory.AutoArchiveOnDispose = true; + logMemory.ArchiveFile = logFile; + } + + Debug("Loading database..."); + Database = new LiteDatabase(Path.Combine(DataDirectory, "config.db")); + Database.Log.Logging += (message) => Trace($"LiteDB: {message}"); + + Debug("Loading settings collection out of database..."); + var settingsDB = Database.GetCollection("settings"); + if (settingsDB.Count() > 1) + { + Warn("Found more than one setting. Loading first one..."); + } + Settings = settingsDB.FindAll().FirstOrDefault(); + if (Settings == null) + { + Warn("Settings not found. Creating new one. This is normal on first start up..."); + Settings = new Settings(); + Settings.Store(); + } + + Debug("Loading jobs collection out of database..."); + Scheduler.JobList = Job.RestoreJobs().ToList(); + + Info("Loaded settings."); + Debug( + $"Settings: Email: {Settings.Email}, password: {(string.IsNullOrEmpty(Settings.Password) ? "not set" : "is set")}, use username: {Settings.UseUserData}, loginToken: {Settings.LoginToken}"); + + Trace("Updating log level..."); + GlobalLogLevel = Settings.ApplicactionLogLevel; +#if DEBUG + //temporary add debug log level if debugging... + GlobalLogLevel |= LogLevel.Debug; + Debug("Running in debug configuartion. Added log level debug."); +#endif + if (useTrace) + { + GlobalLogLevel |= LogLevel.Trace; + Trace("Creating application data folder."); + } + + Debug("Creating discord client..."); + + Client = new DiscordSocketClient(); + Client.Log += (arg) => + { + var logMessage = $"DiscordClient: {arg.Message}"; + switch (arg.Severity) + { + case LogSeverity.Verbose: + Trace(logMessage); + break; + case LogSeverity.Debug: + Trace(logMessage); + break; + case LogSeverity.Info: + Info(logMessage); + break; + case LogSeverity.Warning: + Warn(logMessage); + break; + case LogSeverity.Error: + Error(logMessage); + break; + } + + return Task.CompletedTask; + }; + + + Info("Trying to log into discord..."); + var abort = false; + + Client.Connected += Client_Connected; + + while (Client.LoginState != LoginState.LoggedIn && !abort) + { + Debug(Client.ConnectionState.ToString()); + Debug(Client.LoginState.ToString()); + + Trace("Entering login loop."); + + try + { + if (Client.ConnectionState == ConnectionState.Connecting) + continue; + + if (!string.IsNullOrEmpty(Settings.LoginToken)) + { + Debug("Trying to login with last known token..."); + await Client.LoginAsync(TokenType.User, Settings.LoginToken); + await Task.Delay(1000); + } + + } + catch (HttpException ex) + { + Warn($"Login seems to have failed or gone wrong: {ex.GetType().Name} - {ex.Message}"); + } + + if (Client.LoginState == LoginState.LoggedOut) + { + Settings.Password = string.Empty; + Debug("Showing dialog for username and password..."); + var loginDlg = new LoginDialog(); + loginDlg.ShowDialog(); + Trace("Dialog closed."); + } + } + + Debug("Start checking for invalid jobs..."); + + //Client + + while (Client.Guilds.Count==0) + { + // wait until guilds are loaded + } + + for (var i = Scheduler.JobList.Count - 1; i >= 0; i--) + { + var job = Scheduler.JobList[i]; + var isError = false; + var guild = FindServerById(job.GuildId); + if (guild == null) + isError = true; + else + { + var channel = FindChannelById(guild, job.ChannelId); + if (channel == null) + isError = true; + } + + if (isError) + { + MessageBox.Show($"Invalid job for guild {job.GuildId}, channel {job.ChannelId} found. Guild or channel may not exist any more. This job will be deleted...", "Invalid job", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + + Scheduler.JobList.Remove(job); + Scheduler.RunningJobs.Remove(job.Id); + job.Stop(); + job.Delete(); + } + } + + splash.Close(); + + Info("Starting scheduler..."); + Scheduler.Start(); + + System.Windows.Forms.Application.Run(new MainForm()); + + Info("Stopping scheduler..."); + Scheduler.Stop(); + } + catch (Exception ex) + { + Error($"{ex.Message} occured at: {ex.StackTrace}"); + } + } + + private static Task Client_Connected() + { + Debug("Connected"); + return Task.CompletedTask; + } + + //TODO: this is thrid time we implement this.....this has to be fixed!!! + private static SocketGuild FindServerById(ulong id) + { + Trace($"Trying to find server by Id: {id}"); + return (from s in Core.Client.Guilds where s.Id == id select s).FirstOrDefault(); + } + + private static SocketTextChannel FindChannelById(SocketGuild server, ulong id) + { + Trace($"Trying to find channel in {server} by id: {id}"); + return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); + } + } +} diff --git a/DML.Core/DML.Core.Old.csproj b/DML.Core/DML.Core.Old.csproj new file mode 100644 index 0000000..438b9a8 --- /dev/null +++ b/DML.Core/DML.Core.Old.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp1.1 + + + + + + + + + + + diff --git a/Discord Media Loader.Application/Classes/Core.cs b/Discord Media Loader.Application/Classes/Core.cs new file mode 100644 index 0000000..c740049 --- /dev/null +++ b/Discord Media Loader.Application/Classes/Core.cs @@ -0,0 +1,297 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime; +using System.Threading.Tasks; +using System.Windows.Forms; +using Discord; +using Discord.WebSocket; +using DML.AppCore.Classes; +using DML.Application.Dialogs; +using DML.Client; +using LiteDB; +using SharpRaven; +using SharpRaven.Data; +using SweetLib.Utils; +using SweetLib.Utils.Logger; +using SweetLib.Utils.Logger.Memory; +using Logger = SweetLib.Utils.Logger.Logger; + +namespace DML.Application.Classes +{ + public static class Core + { + //internal static DiscordSocketClient Client { get; set; } + internal static LiteDatabase Database { get; set; } + internal static Settings Settings { get; set; } + internal static JobScheduler Scheduler { get; } = new JobScheduler(); + internal static RavenClient Raven = new RavenClient("https://0de964231669473e9098b9f6cc1d6278:79d9f2eb24034de199b2a37cc058e0f2@sentry.io/257114"); + + internal static string DataDirectory + => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader"); + + public static async Task Run(string[] paramStrings) + { + try + { + var splash = new FrmInternalSplash(); + splash.Show(); + System.Windows.Forms.Application.DoEvents(); + + Logger.Info("Starting up Discord Media Loader application..."); + var useTrace = false; +#if DEBUG + //temporary add debug log level if debugging... + Logger.GlobalLogLevel |= LogLevel.Debug; + Logger.Debug("Running in debug configuartion. Added log level debug."); +#endif + + Logger.Debug($"Parameters: {string.Join(", ", paramStrings)}"); + if (paramStrings.Contains("--trace") || paramStrings.Contains("-t")) + { + useTrace = true; + Logger.GlobalLogLevel |= LogLevel.Trace; + Logger.Trace("Trace parameter found. Added log level trace."); + } + + Logger.Debug($"Application data folder: {DataDirectory}"); + + Logger.Trace("Checking application data folder..."); + if (!Directory.Exists(DataDirectory)) + { + Logger.Debug("Creating application data folder..."); + Directory.CreateDirectory(DataDirectory); + Logger.Trace("Creating application data folder."); + } + + Logger.Trace("Initializing profile optimizations..."); + ProfileOptimization.SetProfileRoot(System.Windows.Forms.Application.UserAppDataPath); + ProfileOptimization.StartProfile("profile.opt"); + Logger.Trace("Finished initializing profile optimizations."); + + Logger.Trace("Trying to identify log memory..."); + var logMemory = Logger.DefaultLogMemory as ArchivableConsoleLogMemory; + if (logMemory != null) + { + var logFolder = Path.Combine(DataDirectory, "logs"); + if (!Directory.Exists(logFolder)) + { + Logger.Debug("Creating log folder..."); + Directory.CreateDirectory(logFolder); + Logger.Trace("Created log folder."); + } + + + var logFile = Path.Combine(logFolder, + SweetUtils.LegalizeFilename($"{DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern)}.log.zip")); + + Logger.Trace($"Setting log file: {logFile}"); + logMemory.AutoArchiveOnDispose = true; + logMemory.ArchiveFile = logFile; + } + + Logger.Debug("Loading database..."); + Database = new LiteDatabase(Path.Combine(DataDirectory, "config.db")); + Database.Log.Logging += (message) => Logger.Trace($"LiteDB: {message}"); + + Logger.Debug("Loading settings collection out of database..."); + var settingsDB = Database.GetCollection("settings"); + if (settingsDB.Count() > 1) + { + Logger.Warn("Found more than one setting. Loading first one..."); + } + Settings = settingsDB.FindAll().FirstOrDefault(); + if (Settings == null) + { + Logger.Warn("Settings not found. Creating new one. This is normal on first start up..."); + Settings = new Settings(); + Settings.Store(); + } + + Logger.Debug("Loading jobs collection out of database..."); + Scheduler.JobList = Job.RestoreJobs().ToList(); + + Logger.Info("Loaded settings."); + Logger.Debug( + $"Settings: Email: {Settings.Email}, password: {(string.IsNullOrEmpty(Settings.Password) ? "not set" : "is set")}, use username: {Settings.UseUserData}, loginToken: {Settings.LoginToken}"); + + Logger.Trace("Updating log level..."); + Logger.GlobalLogLevel = Settings.ApplicactionLogLevel; +#if DEBUG + //temporary add debug log level if debugging... + Logger.GlobalLogLevel |= LogLevel.Debug; + Logger.Debug("Running in debug configuartion. Added log level debug."); +#endif + if (useTrace) + { + Logger.GlobalLogLevel |= LogLevel.Trace; + Logger.Trace("Creating application data folder."); + } + + Logger.Debug("Creating discord client..."); + + var config = new DiscordSocketConfig() + { + DefaultRetryMode = RetryMode.AlwaysRetry, + }; + + //Client = new DiscordSocketClient(config); + DMLClient.Client.Log += (arg) => + { + var logMessage = $"DiscordClient: {arg.Message}"; + switch (arg.Severity) + { + case LogSeverity.Verbose: + Logger.Trace(logMessage); + break; + case LogSeverity.Debug: + Logger.Trace(logMessage); + break; + case LogSeverity.Info: + Logger.Info(logMessage); + break; + case LogSeverity.Warning: + Logger.Warn(logMessage); + break; + case LogSeverity.Error: + Logger.Error(logMessage); + break; + } + + return Task.CompletedTask; + }; + + + Logger.Info("Trying to log into discord..."); + var abort = false; + + DMLClient.Client.Connected += Client_Connected; + + var loggedIn = false; + + while (!loggedIn) + { + if (!string.IsNullOrEmpty(Settings.LoginToken)) + { + Logger.Debug("Trying to login with last known token..."); + loggedIn= await DMLClient.Login(Settings.LoginToken); + } + + if (!loggedIn) + { + Logger.Debug("Showing dialog for username and password..."); + var loginDlg = new LoginDialog(); + loginDlg.ShowDialog(); + } + } + + /*while ((Client.LoginState != LoginState.LoggedIn || Client.ConnectionState!=ConnectionState.Connected) && !abort) + { + Logger.Debug(Client.ConnectionState.ToString()); + Logger.Debug(Client.LoginState.ToString()); + + Logger.Trace("Entering login loop."); + + try + { + if (Client.ConnectionState == ConnectionState.Connecting) + continue; + + if (!string.IsNullOrEmpty(Settings.LoginToken)) + { + Logger.Debug("Trying to login with last known token..."); + await Client.LoginAsync(TokenType.User, Settings.LoginToken); + await Client.StartAsync(); + await Task.Delay(1000); + } + + } + catch (HttpException ex) + { + Logger.Warn($"Login seems to have failed or gone wrong: {ex.GetType().Name} - {ex.Message}"); + } + + if (Client.LoginState == LoginState.LoggedOut) + { + Settings.Password = string.Empty; + Logger.Debug("Showing dialog for username and password..."); + var loginDlg = new LoginDialog(); + loginDlg.ShowDialog(); + Logger.Trace("Dialog closed."); + } + }*/ + + Logger.Debug("Start checking for invalid jobs..."); + + //Client + + while (DMLClient.Client.Guilds.Count == 0) + { + // wait until guilds are loaded + } + + for (var i = Scheduler.JobList.Count - 1; i >= 0; i--) + { + var job = Scheduler.JobList[i]; + var isError = false; + var guild = FindServerById(job.GuildId); + if (guild == null) + isError = true; + else + { + var channel = FindChannelById(guild, job.ChannelId); + if (channel == null) + isError = true; + } + + if (isError) + { + MessageBox.Show($"Invalid job for guild {job.GuildId}, channel {job.ChannelId} found. Guild or channel may not exist any more. This job will be deleted...", "Invalid job", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + + Scheduler.JobList.Remove(job); + Scheduler.RunningJobs.Remove(job.Id); + job.Stop(); + job.Delete(); + } + } + + splash.Close(); + + Logger.Info("Starting scheduler..."); + Scheduler.Start(); + + System.Windows.Forms.Application.Run(new MainForm()); + + Logger.Info("Stopping scheduler..."); + Scheduler.Stop(); + } + catch (Exception ex) + { + Logger.Error($"{ex.Message} occured at: {ex.StackTrace}"); + if (MessageBox.Show($"An error occured while running Discord Media Loader:\n{ex.GetType().Name}: {ex.Message}\n\nDo you aggree to sending the error report to the creator of the tool?", "Discord Media Loader", MessageBoxButtons.YesNo) == DialogResult.Yes) + Raven.Capture(new SentryEvent(ex)); + } + } + + private static Task Client_Connected() + { + Logger.Debug("Connected"); + return Task.CompletedTask; + } + + //TODO: this is thrid time we implement this.....this has to be fixed!!! + private static SocketGuild FindServerById(ulong id) + { + Logger.Trace($"Trying to find server by Id: {id}"); + return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); + } + + private static SocketTextChannel FindChannelById(SocketGuild server, ulong id) + { + Logger.Trace($"Trying to find channel in {server} by id: {id}"); + return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); + } + } +} diff --git a/Discord Media Loader.Application/Classes/Job.cs b/Discord Media Loader.Application/Classes/Job.cs new file mode 100644 index 0000000..0a6d57c --- /dev/null +++ b/Discord Media Loader.Application/Classes/Job.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using DML.Application.Classes; +using DML.Client; +using SweetLib.Utils; +using static SweetLib.Utils.Logger.Logger; + +namespace DML.AppCore.Classes +{ + public class Job + { + public int Id { get; set; } + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public double KnownTimestamp { get; set; } = 0; + private double StopTimestamp { get; set; } = 0; + private bool IsValid { get; set; } = true; + + internal void Store() + { + Debug("Storing job to database..."); + Trace("Getting jobs collection..."); + var jobDb = Core.Database.GetCollection("jobs"); + + Trace("Adding new value..."); + + if (jobDb.Find(x => x.ChannelId == ChannelId && x.GuildId == GuildId).Any()) + { + jobDb.Update(this); + } + else + { + jobDb.Insert(this); + } + } + + public void Delete() + { + Debug("Deleting job from database..."); + Trace("Getting jobs collection..."); + var jobDb = Core.Database.GetCollection("jobs"); + + Trace("Deleting value..."); + jobDb.Delete(Id); + } + + private SocketGuild FindServerById(ulong id) + { + Trace($"Trying to find server by Id: {id}"); + return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); + } + + private SocketTextChannel FindChannelById(SocketGuild server, ulong id) + { + Trace($"Trying to find channel in {server} by id: {id}"); + return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); + } + + internal async Task> Scan() + { + Debug($"Starting scan of guild {GuildId} channel {ChannelId}..."); + var result = new List(); + + var limit = 100; + var lastId = ulong.MaxValue; + var isFirst = true; + var finished = false; + + var guild = FindServerById(GuildId); + var channel = FindChannelById(guild, ChannelId); + + Debug("Checking channel access"); + if (!channel.Users.Contains(channel.Guild.CurrentUser)) + { + Info("Skipping channel without access"); + return result; + } + + if (Math.Abs(StopTimestamp) < 0.4) + StopTimestamp = KnownTimestamp; + Trace("Initialized scanning parameters."); + + while (!finished) + { + Trace("Entering scanning loop..."); + var messages = new List(); + + Trace($"Downloading next {limit} messages..."); + if (isFirst) + { + //messages = await channel.GetMessagesAsync(limit).ToArray() as SocketMessage[]; + var realMessages = await channel.GetMessagesAsync(limit).ToArray(); + + foreach (var realMessageArray in realMessages) + { + foreach (var realMessage in realMessageArray) + { + messages.Add(realMessage); + } + } + } + else + { + var realMessages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray(); + + foreach (var realMessageArray in realMessages) + { + foreach (var realMessage in realMessageArray) + { + messages.Add(realMessage); + } + } + + //messages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray() as SocketMessage[]; + } + Trace($"Downloaded {messages.Count} messages."); + + isFirst = false; + + foreach (var m in messages) + { + if (!IsValid) + return null; + + Debug($"Processing message {m.Id}"); + if (m.Id < lastId) + { + Trace($"Updating lastId ({lastId}) to {m.Id}"); + lastId = m.Id; + } + + if (SweetUtils.DateTimeToUnixTimeStamp(m.CreatedAt.UtcDateTime) <= StopTimestamp) + { + Debug("Found a message with a known timestamp...Stopping scan."); + finished = true; + continue; + } + + Trace($"Message {m.Id} has {m.Attachments.Count} attachments."); + if (m.Attachments.Count > 0) + { + result.Add(m); + Core.Scheduler.TotalAttachments += (ulong)m.Attachments.Count; + Trace($"Added message {m.Id}"); + } + Debug($"Finished message {m.Id}"); + + Core.Scheduler.MessagesScanned++; + } + + finished = finished || messages.Count < limit; + } + Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}."); + + Trace("Sorting messages..."); + result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime)); + + if (result.Count > 0) + { + Trace("Updating StopTimestamp for next scan..."); + StopTimestamp = SweetUtils.DateTimeToUnixTimeStamp(result[result.Count - 1].CreatedAt.UtcDateTime); + } + + Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}."); + + return result; + } + + public void Stop() + { + IsValid = false; + } + + public static IEnumerable RestoreJobs() + { + Debug("Restoring jobs..."); + Trace("Getting jobs collection..."); + var jobDb = Core.Database.GetCollection("jobs"); + + Trace("Creating new empty job list"); + return jobDb.FindAll(); + } + } +} diff --git a/Discord Media Loader.Application/Classes/JobScheduler.cs b/Discord Media Loader.Application/Classes/JobScheduler.cs new file mode 100644 index 0000000..156a131 --- /dev/null +++ b/Discord Media Loader.Application/Classes/JobScheduler.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using DML.Application.Classes; +using SweetLib.Utils; +using SweetLib.Utils.Logger; + +namespace DML.AppCore.Classes +{ + public class JobScheduler + { + private ulong messageScanned = 0; + private ulong totalAttachments = 0; + private ulong attachmentsDownloaded = 0; + + private bool Run { get; set; } = false; + public List JobList { get; set; } = new List(); + public Dictionary> RunningJobs = new Dictionary>(); + internal int RunningThreads { get; set; } = 0; + + internal ulong MessagesScanned + { + get + { + lock (this) + { + return messageScanned; + } + } + set + { + lock (this) + { + messageScanned = value; + } + } + } + + internal ulong TotalAttachments + { + get + { + lock (this) + { + return totalAttachments; + } + } + set + { + lock (this) + { + totalAttachments = value; + } + } + } + + internal ulong AttachmentsDownloaded + { + get + { + lock (this) + { + return attachmentsDownloaded; + } + } + set + { + lock (this) + { + attachmentsDownloaded = value; + } + } + } + + public void Stop() + { + Run = false; + } + + public void Start() + { + Run = true; + + Task.Run(async () => + { + Logger.Info("Started JobScheduler..."); + while (Run) + { + Logger.Debug("Entering job list handler loop..."); + //foreach (var job in JobList) + for (var i = JobList.Count - 1; i >= 0; i--) + { + var job = JobList[i]; + Logger.Debug($"Checking job {job}"); + var hasJob = false; + + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace("Checking if job is already performed..."); + hasJob = RunningJobs.ContainsKey(job.Id); + } + Logger.Trace("Unlocked scheduler."); + + if (!hasJob) + { + Logger.Debug("Job is not performed yet...Performing job..."); + var queue = new Queue(); + + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace("Adding job to running jobs."); + RunningJobs.Add(job.Id, queue); + } + Logger.Trace("Unlocked scheduler."); + + Logger.Trace("Issuing job message scan..."); + var messages = await job.Scan(); + + if (messages == null) + continue; + + Logger.Trace($"Adding {messages.Count} messages to queue..."); + foreach (var msg in messages) + { + queue.Enqueue(msg); + } + Logger.Trace($"Added {queue.Count} messages to queue."); + + if (messages.Count != queue.Count) + Logger.Warn("Not all messages have been added into the queue."); + + var startedDownload = false; + + while (!startedDownload) + { + Logger.Debug("Entering loop to check thread availability"); + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace($"Checking thread limit. Running: {RunningThreads}, Max: {Core.Settings.ThreadLimit}"); + if (RunningThreads >= Core.Settings.ThreadLimit) + continue; + + RunningThreads++; + startedDownload = true; + } + Logger.Trace("Unlocked scheduler."); + } + + Logger.Trace("Start downloading job async."); + Task.Run(() => WorkQueue(job.Id)); // do not await to work parallel + } + } + } + }); + } + + private void WorkQueue(int jobId) + { + try + { + Logger.Debug("Beginning job download..."); + Logger.Trace("Finding job..."); + var job = (from j in JobList where j.Id == jobId select j).FirstOrDefault(); + + if (job == null) + { + Logger.Warn($"Associating job not found! JobId: {jobId}"); + return; + } + Logger.Trace("Found job."); + + Queue queue; + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace("Finiding queue..."); + if (!RunningJobs.TryGetValue(jobId, out queue)) + { + Logger.Warn($"Queue for job {jobId} not found!"); + return; + } + Logger.Trace("Queue found."); + } + Logger.Trace("Unlocked scheduler."); + + Logger.Debug($"Messages to process for job {jobId}: {queue.Count}"); + while (queue.Count > 0) + { + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace("Checking if still a job..."); + if (!RunningJobs.ContainsKey(jobId)) + { + Logger.Warn($"Queue for job {jobId} not found!"); + return; + } + Logger.Trace("Continue working..."); + } + Logger.Trace("Unlocked scheduler."); + + Logger.Trace("Dequeueing message..."); + var message = queue.Dequeue(); + + Logger.Debug($"Attachments for message {message.Id}: {message.Attachments.Count}"); + foreach (var a in message.Attachments) + { + try + { + var fileName = Path.Combine(Core.Settings.OperatingFolder, Core.Settings.FileNameScheme); + + Logger.Trace("Replacing filename placeholders..."); + + var extensionRequired = !fileName.EndsWith("%name%"); + + var serverName = "unknown"; + + var socketTextChannel = message.Channel as SocketTextChannel; + if (socketTextChannel != null) + { + serverName = socketTextChannel.Guild.Name; + serverName = Path.GetInvalidFileNameChars().Aggregate(serverName, (current, c) => current.Replace(c, ' ')); + } + + var channelName = message.Channel.Name; + channelName = Path.GetInvalidFileNameChars().Aggregate(channelName, (current, c) => current.Replace(c, ' ')); + + fileName = + fileName.Replace("%guild%", serverName) + .Replace("%channel%", channelName) + .Replace("%timestamp%", SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime).ToString()) + .Replace("%name%", a.Filename) + .Replace("%id%", a.Id.ToString()); + + if (extensionRequired) + fileName += Path.GetExtension(a.Filename); + + Logger.Trace($"Detemined file name: {fileName}."); + + + if (File.Exists(fileName) && new FileInfo(fileName).Length == a.Size) + { + Logger.Debug($"{fileName} already existing with its estimated size. Skipping..."); + continue; + } + + Logger.Trace("Determining directory..."); + var fileDirectory = Path.GetDirectoryName(fileName); + + if (!Directory.Exists(fileDirectory)) + { + Logger.Info($"Directory {fileDirectory} does not exist. Creating directory..."); + Directory.CreateDirectory(fileDirectory); + Logger.Debug("Created directory."); + } + + var wc = new WebClient(); + Logger.Debug($"Starting downloading of attachment {a.Id}..."); + + wc.DownloadFile(new Uri(a.Url), fileName); + Logger.Debug($"Downloaded attachment {a.Id}."); + + Logger.Trace("Updating known timestamp for job..."); + job.KnownTimestamp = SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime); + job.Store(); + } + finally + { + AttachmentsDownloaded++; + } + } + } + } + finally + { + Logger.Trace("Locking scheduler..."); + lock (this) + { + Logger.Trace($"Removing {jobId} from running jobs..."); + RunningJobs.Remove(jobId); + Logger.Trace("Decreasing thread count..."); + RunningThreads--; + } + Logger.Trace("Unlocked scheduler."); + } + } + } +} diff --git a/Discord Media Loader.Application/Classes/Settings.cs b/Discord Media Loader.Application/Classes/Settings.cs new file mode 100644 index 0000000..a9544ee --- /dev/null +++ b/Discord Media Loader.Application/Classes/Settings.cs @@ -0,0 +1,38 @@ +using SweetLib.Utils.Logger; + +namespace DML.Application.Classes +{ + public class Settings + { + public int Id { get; } = 1; // using always unique ID + public string Email { get; set; } + public string Password { get; set; } + public string LoginToken { get; set; } + public bool UseUserData { get; set; } = false; + public bool SavePassword { get; set; } = false; + public LogLevel ApplicactionLogLevel { get; set; } = LogLevel.Info | LogLevel.Warn | LogLevel.Error; + public string OperatingFolder { get; set; } + public string FileNameScheme { get; set; } = @"%guild%\%channel%\%id%"; + public bool SkipExistingFiles { get; set; } = true; + public int ThreadLimit { get; set; } = 50; + + public void Store() + { + Logger.Trace("Getting settings collection..."); + var settingsDB = Core.Database.GetCollection("settings"); + + Logger.Debug("Storing settings to database..."); + + if (settingsDB.Exists(_setting => _setting.Id == Id)) + { + Logger.Trace("Updating existing value..."); + settingsDB.Update(this); + } + else + { + Logger.Trace("Adding new value..."); + settingsDB.Insert(this); + } + } + } +} diff --git a/Discord Media Loader.Application/DML.Application.csproj b/Discord Media Loader.Application/DML.Application.csproj new file mode 100644 index 0000000..02bf1f5 --- /dev/null +++ b/Discord Media Loader.Application/DML.Application.csproj @@ -0,0 +1,230 @@ + + + + + Debug + AnyCPU + {C130DE6A-3237-42B5-BE9F-783D1CD104C6} + Library + Properties + DML.Application + Discord Media Loader.Application + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Discord.Net.Commands.1.0.2\lib\netstandard1.1\Discord.Net.Commands.dll + + + ..\packages\Discord.Net.Core.1.0.2\lib\net45\Discord.Net.Core.dll + + + ..\packages\Discord.Net.Rest.1.0.2\lib\net45\Discord.Net.Rest.dll + + + ..\packages\Discord.Net.Rpc.1.0.2\lib\net45\Discord.Net.Rpc.dll + + + ..\packages\Discord.Net.Webhook.1.0.2\lib\netstandard1.1\Discord.Net.Webhook.dll + + + ..\packages\Discord.Net.WebSocket.1.0.2\lib\net45\Discord.Net.WebSocket.dll + + + ..\packages\LiteDB.3.1.0\lib\net35\LiteDB.dll + True + + + ..\packages\Microsoft.Extensions.DependencyInjection.1.1.1\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll + True + + + ..\packages\SharpRaven.2.2.0\lib\net45\SharpRaven.dll + + + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + + ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + + + ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + + ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + + ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + + + + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + True + + + + + + + + Form + + + LoginDialog.cs + + + Form + + + FrmInternalSplash.cs + + + Form + + + MainForm.cs + + + + True + True + Resources.resx + + + + + + LoginDialog.cs + + + FrmInternalSplash.cs + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + {02c1f8ef-32f2-4e77-a36d-79129402af37} + SweetLib + + + {045eb4a1-34e7-47e0-867e-e10c40505095} + DML.Client + + + + + + + + \ No newline at end of file diff --git a/Discord Media Loader.Application/Dialogs/LoginDialog.Designer.cs b/Discord Media Loader.Application/Dialogs/LoginDialog.Designer.cs new file mode 100644 index 0000000..0a48921 --- /dev/null +++ b/Discord Media Loader.Application/Dialogs/LoginDialog.Designer.cs @@ -0,0 +1,129 @@ +namespace DML.Application.Dialogs +{ + partial class LoginDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginDialog)); + this.pnlButtons = new System.Windows.Forms.Panel(); + this.btnAbort = new System.Windows.Forms.Button(); + this.btnOk = new System.Windows.Forms.Button(); + this.lbHowToToken = new System.Windows.Forms.Label(); + this.edToken = new System.Windows.Forms.TextBox(); + this.lbToken = new System.Windows.Forms.Label(); + this.pnlButtons.SuspendLayout(); + this.SuspendLayout(); + // + // pnlButtons + // + this.pnlButtons.BackColor = System.Drawing.SystemColors.ButtonShadow; + this.pnlButtons.Controls.Add(this.btnAbort); + this.pnlButtons.Controls.Add(this.btnOk); + this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.pnlButtons.Location = new System.Drawing.Point(0, 168); + this.pnlButtons.Name = "pnlButtons"; + this.pnlButtons.Size = new System.Drawing.Size(426, 51); + this.pnlButtons.TabIndex = 0; + // + // btnAbort + // + this.btnAbort.Location = new System.Drawing.Point(348, 16); + this.btnAbort.Name = "btnAbort"; + this.btnAbort.Size = new System.Drawing.Size(75, 23); + this.btnAbort.TabIndex = 1; + this.btnAbort.Text = "&Abort"; + this.btnAbort.UseVisualStyleBackColor = true; + this.btnAbort.Click += new System.EventHandler(this.btnAbort_Click); + // + // btnOk + // + this.btnOk.Location = new System.Drawing.Point(267, 16); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 0; + this.btnOk.Text = "&Ok"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.btnOk_Click); + // + // lbHowToToken + // + this.lbHowToToken.Location = new System.Drawing.Point(7, 58); + this.lbHowToToken.Name = "lbHowToToken"; + this.lbHowToToken.Size = new System.Drawing.Size(412, 87); + this.lbHowToToken.TabIndex = 5; + this.lbHowToToken.Text = resources.GetString("lbHowToToken.Text"); + // + // edToken + // + this.edToken.Location = new System.Drawing.Point(79, 12); + this.edToken.Name = "edToken"; + this.edToken.Size = new System.Drawing.Size(335, 20); + this.edToken.TabIndex = 4; + // + // lbToken + // + this.lbToken.AutoSize = true; + this.lbToken.Location = new System.Drawing.Point(7, 15); + this.lbToken.Name = "lbToken"; + this.lbToken.Size = new System.Drawing.Size(66, 13); + this.lbToken.TabIndex = 3; + this.lbToken.Text = "Login token:"; + // + // LoginDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(426, 219); + this.Controls.Add(this.lbHowToToken); + this.Controls.Add(this.edToken); + this.Controls.Add(this.lbToken); + this.Controls.Add(this.pnlButtons); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "LoginDialog"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "LoginForm"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LoginDialog_FormClosing); + this.Shown += new System.EventHandler(this.LoginDialog_Shown); + this.pnlButtons.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Panel pnlButtons; + private System.Windows.Forms.Button btnAbort; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Label lbHowToToken; + private System.Windows.Forms.TextBox edToken; + private System.Windows.Forms.Label lbToken; + } +} \ No newline at end of file diff --git a/Discord Media Loader.Application/Dialogs/LoginDialog.cs b/Discord Media Loader.Application/Dialogs/LoginDialog.cs new file mode 100644 index 0000000..a995bb1 --- /dev/null +++ b/Discord Media Loader.Application/Dialogs/LoginDialog.cs @@ -0,0 +1,45 @@ +using System; +using System.Windows.Forms; +using DML.Application.Classes; +using static SweetLib.Utils.Logger.Logger; + +namespace DML.Application.Dialogs +{ + public partial class LoginDialog : Form + { + public LoginDialog() + { + InitializeComponent(); + } + + private void LoginDialog_Shown(object sender, EventArgs e) + { + Trace("Login dialog shown."); + edToken.Text = Core.Settings.LoginToken; + } + + private void LoginDialog_FormClosing(object sender, FormClosingEventArgs e) + { + Trace($"Closing login dialog. Result: {DialogResult}"); + if (DialogResult != DialogResult.OK) + return; + + Debug("Adjusting login settings..."); + Core.Settings.LoginToken = edToken.Text; + + Core.Settings.Store(); + } + + private void btnOk_Click(object sender, EventArgs e) + { + Trace("btnOk pressed."); + DialogResult = DialogResult.OK; + } + + private void btnAbort_Click(object sender, EventArgs e) + { + Trace("btnAbort pressed."); + DialogResult = DialogResult.Abort; + } + } +} diff --git a/Discord Media Loader.Application/Dialogs/LoginDialog.resx b/Discord Media Loader.Application/Dialogs/LoginDialog.resx new file mode 100644 index 0000000..0ffe195 --- /dev/null +++ b/Discord Media Loader.Application/Dialogs/LoginDialog.resx @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + To find your login token please start Discord and press Ctrl + Shift + I shortcut. A browser inspector will open. +Navigate into the "Application" tab and select "Local Storage" / "https://discordapp.com". If the right site keeps blank use "Session Storage" instead. +Look for the token key and copy its value without the quotation marks. + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8b////VP///3////+m////zP// + /+T////1//////////r////u////3f///8z///+m////f////1T///8b////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////M////5D////b//////// + //////////////////////////////////////////////////////////////////////////////// + /9v///+Q////M////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///w3///90////1v// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////W////dP///w3///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A2K4AANiuAADYrgAA2K4AANiuAADfswAA////EP// + /4D////t////////////////////////////////////////////////9/zq/7XiQ/+340n/w+ho/9Pu + kf/m9b//+Pzt///////////////////////////////////////////////////////////t////gf// + /xCa2AAAmtgAAJrYAACa2AAAmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ANiuAADYrgAA2K4AANiu + AAD///8B////Yf///+v//////////////////////////////////////////////////////////+z4 + z/+a2AD/mtgA/5rYAP+a2AD/mtgA/6TcGf+65VL/3fKo//n98P////////////////////////////// + ///////////////////////r////Yf///wGa2AAAmtgAAJrYAACa2AAA////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wDYrgAA2K4AANiuAAD///8k////xP////////////////////////////////////////////////// + ///////////////////h87P/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+n3SD/zOt+//X7 + 5v/////////////////////////////////////////////////////E////I5rYAACa2AAAmtgAAP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A2K4AANiuAAD///9V////8f////////////////////////////////// + ////////////////////////////////////////0+6Q/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+k3Bn/0e2K//v+9v////////////////////////////////////////////// + //H///9VmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wH///+M//////////////////////// + /////////////////////////////////////////////////////////////8bpbv+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+u4DP/5/bD//////////////////// + /////////////////////////////////4z///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wb///+l//////// + //////////////////////////////////////////////////////////////////////////////// + //+55E//mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5zZ + Bv/P7Ib/////////////////////////////////////////////////////pf///wb///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3//////////////////////////////////////////////////////////////////////// + ///////////////////+//3/rd8x/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/7zlV//5/fH///////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wH///+k//////////////////////////////////////////////////////// + ////////////////////////////////////////+f3v/6PcF/+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/seE7//X75v////////////// + /////////////////////////////////6T///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///+K//////////////////////////////////////// + /////////////////////////////////////////////////////////////+751f+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+s3y7/9Pvi////////////////////////////////////////////////i////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///9V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///s+M//rd8w/6XcHf+c2Qb/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/6zfLv/1++f///////////////////////////////////////// + //////9V////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8j////8P// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////v/6/fL/8PnZ/97yrP/E6Gv/qd4l/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/suE8//v99P////////////// + ////////////////////////////8P///yP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8B////xP////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////v9 + 9f/e8qz/s+JA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP/A51/////////////////////////////////////////////////E////Af///wD///8A////AP// + /wD///8A////AP///wD///8A////Yf////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////f867/rN8t/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/9jwnP////////////////////////////////////////////// + /2H///8A////AP///wD///8A////AP///wD///8A////EP///+r///////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////r98//I6nX/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+h2xL/8/rh//////////////////// + ///////////////////////q////Ev///wD///8A////AP///wD///8A////AP///3////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////9/yrf+h2xL/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8Dn + X////////////////////////////////////////////////4D///8A////AP///wD///8A////AP// + /w3////u//////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////7PjQ/6reKP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+c2QX/7vjT///////////////////////////////////////////u////Df// + /wD///8A////AP///wD///90//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////0++L/qd4n/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8PoaP////////////////////////////// + /////////////////3T///8A////AP///wD///8B////2/////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////+360P+k4g3/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+j2xb/+Pzu//// + ///////////////////////////////////////b////Af///wD///8A////O/////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////wNCY/53dAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/9/zr////////////////////////////////////////////////zv///8A////AP// + /5D///////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////f39/+Puin/n+AA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP/F6W3///////////////////////////////////////// + //////+Q////AP///wD////Y//////////////////////////////////////////////////////// + /////////////////////////////+3t7f+3t7f/jIyM/3Nzc/9oaGj/a2tr/35+fv+dnZ3/xsbG//Hx + 8f////////////////////////////////////////////////////////////////////////////// + ////////iZNy/5zbAP+b2QD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/s+JA//////////////////// + ////////////////////////////2f///wD///8d//////////////////////////////////////// + ////////////////////////////////////////9fTx/46Pk/9BQUH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv9ERET/fn5+/8rKyv////////////////////////////////////////////// + /////////////////////////////4iHjP98qA3/n+AA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6ne + J/////////////////////////////////////////////////////8e////Vv////////////////// + ////////////////////////////////////////////////////////+fDL/45/Qv8kJjH/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Xl5e/7e3t//+/v7///////// + //////////////////////////////////////////////////9vbXX/T2Ig/57fAP+a2QD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+l3Bz/////////////////////////////////////////////////////Vv// + /4T/////////////////////////////////////////////////////////////////////8+ax/+O5 + D/92ZBv/JCYx/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/YGBg/8jIyP/////////////////////////////////////////////////09PT/QkJE/zEz + Lf+Mwgb/nd0A/5rYAP+a2AD/mtgA/5rYAP+a2AD/pdwb//////////////////////////////////// + /////////////////4T///+t//////////////////////////////////////////////////////// + ////////8+ez/9mwB//htQD/dWMb/yQnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/dHR0/97e3v////////////////////////////// + ////////r6+v/y4uLv8nIzH/bZET/6HiAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6neJv////////////// + //////////////////////////////////////+t////zP////////////////////////////////// + ////////////////////////+PHS/9qyDv/YrgD/4rUA/3lmGf8kJjH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Ozs7/1ZWVv90dHT/jo6O/5mZmf/Hx8f//v7+//// + ////////////////////////7+/v/0dHR/8uLi7/KCUw/1BkH/+f4AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+z4T7/////////////////////////////////////////////////////zP///+X///////////// + /////////////////////////////////////////v35/+C/N//YrgD/2K4A/+O2AP+Dbhf/IyYx/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/1VVVf+QkJD/xsbG/+rq6v////////////// + ////////////////////////////////////////+vr6/2tra/8uLi7/Li4u/yspL/86Qij/ltEC/5va + AP+a2AD/mtgA/5rYAP+a2AD/x+ly//////////////////////////////////////////////////// + /+D////y//////////////////////////////////////////////////////Dfm//YrgD/2K4A/9iu + AP/itQD/k3oT/yUnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/cHBw/8bGxv/8/Pz///////// + ////////////////////////////////////////////////////////7+/v/3BwcP8uLi7/Li4u/y4u + Lv8tLC7/Ly8u/4e7CP+e3gD/mtgA/5rYAP+a2AD/mtgA/+X1vf////////////////////////////// + ///////////////////////s//////////////////////////////////////////////////////79 + +f/euyr/2K4A/9iuAP/YrgD/4LQA/6iKDf8oKTD/Li4u/y4uLv8uLi7/Li4u/y4uLv9QUFD/xMTE//// + ////////////////////////////////////////////////////////////////////////w8PD/1BQ + UP8uLi7/Li4u/y4uLv8uLi7/Li4u/yknMP95pA7/n+EA/5rYAP+a2AD/mtgA/6neJ//8/vj///////// + ////////////////////////////////////////////+f////////////////////////////////// + ///////////////////168D/2K4A/9iuAP/YrgD/2K4A/92yAP+/mwf/MTAt/ywtLv8uLi7/Li4u/y4u + Lv9wcHD/8PDw//////////////////////////////////////////////////////////////////r6 + +v/ExMT/bm5u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/a44U/6HiAP+a2AD/mtgA/5rY + AP/W75j///////////////////////////////////////////////////////////n////y//////// + ////////////////////////////////////////6tR3/9iuAP/YrgD/2K4A/9iuAP/arwD/06oB/0VA + J/8pKi//Li4u/y4uLv9ra2v/+vr6//////////////////////////////////////////////////39 + /f/n5+f/wsLC/4yMjP9SUlL/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/JyMx/2F+ + GP+h4wD/mtgA/5rYAP+v4DX//P74//////////////////////////////////////////////////// + ///////s////5f///////////////////////////////////////////////+LDQv/YrgD/2K4A/9iu + AP/YrgD/2K4A/+C0AP9oWh7/JCcx/y4uLv9GRkb/7u7u/////////////////////////////v7+/8fH + x/+VlZX/iYmJ/3BwcP9TU1P/OTk5/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/ygkMf9bdRv/oOEA/5rYAP+g2g7/7fjR//////////////////////////////////// + ////////////////////////////4P///8z///////////////////////////////////////////// + ///euyj/2K4A/9iuAP/YrgD/2K4A/9iuAP/itQD/ln0S/yQnMf8uLi7/ra2t//////////////////// + ///////////////////i4uL/e3t7/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/WXEc/6DhAP+d2Qf/4fOy//////////////////// + /////////////////////////////////////////////////8z///+t//////////////////////// + ////////////////////////3Lcc/9iuAP/YrgD/2K4A/9iuAP/YrgD/3LEA/8agBf82NCz/Pj9C//T0 + 9P/////////////////////////////////////////////////Ozs7/ZmZm/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/KCQx/1pzHP+m4w//4POx//// + //////////////////////////////////////////////////////////////////////+t////hP// + /////////////////////////////////////////////9y3Hf/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/gtAD/Z1ke/2lrcv////////////////////////////////////////////////////////////// + //++vr7/ZGRk/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/ygk + Mf93i0X/7PnL//////////////////////////////////////////////////////////////////// + ////////////hP///1T////////////////////////////////////////////////euyr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/4LQA/66OC/+Dg4b///////////////////////////////////////// + ///////////////////////////////////Pz8//hISE/0hISP8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/0JCQv+RkJX/9PXy//////////////////////////////////////////////////// + /////////////////////////////////1b///8c//////////////////////////////////////// + ////////48RG/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9mvAP/dsgD/kopq//////////////////// + ///////////////////////////////////////////////////////////////////09PT/y8vL/6Ki + ov+Dg4P/cXFx/21tbf94eHj/kJCQ/7q6uv/v7+////////////////////////////////////////// + //////////////////////////////////////////////////////8d////AP///9f///////////// + /////////////////////////////+rTdP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4LQA/72f + Iv/09PT///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////Y////AP// + /wD///+P///////////////////////////////////////////06Lb/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/esgD/zsKO//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////kP///wD///8A////Ov///////////////////////////////////////////fvx/9y2 + Gv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+G3Cf/578j///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////zr///8A////AP///wH////a//////////////////////// + ///////////////////p0nD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/3bgh//r0 + 3P////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////9v///8B////AP///wD///8A////dP// + ////////////////////////////////////////+fPZ/9mxCP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/duCH/9+7I//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////90////AP// + /wD///8A////AP///wz////t///////////////////////////////////////////oz2n/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9qyDf/x4qT///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////Df///wD///8A////AP///wD///8A////f/////////////////////////////////// + ////////+/fm/9u1Fv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+nQ + bP/8+u7///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////f////wD///8A////AP///wD///8A////AP///xD////q//////// + ///////////////////////////////////x4qX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/3ron//Hipf///v3///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////6v///xD///8A////AP///wD///8A////AP// + /wD///8A////YP///////////////////////////////////////////////+jPaf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4cA5//HipP/9+/H///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////2H///8A////AP// + /wD///8A////AP///wD///8A////AP///wH////D//////////////////////////////////////// + ///+/fj/4sRE/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/3bgf/+fNY//x4qX/+PHT//367//+/vv////+//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /8P///8B////AP///wD///8A////AP///wD///8A////AP///wD///8A////Iv////D///////////// + //////////////////////////////z57f/gvzX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9ivA//cthj/37wr//fuyv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////D///8i////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///9U////////////////////////////////////////////////+/jo/+C/Nf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/48NH///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////9V////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///4j///////////////////////////////////////////// + ///8+ev/4sND/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/btBP//Pns//////////////////////////////////////////////////// + //////////////////////////////////////////////////////+J////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////pP////////////////// + //////////////////////////////789v/nzGD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/37wt///+/P////////////////////////////// + //////////////////////////////////////////////////////////////////////+k////Af// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3/////////////////////////////////////////////////////+7bj//asQr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+PGSv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////Bv///6X///////////////////////////////////////////// + ////////9+7K/+HBO//YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/oz2n///////////////////////////////////////////////////////////////////////// + //////////////////////+l////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////jP////////////////// + ///////////////////////////////////+/fr/7t2T/924Hv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/7dqL//////////////////////////////////////////////////// + //////////////////////////////////////+M////Af///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A2K4AANiu + AAD///9V////8f/////////////////////////////////////////////////////8+ev/7dmH/966 + Jv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A//Plrv////////////////////////////// + //////////////////////////////////////////////////H///9VmtgAAJrYAAD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////ANiuAADYrgAA2K4AAP///yT////E//////////////////////////////////////// + ///////////////////9/PT/8+av/+bLWv/duB7/2K4A/9iuAP/YrgD/2K4A/9iuAP/37sv///////// + /////////////////////////////////////////////////////////////////8T///8jmtgAAJrY + AACa2AAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDYrgAA2K4AANiuAADYrgAA////Af///2D////r//////// + /////////////////////////////////////////////////////////fvx//btxv/v3pf/6dJw/+TH + T//jxEX/+/jo////////////////////////////////////////////////////////////////6/// + /2D///8BmtgAAJrYAACa2AAAmtgAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////EP///3/////t//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////f////xD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////Df///3T////V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////9X///90////Df///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /zP///+N////1/////////////////////////////////////////////////////////////////// + ///////////////////////X////jf///zP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8b////VP///33///+l////y////+P////y//////// + ///////y////4////8v///+l////ff///1T///8b////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////4Af///////wAAD//////8AAAD/////+AAAAB/////wAAAAD////8AAAAAD////gAAAAA + H///4AAAAAAH///AAAAAAAP//4AAAAAAAf//AAAAAAAA//4AAAAAAAB//gAAAAAAAH/8AAAAAAAAP/gA + AAAAAAAf+AAAAAAAAB/wAAAAAAAAD/AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB8AAAAAAAAADwAAAAAAA + AAOAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABwAAAAAAAAAPAAAAAAAAAA+AA + AAAAAAAH4AAAAAAAAAfwAAAAAAAAD/AAAAAAAAAP+AAAAAAAAB/4AAAAAAAAH/wAAAAAAAA//gAAAAAA + AH/+AAAAAAAAf/8AAAAAAAD//4AAAAAAAf//wAAAAAAD///gAAAAAAf///gAAAAAH////AAAAAA///// + AAAAAP/////AAAAD//////AAAA///////AAAP///////4Af///8= + + + \ No newline at end of file diff --git a/Discord Media Loader.Application/FrmInternalSplash.Designer.cs b/Discord Media Loader.Application/FrmInternalSplash.Designer.cs new file mode 100644 index 0000000..23e8d02 --- /dev/null +++ b/Discord Media Loader.Application/FrmInternalSplash.Designer.cs @@ -0,0 +1,80 @@ +namespace DML.Application +{ + partial class FrmInternalSplash + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmInternalSplash)); + this.lblName = new System.Windows.Forms.Label(); + this.pbLogo = new System.Windows.Forms.PictureBox(); + ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).BeginInit(); + this.SuspendLayout(); + // + // lblName + // + this.lblName.Font = new System.Drawing.Font("Microsoft Sans Serif", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblName.Location = new System.Drawing.Point(11, 129); + this.lblName.Name = "lblName"; + this.lblName.Size = new System.Drawing.Size(379, 35); + this.lblName.TabIndex = 3; + this.lblName.Text = "Discord Media Loader"; + this.lblName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // pbLogo + // + this.pbLogo.Image = global::DML.Application.Properties.Resources.Serraniel_Logo4_NO_BG; + this.pbLogo.Location = new System.Drawing.Point(14, 10); + this.pbLogo.Name = "pbLogo"; + this.pbLogo.Size = new System.Drawing.Size(376, 116); + this.pbLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pbLogo.TabIndex = 2; + this.pbLogo.TabStop = false; + // + // FrmInternalSplash + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.White; + this.ClientSize = new System.Drawing.Size(400, 175); + this.Controls.Add(this.lblName); + this.Controls.Add(this.pbLogo); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "FrmInternalSplash"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Discord Media Loader"; + ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label lblName; + private System.Windows.Forms.PictureBox pbLogo; + } +} \ No newline at end of file diff --git a/Discord Media Loader.Application/FrmInternalSplash.cs b/Discord Media Loader.Application/FrmInternalSplash.cs new file mode 100644 index 0000000..22bcad6 --- /dev/null +++ b/Discord Media Loader.Application/FrmInternalSplash.cs @@ -0,0 +1,12 @@ +using System.Windows.Forms; + +namespace DML.Application +{ + public partial class FrmInternalSplash : Form + { + public FrmInternalSplash() + { + InitializeComponent(); + } + } +} diff --git a/Discord Media Loader/LoginForm.resx b/Discord Media Loader.Application/FrmInternalSplash.resx similarity index 100% rename from Discord Media Loader/LoginForm.resx rename to Discord Media Loader.Application/FrmInternalSplash.resx diff --git a/Discord Media Loader.Application/MainForm.Designer.cs b/Discord Media Loader.Application/MainForm.Designer.cs new file mode 100644 index 0000000..4b17af7 --- /dev/null +++ b/Discord Media Loader.Application/MainForm.Designer.cs @@ -0,0 +1,395 @@ +namespace DML.Application +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.pnlSettings = new System.Windows.Forms.GroupBox(); + this.lbThreadLimit = new System.Windows.Forms.Label(); + this.edThreadLimit = new System.Windows.Forms.NumericUpDown(); + this.cbSkipExisting = new System.Windows.Forms.CheckBox(); + this.edNameScheme = new System.Windows.Forms.TextBox(); + this.lbNameScheme = new System.Windows.Forms.Label(); + this.btnSearchFolders = new System.Windows.Forms.Button(); + this.edOperatingFolder = new System.Windows.Forms.TextBox(); + this.lbOperatingFolder = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.btnAddJob = new System.Windows.Forms.Button(); + this.cbChannel = new System.Windows.Forms.ComboBox(); + this.lbChannel = new System.Windows.Forms.Label(); + this.cbGuild = new System.Windows.Forms.ComboBox(); + this.lbGuild = new System.Windows.Forms.Label(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.pgbProgress = new System.Windows.Forms.ToolStripProgressBar(); + this.lbProgress = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVersionPlaceholder = new System.Windows.Forms.ToolStripStatusLabel(); + this.lbVersion = new System.Windows.Forms.ToolStripStatusLabel(); + this.btnDropDown = new System.Windows.Forms.ToolStripSplitButton(); + this.visitGithubToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.btnDelete = new System.Windows.Forms.Button(); + this.lbxJobs = new System.Windows.Forms.ListBox(); + this.tmrRefreshProgress = new System.Windows.Forms.Timer(this.components); + this.pnlSettings.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.edThreadLimit)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.statusStrip.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // pnlSettings + // + this.pnlSettings.Controls.Add(this.lbThreadLimit); + this.pnlSettings.Controls.Add(this.edThreadLimit); + this.pnlSettings.Controls.Add(this.cbSkipExisting); + this.pnlSettings.Controls.Add(this.edNameScheme); + this.pnlSettings.Controls.Add(this.lbNameScheme); + this.pnlSettings.Controls.Add(this.btnSearchFolders); + this.pnlSettings.Controls.Add(this.edOperatingFolder); + this.pnlSettings.Controls.Add(this.lbOperatingFolder); + this.pnlSettings.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlSettings.Location = new System.Drawing.Point(0, 0); + this.pnlSettings.Name = "pnlSettings"; + this.pnlSettings.Size = new System.Drawing.Size(553, 93); + this.pnlSettings.TabIndex = 0; + this.pnlSettings.TabStop = false; + this.pnlSettings.Text = "Settings"; + // + // lbThreadLimit + // + this.lbThreadLimit.AutoSize = true; + this.lbThreadLimit.Location = new System.Drawing.Point(12, 67); + this.lbThreadLimit.Name = "lbThreadLimit"; + this.lbThreadLimit.Size = new System.Drawing.Size(64, 13); + this.lbThreadLimit.TabIndex = 6; + this.lbThreadLimit.Text = "Thread limit:"; + // + // edThreadLimit + // + this.edThreadLimit.Location = new System.Drawing.Point(113, 65); + this.edThreadLimit.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.edThreadLimit.Name = "edThreadLimit"; + this.edThreadLimit.Size = new System.Drawing.Size(120, 20); + this.edThreadLimit.TabIndex = 7; + this.edThreadLimit.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.edThreadLimit.ValueChanged += new System.EventHandler(this.DoSomethingChanged); + // + // cbSkipExisting + // + this.cbSkipExisting.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cbSkipExisting.AutoSize = true; + this.cbSkipExisting.Location = new System.Drawing.Point(447, 42); + this.cbSkipExisting.Name = "cbSkipExisting"; + this.cbSkipExisting.Size = new System.Drawing.Size(106, 17); + this.cbSkipExisting.TabIndex = 5; + this.cbSkipExisting.Text = "Skip existing files"; + this.cbSkipExisting.UseVisualStyleBackColor = true; + this.cbSkipExisting.TextChanged += new System.EventHandler(this.DoSomethingChanged); + // + // edNameScheme + // + this.edNameScheme.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.edNameScheme.Location = new System.Drawing.Point(113, 39); + this.edNameScheme.Name = "edNameScheme"; + this.edNameScheme.Size = new System.Drawing.Size(328, 20); + this.edNameScheme.TabIndex = 4; + this.edNameScheme.TextChanged += new System.EventHandler(this.DoSomethingChanged); + // + // lbNameScheme + // + this.lbNameScheme.AutoSize = true; + this.lbNameScheme.Location = new System.Drawing.Point(12, 42); + this.lbNameScheme.Name = "lbNameScheme"; + this.lbNameScheme.Size = new System.Drawing.Size(95, 13); + this.lbNameScheme.TabIndex = 3; + this.lbNameScheme.Text = "File name scheme:"; + // + // btnSearchFolders + // + this.btnSearchFolders.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSearchFolders.Location = new System.Drawing.Point(522, 11); + this.btnSearchFolders.Name = "btnSearchFolders"; + this.btnSearchFolders.Size = new System.Drawing.Size(25, 23); + this.btnSearchFolders.TabIndex = 2; + this.btnSearchFolders.Text = "..."; + this.btnSearchFolders.UseVisualStyleBackColor = true; + this.btnSearchFolders.Click += new System.EventHandler(this.btnSearchFolders_Click); + // + // edOperatingFolder + // + this.edOperatingFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.edOperatingFolder.Location = new System.Drawing.Point(113, 13); + this.edOperatingFolder.Name = "edOperatingFolder"; + this.edOperatingFolder.Size = new System.Drawing.Size(403, 20); + this.edOperatingFolder.TabIndex = 1; + this.edOperatingFolder.TextChanged += new System.EventHandler(this.DoSomethingChanged); + // + // lbOperatingFolder + // + this.lbOperatingFolder.AutoSize = true; + this.lbOperatingFolder.Location = new System.Drawing.Point(12, 16); + this.lbOperatingFolder.Name = "lbOperatingFolder"; + this.lbOperatingFolder.Size = new System.Drawing.Size(85, 13); + this.lbOperatingFolder.TabIndex = 0; + this.lbOperatingFolder.Text = "Operating folder:"; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.btnAddJob); + this.groupBox1.Controls.Add(this.cbChannel); + this.groupBox1.Controls.Add(this.lbChannel); + this.groupBox1.Controls.Add(this.cbGuild); + this.groupBox1.Controls.Add(this.lbGuild); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Top; + this.groupBox1.Location = new System.Drawing.Point(0, 93); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(553, 57); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Add a job"; + // + // btnAddJob + // + this.btnAddJob.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAddJob.Location = new System.Drawing.Point(481, 19); + this.btnAddJob.Name = "btnAddJob"; + this.btnAddJob.Size = new System.Drawing.Size(66, 23); + this.btnAddJob.TabIndex = 4; + this.btnAddJob.Text = "&Add"; + this.btnAddJob.UseVisualStyleBackColor = true; + this.btnAddJob.Click += new System.EventHandler(this.btnAddJob_Click); + // + // cbChannel + // + this.cbChannel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cbChannel.FormattingEnabled = true; + this.cbChannel.Location = new System.Drawing.Point(294, 19); + this.cbChannel.Name = "cbChannel"; + this.cbChannel.Size = new System.Drawing.Size(181, 21); + this.cbChannel.TabIndex = 3; + // + // lbChannel + // + this.lbChannel.AutoSize = true; + this.lbChannel.Location = new System.Drawing.Point(239, 22); + this.lbChannel.Name = "lbChannel"; + this.lbChannel.Size = new System.Drawing.Size(49, 13); + this.lbChannel.TabIndex = 2; + this.lbChannel.Text = "Channel:"; + // + // cbGuild + // + this.cbGuild.FormattingEnabled = true; + this.cbGuild.Location = new System.Drawing.Point(52, 19); + this.cbGuild.Name = "cbGuild"; + this.cbGuild.Size = new System.Drawing.Size(181, 21); + this.cbGuild.TabIndex = 1; + this.cbGuild.SelectedIndexChanged += new System.EventHandler(this.cbGuild_SelectedIndexChanged); + // + // lbGuild + // + this.lbGuild.AutoSize = true; + this.lbGuild.Location = new System.Drawing.Point(12, 22); + this.lbGuild.Name = "lbGuild"; + this.lbGuild.Size = new System.Drawing.Size(34, 13); + this.lbGuild.TabIndex = 0; + this.lbGuild.Text = "Guild:"; + // + // statusStrip + // + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.pgbProgress, + this.lbProgress, + this.lblVersionPlaceholder, + this.lbVersion, + this.btnDropDown}); + this.statusStrip.Location = new System.Drawing.Point(0, 311); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(553, 22); + this.statusStrip.TabIndex = 2; + this.statusStrip.Text = "statusStrip1"; + // + // pgbProgress + // + this.pgbProgress.Name = "pgbProgress"; + this.pgbProgress.Size = new System.Drawing.Size(100, 16); + // + // lbProgress + // + this.lbProgress.Name = "lbProgress"; + this.lbProgress.Size = new System.Drawing.Size(0, 17); + // + // lblVersionPlaceholder + // + this.lblVersionPlaceholder.Name = "lblVersionPlaceholder"; + this.lblVersionPlaceholder.Size = new System.Drawing.Size(302, 17); + this.lblVersionPlaceholder.Spring = true; + // + // lbVersion + // + this.lbVersion.Name = "lbVersion"; + this.lbVersion.Size = new System.Drawing.Size(118, 17); + this.lbVersion.Text = "v https://github.com"; + // + // btnDropDown + // + this.btnDropDown.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnDropDown.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.visitGithubToolStripMenuItem, + this.aboutToolStripMenuItem}); + this.btnDropDown.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnDropDown.Name = "btnDropDown"; + this.btnDropDown.Size = new System.Drawing.Size(16, 20); + this.btnDropDown.Text = "Options"; + // + // visitGithubToolStripMenuItem + // + this.visitGithubToolStripMenuItem.Name = "visitGithubToolStripMenuItem"; + this.visitGithubToolStripMenuItem.Size = new System.Drawing.Size(135, 22); + this.visitGithubToolStripMenuItem.Text = "Visit Github"; + this.visitGithubToolStripMenuItem.Click += new System.EventHandler(this.visitGithubToolStripMenuItem_Click); + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(135, 22); + this.aboutToolStripMenuItem.Text = "About"; + this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.btnDelete); + this.groupBox2.Controls.Add(this.lbxJobs); + this.groupBox2.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox2.Location = new System.Drawing.Point(0, 150); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(553, 161); + this.groupBox2.TabIndex = 3; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Jobs"; + // + // btnDelete + // + this.btnDelete.Dock = System.Windows.Forms.DockStyle.Bottom; + this.btnDelete.Location = new System.Drawing.Point(3, 135); + this.btnDelete.Name = "btnDelete"; + this.btnDelete.Size = new System.Drawing.Size(547, 23); + this.btnDelete.TabIndex = 1; + this.btnDelete.Text = "Delete selected"; + this.btnDelete.UseVisualStyleBackColor = true; + this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click); + // + // lbxJobs + // + this.lbxJobs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lbxJobs.FormattingEnabled = true; + this.lbxJobs.Location = new System.Drawing.Point(6, 19); + this.lbxJobs.Name = "lbxJobs"; + this.lbxJobs.Size = new System.Drawing.Size(541, 108); + this.lbxJobs.TabIndex = 0; + // + // tmrRefreshProgress + // + this.tmrRefreshProgress.Enabled = true; + this.tmrRefreshProgress.Interval = 500; + this.tmrRefreshProgress.Tick += new System.EventHandler(this.tmrRefreshProgress_Tick); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(553, 333); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.statusStrip); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.pnlSettings); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(100, 75); + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Discord Medial Loader"; + this.Shown += new System.EventHandler(this.MainForm_Shown); + this.pnlSettings.ResumeLayout(false); + this.pnlSettings.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.edThreadLimit)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.GroupBox pnlSettings; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label lbThreadLimit; + private System.Windows.Forms.NumericUpDown edThreadLimit; + private System.Windows.Forms.CheckBox cbSkipExisting; + private System.Windows.Forms.TextBox edNameScheme; + private System.Windows.Forms.Label lbNameScheme; + private System.Windows.Forms.Button btnSearchFolders; + private System.Windows.Forms.TextBox edOperatingFolder; + private System.Windows.Forms.Label lbOperatingFolder; + private System.Windows.Forms.Button btnAddJob; + private System.Windows.Forms.ComboBox cbChannel; + private System.Windows.Forms.Label lbChannel; + private System.Windows.Forms.ComboBox cbGuild; + private System.Windows.Forms.Label lbGuild; + private System.Windows.Forms.Button btnDelete; + private System.Windows.Forms.ListBox lbxJobs; + private System.Windows.Forms.ToolStripProgressBar pgbProgress; + private System.Windows.Forms.ToolStripStatusLabel lbProgress; + private System.Windows.Forms.Timer tmrRefreshProgress; + private System.Windows.Forms.ToolStripStatusLabel lblVersionPlaceholder; + private System.Windows.Forms.ToolStripStatusLabel lbVersion; + private System.Windows.Forms.ToolStripSplitButton btnDropDown; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem visitGithubToolStripMenuItem; + } +} \ No newline at end of file diff --git a/Discord Media Loader.Application/MainForm.cs b/Discord Media Loader.Application/MainForm.cs new file mode 100644 index 0000000..20b91ba --- /dev/null +++ b/Discord Media Loader.Application/MainForm.cs @@ -0,0 +1,252 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Windows.Forms; +using Discord.WebSocket; +using DML.AppCore.Classes; +using DML.Application.Classes; +using DML.Client; +using static SweetLib.Utils.Logger.Logger; + +namespace DML.Application +{ + public partial class MainForm : Form + { + private bool IsInitialized { get; set; } = false; + public MainForm() + { + InitializeComponent(); + } + + private void MainForm_Shown(object sender, System.EventArgs e) + { + Trace("MainForm shown executed."); + RefreshComponents(); + + IsInitialized = true; + } + + private void RefreshComponents() + { + Debug("Refreshing components..."); + + lbVersion.Text = $"v{Assembly.GetExecutingAssembly().GetName().Version} Copyright © by Serraniel"; + + Trace("Refreshing operating folder component..."); + edOperatingFolder.Text = Core.Settings.OperatingFolder; + + Trace("Refreshing name scheme component..."); + edNameScheme.Text = Core.Settings.FileNameScheme; + + Trace("Refreshing skip existing files component..."); + cbSkipExisting.Checked = Core.Settings.SkipExistingFiles; + + Trace("Refreshing thread limit component..."); + edThreadLimit.Value = Core.Settings.ThreadLimit; + + if (cbGuild.Items.Count == 0) + { + Trace("Adding guilds to component..."); + cbGuild.Items.AddRange(DMLClient.Client.Guilds.OrderBy(g => g.Name).Select(g => g.Name).ToArray()); + cbGuild.SelectedIndex = 0; + Trace("Guild component initialized."); + } + + Trace("Refreshing job list component..."); + var oldIndex = lbxJobs.SelectedIndex; + lbxJobs.Items.Clear(); + foreach (var job in Core.Scheduler.JobList) + { + lbxJobs.Items.Add( + $"{FindServerById(job.GuildId)?.Name}:{FindChannelById(FindServerById(job.GuildId), job.ChannelId)?.Name}"); + } + lbxJobs.SelectedIndex = oldIndex; + } + + private void DoSomethingChanged(object sender, System.EventArgs e) + { + Debug($"DoSomethingChanged excuted by {sender}."); + if (!IsInitialized) + { + Trace("Form not initialized. Leaving DoSomethingChanged..."); + return; + } + + Trace("Updating operating folder..."); + Core.Settings.OperatingFolder = edOperatingFolder.Text; + + Trace("Updating name scheme..."); + Core.Settings.FileNameScheme = edNameScheme.Text; + + Trace("Updating skip existing files..."); + Core.Settings.SkipExistingFiles = cbSkipExisting.Checked; + + Trace("Updating thread limit..."); + Core.Settings.ThreadLimit = (int)edThreadLimit.Value; + + Trace("Storing new settings..."); + Core.Settings.Store(); + + Info("New settings have been saved."); + } + + private void btnSearchFolders_Click(object sender, System.EventArgs e) + { + Trace("Operating folder button pressed."); + using (var browserDialog = new FolderBrowserDialog()) + { + Debug("Showing file browser dialog for operating folder..."); + + browserDialog.SelectedPath = edOperatingFolder.Text; + browserDialog.ShowNewFolderButton = true; + browserDialog.Description = "Select an operating folder..."; + + if (browserDialog.ShowDialog() == DialogResult.OK) + { + edOperatingFolder.Text = browserDialog.SelectedPath; + Debug("Updated operating folder."); + } + } + } + + private SocketGuild FindServerByName(string name) + { + Trace($"Trying to find server by name: {name}"); + return (from s in DMLClient.Client.Guilds where s.Name == name select s).FirstOrDefault(); + } + + private SocketTextChannel FindChannelByName(SocketGuild server, string name) + { + Trace($"Trying to find channel in {server} by name: {name}"); + return (from c in server.TextChannels where c.Name == name select c).FirstOrDefault(); + } + + private SocketGuild FindServerById(ulong id) + { + Trace($"Trying to find server by Id: {id}"); + return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); + } + + private SocketTextChannel FindChannelById(SocketGuild server, ulong id) + { + Trace($"Trying to find channel in {server} by id: {id}"); + return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); + } + + private void cbGuild_SelectedIndexChanged(object sender, System.EventArgs e) + { + Trace("Guild index changed."); + Debug("Updating channel dropdown component..."); + + UseWaitCursor = true; + try + { + var guild = FindServerByName(cbGuild.Text); + + if (guild != null) + { + Trace("Cleaning channel component from old values..."); + cbChannel.Items.Clear(); + + Trace("Adding new channels..."); + cbChannel.Items.AddRange(guild.TextChannels.OrderBy(c => c.Position).Select(c => c.Name).ToArray()); + Trace($"Added {cbChannel.Items.Count} channels."); + + cbChannel.SelectedIndex = 0; + } + else + { + Warn($"Guild {cbGuild.Text} could not be found!"); + } + } + finally + { + UseWaitCursor = false; + } + + Debug("Finished updating channel dropdown component."); + } + + private void btnAddJob_Click(object sender, System.EventArgs e) + { + var job = new Job + { + GuildId = FindServerByName(cbGuild.Text).Id, + ChannelId = FindChannelByName(FindServerByName(cbGuild.Text), cbChannel.Text).Id + }; + + if (!(from j in Core.Scheduler.JobList + where j.GuildId == job.GuildId && j.ChannelId == job.ChannelId + select j).Any()) + { + job.Store(); + Core.Scheduler.JobList.Add(job); + } + + RefreshComponents(); + } + + private void btnDelete_Click(object sender, System.EventArgs e) + { + Trace("Deleting job pressed."); + + if (lbxJobs.SelectedIndex < 0) + { + Warn("No job selected."); + MessageBox.Show("No job has been seleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + var jobNameData = lbxJobs.SelectedItem.ToString().Split(':'); + + var guildName = ""; + for (var i = 0; i < jobNameData.Length - 1; i++) + guildName += jobNameData[i] + ":"; + guildName = guildName.Substring(0, guildName.Length - 1); + + var channelName = jobNameData[jobNameData.Length - 1]; + + var guild = FindServerByName(guildName); + var channel = FindChannelByName(guild, channelName); + + foreach (var job in Core.Scheduler.JobList) + { + if (job.GuildId == guild.Id && job.ChannelId == channel.Id) + { + Core.Scheduler.JobList.Remove(job); + Core.Scheduler.RunningJobs.Remove(job.Id); + job.Stop(); + job.Delete(); + break; + } + } + + lbxJobs.SelectedIndex = -1; + RefreshComponents(); + } + + private void tmrRefreshProgress_Tick(object sender, System.EventArgs e) + { + var scanned = Core.Scheduler.MessagesScanned; + var totalAttachments = Core.Scheduler.TotalAttachments; + var done = Core.Scheduler.AttachmentsDownloaded; + + var progress = totalAttachments > 0 ? (int)(100 * done / totalAttachments) : 0; + progress = Math.Min(Math.Max(0, progress), 100); + pgbProgress.Maximum = 100; + pgbProgress.Value = progress; + + lbProgress.Text = $"Scanned: {scanned} Downloaded: {done} Open: {totalAttachments - done}"; + } + + private void aboutToolStripMenuItem_Click(object sender, System.EventArgs e) + { + MessageBox.Show(Properties.Resources.AboutString); + } + + private void visitGithubToolStripMenuItem_Click(object sender, System.EventArgs e) + { + Process.Start("https://github.com/Serraniel/DiscordMediaLoader/"); + } + } +} diff --git a/Discord Media Loader.Application/MainForm.resx b/Discord Media Loader.Application/MainForm.resx new file mode 100644 index 0000000..7c9ffe0 --- /dev/null +++ b/Discord Media Loader.Application/MainForm.resx @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 126, 17 + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8b////VP///3////+m////zP// + /+T////1//////////r////u////3f///8z///+m////f////1T///8b////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////M////5D////b//////// + //////////////////////////////////////////////////////////////////////////////// + /9v///+Q////M////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///w3///90////1v// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////W////dP///w3///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A2K4AANiuAADYrgAA2K4AANiuAADfswAA////EP// + /4D////t////////////////////////////////////////////////9/zq/7XiQ/+340n/w+ho/9Pu + kf/m9b//+Pzt///////////////////////////////////////////////////////////t////gf// + /xCa2AAAmtgAAJrYAACa2AAAmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ANiuAADYrgAA2K4AANiu + AAD///8B////Yf///+v//////////////////////////////////////////////////////////+z4 + z/+a2AD/mtgA/5rYAP+a2AD/mtgA/6TcGf+65VL/3fKo//n98P////////////////////////////// + ///////////////////////r////Yf///wGa2AAAmtgAAJrYAACa2AAA////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wDYrgAA2K4AANiuAAD///8k////xP////////////////////////////////////////////////// + ///////////////////h87P/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+n3SD/zOt+//X7 + 5v/////////////////////////////////////////////////////E////I5rYAACa2AAAmtgAAP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A2K4AANiuAAD///9V////8f////////////////////////////////// + ////////////////////////////////////////0+6Q/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+k3Bn/0e2K//v+9v////////////////////////////////////////////// + //H///9VmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wH///+M//////////////////////// + /////////////////////////////////////////////////////////////8bpbv+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+u4DP/5/bD//////////////////// + /////////////////////////////////4z///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wb///+l//////// + //////////////////////////////////////////////////////////////////////////////// + //+55E//mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5zZ + Bv/P7Ib/////////////////////////////////////////////////////pf///wb///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3//////////////////////////////////////////////////////////////////////// + ///////////////////+//3/rd8x/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/7zlV//5/fH///////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wH///+k//////////////////////////////////////////////////////// + ////////////////////////////////////////+f3v/6PcF/+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/seE7//X75v////////////// + /////////////////////////////////6T///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///+K//////////////////////////////////////// + /////////////////////////////////////////////////////////////+751f+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+s3y7/9Pvi////////////////////////////////////////////////i////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///9V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///s+M//rd8w/6XcHf+c2Qb/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/6zfLv/1++f///////////////////////////////////////// + //////9V////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8j////8P// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////v/6/fL/8PnZ/97yrP/E6Gv/qd4l/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/suE8//v99P////////////// + ////////////////////////////8P///yP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8B////xP////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////v9 + 9f/e8qz/s+JA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP/A51/////////////////////////////////////////////////E////Af///wD///8A////AP// + /wD///8A////AP///wD///8A////Yf////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////f867/rN8t/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/9jwnP////////////////////////////////////////////// + /2H///8A////AP///wD///8A////AP///wD///8A////EP///+r///////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////r98//I6nX/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+h2xL/8/rh//////////////////// + ///////////////////////q////Ev///wD///8A////AP///wD///8A////AP///3////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////9/yrf+h2xL/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8Dn + X////////////////////////////////////////////////4D///8A////AP///wD///8A////AP// + /w3////u//////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////7PjQ/6reKP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+c2QX/7vjT///////////////////////////////////////////u////Df// + /wD///8A////AP///wD///90//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////0++L/qd4n/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8PoaP////////////////////////////// + /////////////////3T///8A////AP///wD///8B////2/////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////+360P+k4g3/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+j2xb/+Pzu//// + ///////////////////////////////////////b////Af///wD///8A////O/////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////wNCY/53dAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/9/zr////////////////////////////////////////////////zv///8A////AP// + /5D///////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////f39/+Puin/n+AA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP/F6W3///////////////////////////////////////// + //////+Q////AP///wD////Y//////////////////////////////////////////////////////// + /////////////////////////////+3t7f+3t7f/jIyM/3Nzc/9oaGj/a2tr/35+fv+dnZ3/xsbG//Hx + 8f////////////////////////////////////////////////////////////////////////////// + ////////iZNy/5zbAP+b2QD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/s+JA//////////////////// + ////////////////////////////2f///wD///8d//////////////////////////////////////// + ////////////////////////////////////////9fTx/46Pk/9BQUH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv9ERET/fn5+/8rKyv////////////////////////////////////////////// + /////////////////////////////4iHjP98qA3/n+AA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6ne + J/////////////////////////////////////////////////////8e////Vv////////////////// + ////////////////////////////////////////////////////////+fDL/45/Qv8kJjH/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Xl5e/7e3t//+/v7///////// + //////////////////////////////////////////////////9vbXX/T2Ig/57fAP+a2QD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+l3Bz/////////////////////////////////////////////////////Vv// + /4T/////////////////////////////////////////////////////////////////////8+ax/+O5 + D/92ZBv/JCYx/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/YGBg/8jIyP/////////////////////////////////////////////////09PT/QkJE/zEz + Lf+Mwgb/nd0A/5rYAP+a2AD/mtgA/5rYAP+a2AD/pdwb//////////////////////////////////// + /////////////////4T///+t//////////////////////////////////////////////////////// + ////////8+ez/9mwB//htQD/dWMb/yQnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/dHR0/97e3v////////////////////////////// + ////////r6+v/y4uLv8nIzH/bZET/6HiAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6neJv////////////// + //////////////////////////////////////+t////zP////////////////////////////////// + ////////////////////////+PHS/9qyDv/YrgD/4rUA/3lmGf8kJjH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Ozs7/1ZWVv90dHT/jo6O/5mZmf/Hx8f//v7+//// + ////////////////////////7+/v/0dHR/8uLi7/KCUw/1BkH/+f4AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+z4T7/////////////////////////////////////////////////////zP///+X///////////// + /////////////////////////////////////////v35/+C/N//YrgD/2K4A/+O2AP+Dbhf/IyYx/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/1VVVf+QkJD/xsbG/+rq6v////////////// + ////////////////////////////////////////+vr6/2tra/8uLi7/Li4u/yspL/86Qij/ltEC/5va + AP+a2AD/mtgA/5rYAP+a2AD/x+ly//////////////////////////////////////////////////// + /+D////y//////////////////////////////////////////////////////Dfm//YrgD/2K4A/9iu + AP/itQD/k3oT/yUnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/cHBw/8bGxv/8/Pz///////// + ////////////////////////////////////////////////////////7+/v/3BwcP8uLi7/Li4u/y4u + Lv8tLC7/Ly8u/4e7CP+e3gD/mtgA/5rYAP+a2AD/mtgA/+X1vf////////////////////////////// + ///////////////////////s//////////////////////////////////////////////////////79 + +f/euyr/2K4A/9iuAP/YrgD/4LQA/6iKDf8oKTD/Li4u/y4uLv8uLi7/Li4u/y4uLv9QUFD/xMTE//// + ////////////////////////////////////////////////////////////////////////w8PD/1BQ + UP8uLi7/Li4u/y4uLv8uLi7/Li4u/yknMP95pA7/n+EA/5rYAP+a2AD/mtgA/6neJ//8/vj///////// + ////////////////////////////////////////////+f////////////////////////////////// + ///////////////////168D/2K4A/9iuAP/YrgD/2K4A/92yAP+/mwf/MTAt/ywtLv8uLi7/Li4u/y4u + Lv9wcHD/8PDw//////////////////////////////////////////////////////////////////r6 + +v/ExMT/bm5u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/a44U/6HiAP+a2AD/mtgA/5rY + AP/W75j///////////////////////////////////////////////////////////n////y//////// + ////////////////////////////////////////6tR3/9iuAP/YrgD/2K4A/9iuAP/arwD/06oB/0VA + J/8pKi//Li4u/y4uLv9ra2v/+vr6//////////////////////////////////////////////////39 + /f/n5+f/wsLC/4yMjP9SUlL/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/JyMx/2F+ + GP+h4wD/mtgA/5rYAP+v4DX//P74//////////////////////////////////////////////////// + ///////s////5f///////////////////////////////////////////////+LDQv/YrgD/2K4A/9iu + AP/YrgD/2K4A/+C0AP9oWh7/JCcx/y4uLv9GRkb/7u7u/////////////////////////////v7+/8fH + x/+VlZX/iYmJ/3BwcP9TU1P/OTk5/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/ygkMf9bdRv/oOEA/5rYAP+g2g7/7fjR//////////////////////////////////// + ////////////////////////////4P///8z///////////////////////////////////////////// + ///euyj/2K4A/9iuAP/YrgD/2K4A/9iuAP/itQD/ln0S/yQnMf8uLi7/ra2t//////////////////// + ///////////////////i4uL/e3t7/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/WXEc/6DhAP+d2Qf/4fOy//////////////////// + /////////////////////////////////////////////////8z///+t//////////////////////// + ////////////////////////3Lcc/9iuAP/YrgD/2K4A/9iuAP/YrgD/3LEA/8agBf82NCz/Pj9C//T0 + 9P/////////////////////////////////////////////////Ozs7/ZmZm/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/KCQx/1pzHP+m4w//4POx//// + //////////////////////////////////////////////////////////////////////+t////hP// + /////////////////////////////////////////////9y3Hf/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/gtAD/Z1ke/2lrcv////////////////////////////////////////////////////////////// + //++vr7/ZGRk/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/ygk + Mf93i0X/7PnL//////////////////////////////////////////////////////////////////// + ////////////hP///1T////////////////////////////////////////////////euyr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/4LQA/66OC/+Dg4b///////////////////////////////////////// + ///////////////////////////////////Pz8//hISE/0hISP8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/0JCQv+RkJX/9PXy//////////////////////////////////////////////////// + /////////////////////////////////1b///8c//////////////////////////////////////// + ////////48RG/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9mvAP/dsgD/kopq//////////////////// + ///////////////////////////////////////////////////////////////////09PT/y8vL/6Ki + ov+Dg4P/cXFx/21tbf94eHj/kJCQ/7q6uv/v7+////////////////////////////////////////// + //////////////////////////////////////////////////////8d////AP///9f///////////// + /////////////////////////////+rTdP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4LQA/72f + Iv/09PT///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////Y////AP// + /wD///+P///////////////////////////////////////////06Lb/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/esgD/zsKO//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////kP///wD///8A////Ov///////////////////////////////////////////fvx/9y2 + Gv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+G3Cf/578j///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////zr///8A////AP///wH////a//////////////////////// + ///////////////////p0nD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/3bgh//r0 + 3P////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////9v///8B////AP///wD///8A////dP// + ////////////////////////////////////////+fPZ/9mxCP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/duCH/9+7I//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////90////AP// + /wD///8A////AP///wz////t///////////////////////////////////////////oz2n/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9qyDf/x4qT///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////Df///wD///8A////AP///wD///8A////f/////////////////////////////////// + ////////+/fm/9u1Fv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+nQ + bP/8+u7///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////f////wD///8A////AP///wD///8A////AP///xD////q//////// + ///////////////////////////////////x4qX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/3ron//Hipf///v3///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////6v///xD///8A////AP///wD///8A////AP// + /wD///8A////YP///////////////////////////////////////////////+jPaf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4cA5//HipP/9+/H///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////2H///8A////AP// + /wD///8A////AP///wD///8A////AP///wH////D//////////////////////////////////////// + ///+/fj/4sRE/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/3bgf/+fNY//x4qX/+PHT//367//+/vv////+//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /8P///8B////AP///wD///8A////AP///wD///8A////AP///wD///8A////Iv////D///////////// + //////////////////////////////z57f/gvzX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9ivA//cthj/37wr//fuyv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////D///8i////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///9U////////////////////////////////////////////////+/jo/+C/Nf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/48NH///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////9V////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///4j///////////////////////////////////////////// + ///8+ev/4sND/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/btBP//Pns//////////////////////////////////////////////////// + //////////////////////////////////////////////////////+J////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////pP////////////////// + //////////////////////////////789v/nzGD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/37wt///+/P////////////////////////////// + //////////////////////////////////////////////////////////////////////+k////Af// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3/////////////////////////////////////////////////////+7bj//asQr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+PGSv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////Bv///6X///////////////////////////////////////////// + ////////9+7K/+HBO//YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/oz2n///////////////////////////////////////////////////////////////////////// + //////////////////////+l////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////jP////////////////// + ///////////////////////////////////+/fr/7t2T/924Hv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/7dqL//////////////////////////////////////////////////// + //////////////////////////////////////+M////Af///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A2K4AANiu + AAD///9V////8f/////////////////////////////////////////////////////8+ev/7dmH/966 + Jv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A//Plrv////////////////////////////// + //////////////////////////////////////////////////H///9VmtgAAJrYAAD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////ANiuAADYrgAA2K4AAP///yT////E//////////////////////////////////////// + ///////////////////9/PT/8+av/+bLWv/duB7/2K4A/9iuAP/YrgD/2K4A/9iuAP/37sv///////// + /////////////////////////////////////////////////////////////////8T///8jmtgAAJrY + AACa2AAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDYrgAA2K4AANiuAADYrgAA////Af///2D////r//////// + /////////////////////////////////////////////////////////fvx//btxv/v3pf/6dJw/+TH + T//jxEX/+/jo////////////////////////////////////////////////////////////////6/// + /2D///8BmtgAAJrYAACa2AAAmtgAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////EP///3/////t//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////f////xD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////Df///3T////V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////9X///90////Df///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /zP///+N////1/////////////////////////////////////////////////////////////////// + ///////////////////////X////jf///zP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8b////VP///33///+l////y////+P////y//////// + ///////y////4////8v///+l////ff///1T///8b////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////4Af///////wAAD//////8AAAD/////+AAAAB/////wAAAAD////8AAAAAD////gAAAAA + H///4AAAAAAH///AAAAAAAP//4AAAAAAAf//AAAAAAAA//4AAAAAAAB//gAAAAAAAH/8AAAAAAAAP/gA + AAAAAAAf+AAAAAAAAB/wAAAAAAAAD/AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB8AAAAAAAAADwAAAAAAA + AAOAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABwAAAAAAAAAPAAAAAAAAAA+AA + AAAAAAAH4AAAAAAAAAfwAAAAAAAAD/AAAAAAAAAP+AAAAAAAAB/4AAAAAAAAH/wAAAAAAAA//gAAAAAA + AH/+AAAAAAAAf/8AAAAAAAD//4AAAAAAAf//wAAAAAAD///gAAAAAAf///gAAAAAH////AAAAAA///// + AAAAAP/////AAAAD//////AAAA///////AAAP///////4Af///8= + + + \ No newline at end of file diff --git a/Discord Media Loader.Application/Properties/AssemblyInfo.cs b/Discord Media Loader.Application/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8bfbcd1 --- /dev/null +++ b/Discord Media Loader.Application/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Discord Media Loader.Application")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Serraniel")] +[assembly: AssemblyProduct("Discord Media Loader.Application")] +[assembly: AssemblyCopyright("Copyright © 2017 by Serraniel")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("c130de6a-3237-42b5-be9f-783d1cd104c6")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Discord Media Loader.Application/Properties/Resources.Designer.cs b/Discord Media Loader.Application/Properties/Resources.Designer.cs new file mode 100644 index 0000000..971d55e --- /dev/null +++ b/Discord Media Loader.Application/Properties/Resources.Designer.cs @@ -0,0 +1,92 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace DML.Application.Properties { + using System; + + + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DML.Application.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Discord Media Loader by Serraniel - Apache 2.0 License + ///https://github.com/Serraniel/DiscordMediaLoader/ + /// + ///Made with: + ///SweetLib (Copyright (c) 2017 Serraniel - GNU General Public License v3.0) + ///Discord.Net (Copyright (c) 2015 RogueException - MIT License) + ///Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) + ///Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) + ///RestSharp (Copyright (c) restsharp - Apache 2.0 License) + ///WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) + /// [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + /// + internal static string AboutString { + get { + return ResourceManager.GetString("AboutString", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Serraniel_Logo4_NO_BG { + get { + object obj = ResourceManager.GetObject("Serraniel_Logo4_NO_BG", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/Discord Media Loader.Application/Properties/Resources.resx b/Discord Media Loader.Application/Properties/Resources.resx new file mode 100644 index 0000000..6c5f149 --- /dev/null +++ b/Discord Media Loader.Application/Properties/Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Discord Media Loader by Serraniel - Apache 2.0 License +https://github.com/Serraniel/DiscordMediaLoader/ + +Made with: +SweetLib (Copyright (c) 2017 Serraniel - GNU General Public License v3.0) +Discord.Net (Copyright (c) 2015 RogueException - MIT License) +Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) +Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) +RestSharp (Copyright (c) restsharp - Apache 2.0 License) +WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) +LiteDB (Copyright (c) 2014 - 2015 Mauricio David - MIT License) +Octokit (Copyright (c) 2012 GitHub, Inc - MIT License) + + + + ..\Resources\Serraniel-Logo4-NO-BG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Discord Media Loader.Application/Resources/Serraniel-Logo4-NO-BG.png b/Discord Media Loader.Application/Resources/Serraniel-Logo4-NO-BG.png new file mode 100644 index 0000000..aeb98e3 Binary files /dev/null and b/Discord Media Loader.Application/Resources/Serraniel-Logo4-NO-BG.png differ diff --git a/Discord Media Loader.Application/app.config b/Discord Media Loader.Application/app.config new file mode 100644 index 0000000..d5170f1 --- /dev/null +++ b/Discord Media Loader.Application/app.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Discord Media Loader.Application/packages.config b/Discord Media Loader.Application/packages.config new file mode 100644 index 0000000..d3092d0 --- /dev/null +++ b/Discord Media Loader.Application/packages.config @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Discord Media Loader.sln b/Discord Media Loader.sln index d23ac3c..8124baf 100644 --- a/Discord Media Loader.sln +++ b/Discord Media Loader.sln @@ -1,9 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord Media Loader", "Discord Media Loader\Discord Media Loader.csproj", "{83E64057-7010-4DF3-A011-F7E536508407}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord Media Loader", "Discord Media Loader\Discord Media Loader.csproj", "{EDC92554-DBC1-4F9C-9317-379A8BF441E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DML.Application", "Discord Media Loader.Application\DML.Application.csproj", "{C130DE6A-3237-42B5-BE9F-783D1CD104C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SweetLib", "..\SweetLib\SweetLib\SweetLib.csproj", "{02C1F8EF-32F2-4E77-A36D-79129402AF37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DML.Client", "DML.Client\DML.Client.csproj", "{045EB4A1-34E7-47E0-867E-E10C40505095}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,12 +17,27 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {83E64057-7010-4DF3-A011-F7E536508407}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83E64057-7010-4DF3-A011-F7E536508407}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83E64057-7010-4DF3-A011-F7E536508407}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83E64057-7010-4DF3-A011-F7E536508407}.Release|Any CPU.Build.0 = Release|Any CPU + {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Release|Any CPU.Build.0 = Release|Any CPU + {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Release|Any CPU.Build.0 = Release|Any CPU + {02C1F8EF-32F2-4E77-A36D-79129402AF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02C1F8EF-32F2-4E77-A36D-79129402AF37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02C1F8EF-32F2-4E77-A36D-79129402AF37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02C1F8EF-32F2-4E77-A36D-79129402AF37}.Release|Any CPU.Build.0 = Release|Any CPU + {045EB4A1-34E7-47E0-867E-E10C40505095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {045EB4A1-34E7-47E0-867E-E10C40505095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {045EB4A1-34E7-47E0-867E-E10C40505095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {045EB4A1-34E7-47E0-867E-E10C40505095}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0B742DE0-D6AF-4033-9605-863C32A7FFD8} + EndGlobalSection EndGlobal diff --git a/Discord Media Loader/App.config b/Discord Media Loader/App.config index 9610fd5..7cf02b2 100644 --- a/Discord Media Loader/App.config +++ b/Discord Media Loader/App.config @@ -1,18 +1,26 @@ - + - - -
- - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/Discord Media Loader/Discord Media Loader.csproj b/Discord Media Loader/Discord Media Loader.csproj index e6017f7..0977fa7 100644 --- a/Discord Media Loader/Discord Media Loader.csproj +++ b/Discord Media Loader/Discord Media Loader.csproj @@ -4,12 +4,12 @@ Debug AnyCPU - {83E64057-7010-4DF3-A011-F7E536508407} + {EDC92554-DBC1-4F9C-9317-379A8BF441E8} WinExe Properties Discord_Media_Loader Discord Media Loader - v4.6 + v4.6.1 512 true @@ -32,24 +32,43 @@ TRACE prompt 4 - True - True - True - None.Increment.None.None - None.Increment.None.None - None.Increment.None.None Serraniel-64x64.ico + + + - - ..\packages\Discord.Net.0.9.6\lib\net45\Discord.Net.dll - True + + ..\packages\Discord.Net.Commands.1.0.2\lib\netstandard1.1\Discord.Net.Commands.dll - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\packages\Discord.Net.Core.1.0.2\lib\net45\Discord.Net.Core.dll + + + ..\packages\Discord.Net.Rest.1.0.2\lib\net45\Discord.Net.Rest.dll + + + ..\packages\Discord.Net.Rpc.1.0.2\lib\net45\Discord.Net.Rpc.dll + + + ..\packages\Discord.Net.Webhook.1.0.2\lib\netstandard1.1\Discord.Net.Webhook.dll + + + ..\packages\Discord.Net.WebSocket.1.0.2\lib\net45\Discord.Net.WebSocket.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.1.1.1\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll @@ -67,47 +86,99 @@ ..\packages\Octokit.0.24.1-alpha0001\lib\net45\Octokit.dll True - - ..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll + + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll True - + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + + + ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + + ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + + ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + - - - ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll - True + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + Form + + + FrmDownload.cs + + + Form + + + FrmSplash.cs + + - - Form - - - LoginForm.cs - - - Form - - - MainForm.cs - - - LoginForm.cs + + FrmDownload.cs - - MainForm.cs + + FrmSplash.cs ResXFileCodeGenerator @@ -134,7 +205,15 @@ + + + + + + {c130de6a-3237-42b5-be9f-783d1cd104c6} + DML.Application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8b////VP///3////+m////zP// + /+T////1//////////r////u////3f///8z///+m////f////1T///8b////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////M////5D////b//////// + //////////////////////////////////////////////////////////////////////////////// + /9v///+Q////M////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///w3///90////1v// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////W////dP///w3///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A2K4AANiuAADYrgAA2K4AANiuAADfswAA////EP// + /4D////t////////////////////////////////////////////////9/zq/7XiQ/+340n/w+ho/9Pu + kf/m9b//+Pzt///////////////////////////////////////////////////////////t////gf// + /xCa2AAAmtgAAJrYAACa2AAAmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ANiuAADYrgAA2K4AANiu + AAD///8B////Yf///+v//////////////////////////////////////////////////////////+z4 + z/+a2AD/mtgA/5rYAP+a2AD/mtgA/6TcGf+65VL/3fKo//n98P////////////////////////////// + ///////////////////////r////Yf///wGa2AAAmtgAAJrYAACa2AAA////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wDYrgAA2K4AANiuAAD///8k////xP////////////////////////////////////////////////// + ///////////////////h87P/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+n3SD/zOt+//X7 + 5v/////////////////////////////////////////////////////E////I5rYAACa2AAAmtgAAP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A2K4AANiuAAD///9V////8f////////////////////////////////// + ////////////////////////////////////////0+6Q/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+k3Bn/0e2K//v+9v////////////////////////////////////////////// + //H///9VmtgAAJrYAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wH///+M//////////////////////// + /////////////////////////////////////////////////////////////8bpbv+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+u4DP/5/bD//////////////////// + /////////////////////////////////4z///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wb///+l//////// + //////////////////////////////////////////////////////////////////////////////// + //+55E//mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5zZ + Bv/P7Ib/////////////////////////////////////////////////////pf///wb///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3//////////////////////////////////////////////////////////////////////// + ///////////////////+//3/rd8x/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/7zlV//5/fH///////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wH///+k//////////////////////////////////////////////////////// + ////////////////////////////////////////+f3v/6PcF/+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/seE7//X75v////////////// + /////////////////////////////////6T///8B////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///+K//////////////////////////////////////// + /////////////////////////////////////////////////////////////+751f+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+s3y7/9Pvi////////////////////////////////////////////////i////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///9V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///s+M//rd8w/6XcHf+c2Qb/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/6zfLv/1++f///////////////////////////////////////// + //////9V////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8j////8P// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////v/6/fL/8PnZ/97yrP/E6Gv/qd4l/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/suE8//v99P////////////// + ////////////////////////////8P///yP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8B////xP////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////v9 + 9f/e8qz/s+JA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP/A51/////////////////////////////////////////////////E////Af///wD///8A////AP// + /wD///8A////AP///wD///8A////Yf////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////f867/rN8t/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/9jwnP////////////////////////////////////////////// + /2H///8A////AP///wD///8A////AP///wD///8A////EP///+r///////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////r98//I6nX/mtgA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+h2xL/8/rh//////////////////// + ///////////////////////q////Ev///wD///8A////AP///wD///8A////AP///3////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////9/yrf+h2xL/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8Dn + X////////////////////////////////////////////////4D///8A////AP///wD///8A////AP// + /w3////u//////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////7PjQ/6reKP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+c2QX/7vjT///////////////////////////////////////////u////Df// + /wD///8A////AP///wD///90//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////0++L/qd4n/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/8PoaP////////////////////////////// + /////////////////3T///8A////AP///wD///8B////2/////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////+360P+k4g3/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP+j2xb/+Pzu//// + ///////////////////////////////////////b////Af///wD///8A////O/////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////wNCY/53dAP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+a2AD/mtgA/9/zr////////////////////////////////////////////////zv///8A////AP// + /5D///////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////f39/+Puin/n+AA/5rY + AP+a2AD/mtgA/5rYAP+a2AD/mtgA/5rYAP/F6W3///////////////////////////////////////// + //////+Q////AP///wD////Y//////////////////////////////////////////////////////// + /////////////////////////////+3t7f+3t7f/jIyM/3Nzc/9oaGj/a2tr/35+fv+dnZ3/xsbG//Hx + 8f////////////////////////////////////////////////////////////////////////////// + ////////iZNy/5zbAP+b2QD/mtgA/5rYAP+a2AD/mtgA/5rYAP+a2AD/s+JA//////////////////// + ////////////////////////////2f///wD///8d//////////////////////////////////////// + ////////////////////////////////////////9fTx/46Pk/9BQUH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv9ERET/fn5+/8rKyv////////////////////////////////////////////// + /////////////////////////////4iHjP98qA3/n+AA/5rYAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6ne + J/////////////////////////////////////////////////////8e////Vv////////////////// + ////////////////////////////////////////////////////////+fDL/45/Qv8kJjH/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Xl5e/7e3t//+/v7///////// + //////////////////////////////////////////////////9vbXX/T2Ig/57fAP+a2QD/mtgA/5rY + AP+a2AD/mtgA/5rYAP+l3Bz/////////////////////////////////////////////////////Vv// + /4T/////////////////////////////////////////////////////////////////////8+ax/+O5 + D/92ZBv/JCYx/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/YGBg/8jIyP/////////////////////////////////////////////////09PT/QkJE/zEz + Lf+Mwgb/nd0A/5rYAP+a2AD/mtgA/5rYAP+a2AD/pdwb//////////////////////////////////// + /////////////////4T///+t//////////////////////////////////////////////////////// + ////////8+ez/9mwB//htQD/dWMb/yQnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/dHR0/97e3v////////////////////////////// + ////////r6+v/y4uLv8nIzH/bZET/6HiAP+a2AD/mtgA/5rYAP+a2AD/mtgA/6neJv////////////// + //////////////////////////////////////+t////zP////////////////////////////////// + ////////////////////////+PHS/9qyDv/YrgD/4rUA/3lmGf8kJjH/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Ozs7/1ZWVv90dHT/jo6O/5mZmf/Hx8f//v7+//// + ////////////////////////7+/v/0dHR/8uLi7/KCUw/1BkH/+f4AD/mtgA/5rYAP+a2AD/mtgA/5rY + AP+z4T7/////////////////////////////////////////////////////zP///+X///////////// + /////////////////////////////////////////v35/+C/N//YrgD/2K4A/+O2AP+Dbhf/IyYx/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/1VVVf+QkJD/xsbG/+rq6v////////////// + ////////////////////////////////////////+vr6/2tra/8uLi7/Li4u/yspL/86Qij/ltEC/5va + AP+a2AD/mtgA/5rYAP+a2AD/x+ly//////////////////////////////////////////////////// + /+D////y//////////////////////////////////////////////////////Dfm//YrgD/2K4A/9iu + AP/itQD/k3oT/yUnMf8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/cHBw/8bGxv/8/Pz///////// + ////////////////////////////////////////////////////////7+/v/3BwcP8uLi7/Li4u/y4u + Lv8tLC7/Ly8u/4e7CP+e3gD/mtgA/5rYAP+a2AD/mtgA/+X1vf////////////////////////////// + ///////////////////////s//////////////////////////////////////////////////////79 + +f/euyr/2K4A/9iuAP/YrgD/4LQA/6iKDf8oKTD/Li4u/y4uLv8uLi7/Li4u/y4uLv9QUFD/xMTE//// + ////////////////////////////////////////////////////////////////////////w8PD/1BQ + UP8uLi7/Li4u/y4uLv8uLi7/Li4u/yknMP95pA7/n+EA/5rYAP+a2AD/mtgA/6neJ//8/vj///////// + ////////////////////////////////////////////+f////////////////////////////////// + ///////////////////168D/2K4A/9iuAP/YrgD/2K4A/92yAP+/mwf/MTAt/ywtLv8uLi7/Li4u/y4u + Lv9wcHD/8PDw//////////////////////////////////////////////////////////////////r6 + +v/ExMT/bm5u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/a44U/6HiAP+a2AD/mtgA/5rY + AP/W75j///////////////////////////////////////////////////////////n////y//////// + ////////////////////////////////////////6tR3/9iuAP/YrgD/2K4A/9iuAP/arwD/06oB/0VA + J/8pKi//Li4u/y4uLv9ra2v/+vr6//////////////////////////////////////////////////39 + /f/n5+f/wsLC/4yMjP9SUlL/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/JyMx/2F+ + GP+h4wD/mtgA/5rYAP+v4DX//P74//////////////////////////////////////////////////// + ///////s////5f///////////////////////////////////////////////+LDQv/YrgD/2K4A/9iu + AP/YrgD/2K4A/+C0AP9oWh7/JCcx/y4uLv9GRkb/7u7u/////////////////////////////v7+/8fH + x/+VlZX/iYmJ/3BwcP9TU1P/OTk5/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/ygkMf9bdRv/oOEA/5rYAP+g2g7/7fjR//////////////////////////////////// + ////////////////////////////4P///8z///////////////////////////////////////////// + ///euyj/2K4A/9iuAP/YrgD/2K4A/9iuAP/itQD/ln0S/yQnMf8uLi7/ra2t//////////////////// + ///////////////////i4uL/e3t7/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8oJDH/WXEc/6DhAP+d2Qf/4fOy//////////////////// + /////////////////////////////////////////////////8z///+t//////////////////////// + ////////////////////////3Lcc/9iuAP/YrgD/2K4A/9iuAP/YrgD/3LEA/8agBf82NCz/Pj9C//T0 + 9P/////////////////////////////////////////////////Ozs7/ZmZm/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/KCQx/1pzHP+m4w//4POx//// + //////////////////////////////////////////////////////////////////////+t////hP// + /////////////////////////////////////////////9y3Hf/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/gtAD/Z1ke/2lrcv////////////////////////////////////////////////////////////// + //++vr7/ZGRk/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/y4uLv8uLi7/Li4u/ygk + Mf93i0X/7PnL//////////////////////////////////////////////////////////////////// + ////////////hP///1T////////////////////////////////////////////////euyr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/4LQA/66OC/+Dg4b///////////////////////////////////////// + ///////////////////////////////////Pz8//hISE/0hISP8uLi7/Li4u/y4uLv8uLi7/Li4u/y4u + Lv8uLi7/Li4u/0JCQv+RkJX/9PXy//////////////////////////////////////////////////// + /////////////////////////////////1b///8c//////////////////////////////////////// + ////////48RG/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9mvAP/dsgD/kopq//////////////////// + ///////////////////////////////////////////////////////////////////09PT/y8vL/6Ki + ov+Dg4P/cXFx/21tbf94eHj/kJCQ/7q6uv/v7+////////////////////////////////////////// + //////////////////////////////////////////////////////8d////AP///9f///////////// + /////////////////////////////+rTdP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4LQA/72f + Iv/09PT///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////Y////AP// + /wD///+P///////////////////////////////////////////06Lb/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/esgD/zsKO//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////kP///wD///8A////Ov///////////////////////////////////////////fvx/9y2 + Gv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+G3Cf/578j///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////zr///8A////AP///wH////a//////////////////////// + ///////////////////p0nD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/3bgh//r0 + 3P////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////9v///8B////AP///wD///8A////dP// + ////////////////////////////////////////+fPZ/9mxCP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/duCH/9+7I//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////90////AP// + /wD///8A////AP///wz////t///////////////////////////////////////////oz2n/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9qyDf/x4qT///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////Df///wD///8A////AP///wD///8A////f/////////////////////////////////// + ////////+/fm/9u1Fv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+nQ + bP/8+u7///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////f////wD///8A////AP///wD///8A////AP///xD////q//////// + ///////////////////////////////////x4qX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/3ron//Hipf///v3///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////6v///xD///8A////AP///wD///8A////AP// + /wD///8A////YP///////////////////////////////////////////////+jPaf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/4cA5//HipP/9+/H///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////2H///8A////AP// + /wD///8A////AP///wD///8A////AP///wH////D//////////////////////////////////////// + ///+/fj/4sRE/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/3bgf/+fNY//x4qX/+PHT//367//+/vv////+//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /8P///8B////AP///wD///8A////AP///wD///8A////AP///wD///8A////Iv////D///////////// + //////////////////////////////z57f/gvzX/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9ivA//cthj/37wr//fuyv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////D///8i////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///9U////////////////////////////////////////////////+/jo/+C/Nf/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/48NH///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////9V////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///4j///////////////////////////////////////////// + ///8+ev/4sND/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/btBP//Pns//////////////////////////////////////////////////// + //////////////////////////////////////////////////////+J////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////pP////////////////// + //////////////////////////////789v/nzGD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/37wt///+/P////////////////////////////// + //////////////////////////////////////////////////////////////////////+k////Af// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wb///+3/////////////////////////////////////////////////////+7bj//asQr/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/+PGSv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////+3////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////Bv///6X///////////////////////////////////////////// + ////////9+7K/+HBO//YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/oz2n///////////////////////////////////////////////////////////////////////// + //////////////////////+l////Bv///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8B////jP////////////////// + ///////////////////////////////////+/fr/7t2T/924Hv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iu + AP/YrgD/2K4A/9iuAP/YrgD/7dqL//////////////////////////////////////////////////// + //////////////////////////////////////+M////Af///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A2K4AANiu + AAD///9V////8f/////////////////////////////////////////////////////8+ev/7dmH/966 + Jv/YrgD/2K4A/9iuAP/YrgD/2K4A/9iuAP/YrgD/2K4A//Plrv////////////////////////////// + //////////////////////////////////////////////////H///9VmtgAAJrYAAD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////ANiuAADYrgAA2K4AAP///yT////E//////////////////////////////////////// + ///////////////////9/PT/8+av/+bLWv/duB7/2K4A/9iuAP/YrgD/2K4A/9iuAP/37sv///////// + /////////////////////////////////////////////////////////////////8T///8jmtgAAJrY + AACa2AAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDYrgAA2K4AANiuAADYrgAA////Af///2D////r//////// + /////////////////////////////////////////////////////////fvx//btxv/v3pf/6dJw/+TH + T//jxEX/+/jo////////////////////////////////////////////////////////////////6/// + /2D///8BmtgAAJrYAACa2AAAmtgAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////EP///3/////t//////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////t////f////xD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////Df///3T////V//////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////9X///90////Df///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /zP///+N////1/////////////////////////////////////////////////////////////////// + ///////////////////////X////jf///zP///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8b////VP///33///+l////y////+P////y//////// + ///////y////4////8v///+l////ff///1T///8b////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////4Af///////wAAD//////8AAAD/////+AAAAB/////wAAAAD////8AAAAAD////gAAAAA + H///4AAAAAAH///AAAAAAAP//4AAAAAAAf//AAAAAAAA//4AAAAAAAB//gAAAAAAAH/8AAAAAAAAP/gA + AAAAAAAf+AAAAAAAAB/wAAAAAAAAD/AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB8AAAAAAAAADwAAAAAAA + AAOAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABwAAAAAAAAAPAAAAAAAAAA+AA + AAAAAAAH4AAAAAAAAAfwAAAAAAAAD/AAAAAAAAAP+AAAAAAAAB/4AAAAAAAAH/wAAAAAAAA//gAAAAAA + AH/+AAAAAAAAf/8AAAAAAAD//4AAAAAAAf//wAAAAAAD///gAAAAAAf///gAAAAAH////AAAAAA///// + AAAAAP/////AAAAD//////AAAA///////AAAP///////4Af///8= + + + \ No newline at end of file diff --git a/Discord Media Loader/Helper/TaskBarProgress.cs b/Discord Media Loader/Helper/TaskBarProgress.cs new file mode 100644 index 0000000..93331e3 --- /dev/null +++ b/Discord Media Loader/Helper/TaskBarProgress.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.InteropServices; + +namespace Discord_Media_Loader.Helper +{ + internal static class TaskBarProgress + { + internal enum TaskbarStates + { + NoProgress = 0, + Indeterminate = 0x1, + Normal = 0x2, + Error = 0x4, + Paused = 0x8 + } + + [ComImport] + [Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface ITaskbarList3 + { + // ITaskbarList + [PreserveSig] + void HrInit(); + [PreserveSig] + void AddTab(IntPtr hwnd); + [PreserveSig] + void DeleteTab(IntPtr hwnd); + [PreserveSig] + void ActivateTab(IntPtr hwnd); + [PreserveSig] + void SetActiveAlt(IntPtr hwnd); + + // ITaskbarList2 + [PreserveSig] + void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); + + // ITaskbarList3 + [PreserveSig] + void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal); + [PreserveSig] + void SetProgressState(IntPtr hwnd, TaskbarStates state); + } + + [Guid("56FDF344-FD6D-11d0-958A-006097C9A090")] + [ClassInterface(ClassInterfaceType.None)] + [ComImport] + private class TaskbarInstance + { + } + + private static ITaskbarList3 taskbarInstance = (ITaskbarList3)new TaskbarInstance(); + private static bool taskbarSupported = Environment.OSVersion.Version >= new Version(6, 1); + + internal static void SetState(IntPtr windowHandle, TaskbarStates taskbarState) + { + if (taskbarSupported) taskbarInstance.SetProgressState(windowHandle, taskbarState); + } + + internal static void SetValue(IntPtr windowHandle, double progressValue, double progressMax) + { + if (taskbarSupported) taskbarInstance.SetProgressValue(windowHandle, (ulong)progressValue, (ulong)progressMax); + } + } +} diff --git a/Discord Media Loader/Helper/VersionHelper.cs b/Discord Media Loader/Helper/VersionHelper.cs index bbe1ab9..bce4af3 100644 --- a/Discord Media Loader/Helper/VersionHelper.cs +++ b/Discord Media Loader/Helper/VersionHelper.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; using System.Reflection; -using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Octokit; namespace Discord_Media_Loader.Helper @@ -15,32 +10,30 @@ namespace Discord_Media_Loader.Helper { internal static Version CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version; - internal static async Task GetLatestReleaseVersion() + internal static Version AppVersion => AssemblyName.GetAssemblyName("Discord Media Loader.Application.dll").Version; + + internal static async Task GetReleaseVersion() { - var github = new GitHubClient(new ProductHeaderValue("DiscordMediaLoader")); + var github = new GitHubClient(new ProductHeaderValue("DiscordMedialLoader")); + var tag = - (await github.Repository.Release.GetAll("Serraniel", "DiscordMediaLoader")).OrderBy(x => x.CreatedAt).First().TagName.Replace("v", "") ?? ""; + (await github.Repository.Release.GetAll("Serraniel", "DiscordMediaLoader")).OrderByDescending(x => x.CreatedAt).First().TagName.Replace("v", "") ?? ""; + var version = new Version(tag); return version; } internal static async Task DownloadLatestReleaseVersion() { - return await DownloadReleaseVersion(await GetLatestReleaseVersion()); + return await DownloadVersion(await GetReleaseVersion()); } - internal static async Task DownloadReleaseVersion(Version version) + internal static async Task DownloadVersion(Version version) { var github = new GitHubClient(new ProductHeaderValue("DiscordMediaLoader")); var releaseVersion = (from release in (await github.Repository.Release.GetAll("Serraniel", "DiscordMediaLoader")) where release.TagName == $"v{version.Major}.{version.Minor}.{version.Build}.{version.Revision}" select release).First(); - //where release.TagName == $"v{version.Major}.{version.Minor}.{version.Revision}.{version.Build}" - /*var r = releases.ElementAt(0); - if (r.TagName == $"v{version.Major}.{version.Minor}.{version.Build}.{version.Revision}") - { - var releaseVersion = releases.First(); - return releaseVersion.Url; - }*/ - return releaseVersion.ZipballUrl; + + return releaseVersion.Assets.FirstOrDefault()?.BrowserDownloadUrl; } } } diff --git a/Discord Media Loader/LoginForm.Designer.cs b/Discord Media Loader/LoginForm.Designer.cs deleted file mode 100644 index fddb86d..0000000 --- a/Discord Media Loader/LoginForm.Designer.cs +++ /dev/null @@ -1,127 +0,0 @@ -namespace Discord_Media_Loader -{ - partial class LoginForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginForm)); - this.lbEmail = new System.Windows.Forms.Label(); - this.lbPassword = new System.Windows.Forms.Label(); - this.tbxEmail = new System.Windows.Forms.TextBox(); - this.tbxPassword = new System.Windows.Forms.TextBox(); - this.btnLogin = new System.Windows.Forms.Button(); - this.btnAbort = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // lbEmail - // - this.lbEmail.AutoSize = true; - this.lbEmail.Location = new System.Drawing.Point(12, 9); - this.lbEmail.Name = "lbEmail"; - this.lbEmail.Size = new System.Drawing.Size(35, 13); - this.lbEmail.TabIndex = 0; - this.lbEmail.Text = "Email:"; - // - // lbPassword - // - this.lbPassword.AutoSize = true; - this.lbPassword.Location = new System.Drawing.Point(12, 35); - this.lbPassword.Name = "lbPassword"; - this.lbPassword.Size = new System.Drawing.Size(56, 13); - this.lbPassword.TabIndex = 1; - this.lbPassword.Text = "Password:"; - // - // tbxEmail - // - this.tbxEmail.Location = new System.Drawing.Point(79, 6); - this.tbxEmail.Name = "tbxEmail"; - this.tbxEmail.Size = new System.Drawing.Size(204, 20); - this.tbxEmail.TabIndex = 2; - this.tbxEmail.KeyUp += new System.Windows.Forms.KeyEventHandler(this.tbx_KeyUp); - // - // tbxPassword - // - this.tbxPassword.Location = new System.Drawing.Point(79, 32); - this.tbxPassword.Name = "tbxPassword"; - this.tbxPassword.PasswordChar = '•'; - this.tbxPassword.Size = new System.Drawing.Size(204, 20); - this.tbxPassword.TabIndex = 3; - this.tbxPassword.KeyUp += new System.Windows.Forms.KeyEventHandler(this.tbx_KeyUp); - // - // btnLogin - // - this.btnLogin.Location = new System.Drawing.Point(127, 78); - this.btnLogin.Name = "btnLogin"; - this.btnLogin.Size = new System.Drawing.Size(75, 23); - this.btnLogin.TabIndex = 4; - this.btnLogin.Text = "&Login"; - this.btnLogin.UseVisualStyleBackColor = true; - this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click); - // - // btnAbort - // - this.btnAbort.Location = new System.Drawing.Point(208, 78); - this.btnAbort.Name = "btnAbort"; - this.btnAbort.Size = new System.Drawing.Size(75, 23); - this.btnAbort.TabIndex = 5; - this.btnAbort.Text = "&Abort"; - this.btnAbort.UseVisualStyleBackColor = true; - this.btnAbort.Click += new System.EventHandler(this.btnAbort_Click); - // - // LoginForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(296, 114); - this.Controls.Add(this.btnAbort); - this.Controls.Add(this.btnLogin); - this.Controls.Add(this.tbxPassword); - this.Controls.Add(this.tbxEmail); - this.Controls.Add(this.lbPassword); - this.Controls.Add(this.lbEmail); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "LoginForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Login"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label lbEmail; - private System.Windows.Forms.Label lbPassword; - private System.Windows.Forms.TextBox tbxEmail; - private System.Windows.Forms.TextBox tbxPassword; - private System.Windows.Forms.Button btnLogin; - private System.Windows.Forms.Button btnAbort; - } -} \ No newline at end of file diff --git a/Discord Media Loader/LoginForm.cs b/Discord Media Loader/LoginForm.cs deleted file mode 100644 index cf6f81e..0000000 --- a/Discord Media Loader/LoginForm.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Windows.Forms; -using Discord_Media_Loader.Helper; - -namespace Discord_Media_Loader -{ - public partial class LoginForm : Form - { - public static bool Exec(ref string email, out string password) - { - var loginForm = new LoginForm { tbxEmail = { Text = email } }; - password = ""; - - if (loginForm.ShowDialog() == DialogResult.OK) - { - email = loginForm.tbxEmail.Text; - password = loginForm.tbxPassword.Text; - return true; - } - - return false; - } - - public LoginForm() - { - InitializeComponent(); - } - - private void btnAbort_Click(object sender, EventArgs e) - { - DialogResult = DialogResult.Abort; - } - - private void btnLogin_Click(object sender, EventArgs e) - { - DialogResult = DialogResult.OK; - } - - private void tbx_KeyUp(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Return) - DialogResult = DialogResult.OK; - else if (e.KeyCode == Keys.Escape) - DialogResult = DialogResult.Abort; - } - } -} diff --git a/Discord Media Loader/MainForm.Designer.cs b/Discord Media Loader/MainForm.Designer.cs deleted file mode 100644 index f572a18..0000000 --- a/Discord Media Loader/MainForm.Designer.cs +++ /dev/null @@ -1,312 +0,0 @@ -namespace Discord_Media_Loader -{ - partial class MainForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - this.lbPath = new System.Windows.Forms.Label(); - this.btnSearch = new System.Windows.Forms.Button(); - this.tbxPath = new System.Windows.Forms.TextBox(); - this.dtpLimit = new System.Windows.Forms.DateTimePicker(); - this.btnDownload = new System.Windows.Forms.Button(); - this.cbLimitDate = new System.Windows.Forms.CheckBox(); - this.lbThread = new System.Windows.Forms.Label(); - this.cbChannels = new System.Windows.Forms.ComboBox(); - this.nupThreadCount = new System.Windows.Forms.NumericUpDown(); - this.lbChannel = new System.Windows.Forms.Label(); - this.lbScanCount = new System.Windows.Forms.Label(); - this.lbUsername = new System.Windows.Forms.Label(); - this.lbDownload = new System.Windows.Forms.Label(); - this.cbGuilds = new System.Windows.Forms.ComboBox(); - this.lbGuild = new System.Windows.Forms.Label(); - this.cbSkip = new System.Windows.Forms.CheckBox(); - this.lbCopyright = new System.Windows.Forms.Label(); - this.lbGithub = new System.Windows.Forms.LinkLabel(); - this.lbAbout = new System.Windows.Forms.LinkLabel(); - this.lbVersion = new System.Windows.Forms.LinkLabel(); - ((System.ComponentModel.ISupportInitialize)(this.nupThreadCount)).BeginInit(); - this.SuspendLayout(); - // - // lbPath - // - this.lbPath.AutoSize = true; - this.lbPath.Location = new System.Drawing.Point(12, 110); - this.lbPath.Name = "lbPath"; - this.lbPath.Size = new System.Drawing.Size(32, 13); - this.lbPath.TabIndex = 8; - this.lbPath.Text = "Path:"; - // - // btnSearch - // - this.btnSearch.Location = new System.Drawing.Point(403, 105); - this.btnSearch.Name = "btnSearch"; - this.btnSearch.Size = new System.Drawing.Size(25, 23); - this.btnSearch.TabIndex = 9; - this.btnSearch.Text = "..."; - this.btnSearch.UseVisualStyleBackColor = true; - this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click); - // - // tbxPath - // - this.tbxPath.Location = new System.Drawing.Point(50, 107); - this.tbxPath.Name = "tbxPath"; - this.tbxPath.Size = new System.Drawing.Size(342, 20); - this.tbxPath.TabIndex = 7; - // - // dtpLimit - // - this.dtpLimit.Format = System.Windows.Forms.DateTimePickerFormat.Short; - this.dtpLimit.Location = new System.Drawing.Point(158, 70); - this.dtpLimit.Name = "dtpLimit"; - this.dtpLimit.Size = new System.Drawing.Size(95, 20); - this.dtpLimit.TabIndex = 6; - // - // btnDownload - // - this.btnDownload.Location = new System.Drawing.Point(12, 167); - this.btnDownload.Name = "btnDownload"; - this.btnDownload.Size = new System.Drawing.Size(415, 23); - this.btnDownload.TabIndex = 20; - this.btnDownload.Text = "Start downloading"; - this.btnDownload.UseVisualStyleBackColor = true; - this.btnDownload.Click += new System.EventHandler(this.btnDownload_Click); - // - // cbLimitDate - // - this.cbLimitDate.AutoSize = true; - this.cbLimitDate.Checked = true; - this.cbLimitDate.CheckState = System.Windows.Forms.CheckState.Checked; - this.cbLimitDate.Location = new System.Drawing.Point(15, 73); - this.cbLimitDate.Name = "cbLimitDate"; - this.cbLimitDate.Size = new System.Drawing.Size(137, 17); - this.cbLimitDate.TabIndex = 5; - this.cbLimitDate.Text = "Only media posted after"; - this.cbLimitDate.UseVisualStyleBackColor = true; - this.cbLimitDate.CheckedChanged += new System.EventHandler(this.cbLimitDate_CheckedChanged); - // - // lbThread - // - this.lbThread.AutoSize = true; - this.lbThread.Location = new System.Drawing.Point(12, 141); - this.lbThread.Name = "lbThread"; - this.lbThread.Size = new System.Drawing.Size(64, 13); - this.lbThread.TabIndex = 11; - this.lbThread.Text = "Thread limit:"; - // - // cbChannels - // - this.cbChannels.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbChannels.FormattingEnabled = true; - this.cbChannels.Location = new System.Drawing.Point(279, 32); - this.cbChannels.Name = "cbChannels"; - this.cbChannels.Size = new System.Drawing.Size(149, 21); - this.cbChannels.TabIndex = 4; - // - // nupThreadCount - // - this.nupThreadCount.Location = new System.Drawing.Point(82, 139); - this.nupThreadCount.Name = "nupThreadCount"; - this.nupThreadCount.Size = new System.Drawing.Size(70, 20); - this.nupThreadCount.TabIndex = 12; - this.nupThreadCount.Value = new decimal(new int[] { - 50, - 0, - 0, - 0}); - // - // lbChannel - // - this.lbChannel.AutoSize = true; - this.lbChannel.Location = new System.Drawing.Point(224, 38); - this.lbChannel.Name = "lbChannel"; - this.lbChannel.Size = new System.Drawing.Size(49, 13); - this.lbChannel.TabIndex = 3; - this.lbChannel.Text = "Channel:"; - // - // lbScanCount - // - this.lbScanCount.AutoSize = true; - this.lbScanCount.Location = new System.Drawing.Point(12, 203); - this.lbScanCount.Name = "lbScanCount"; - this.lbScanCount.Size = new System.Drawing.Size(102, 13); - this.lbScanCount.TabIndex = 13; - this.lbScanCount.Text = "Messages scanned:"; - // - // lbUsername - // - this.lbUsername.AutoSize = true; - this.lbUsername.Location = new System.Drawing.Point(12, 9); - this.lbUsername.Name = "lbUsername"; - this.lbUsername.Size = new System.Drawing.Size(58, 13); - this.lbUsername.TabIndex = 2; - this.lbUsername.Text = "Username:"; - // - // lbDownload - // - this.lbDownload.AutoSize = true; - this.lbDownload.Location = new System.Drawing.Point(234, 203); - this.lbDownload.Name = "lbDownload"; - this.lbDownload.Size = new System.Drawing.Size(92, 13); - this.lbDownload.TabIndex = 14; - this.lbDownload.Text = "Files downloaded:"; - // - // cbGuilds - // - this.cbGuilds.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbGuilds.FormattingEnabled = true; - this.cbGuilds.Location = new System.Drawing.Point(52, 32); - this.cbGuilds.Name = "cbGuilds"; - this.cbGuilds.Size = new System.Drawing.Size(164, 21); - this.cbGuilds.TabIndex = 1; - this.cbGuilds.SelectedIndexChanged += new System.EventHandler(this.cbGuilds_SelectedIndexChanged); - // - // lbGuild - // - this.lbGuild.AutoSize = true; - this.lbGuild.Location = new System.Drawing.Point(12, 35); - this.lbGuild.Name = "lbGuild"; - this.lbGuild.Size = new System.Drawing.Size(34, 13); - this.lbGuild.TabIndex = 0; - this.lbGuild.Text = "Guild:"; - // - // cbSkip - // - this.cbSkip.AutoSize = true; - this.cbSkip.Checked = true; - this.cbSkip.CheckState = System.Windows.Forms.CheckState.Checked; - this.cbSkip.Location = new System.Drawing.Point(227, 141); - this.cbSkip.Name = "cbSkip"; - this.cbSkip.Size = new System.Drawing.Size(106, 17); - this.cbSkip.TabIndex = 16; - this.cbSkip.Text = "Skip existing files"; - this.cbSkip.UseVisualStyleBackColor = true; - // - // lbCopyright - // - this.lbCopyright.AutoSize = true; - this.lbCopyright.Location = new System.Drawing.Point(12, 274); - this.lbCopyright.Name = "lbCopyright"; - this.lbCopyright.Size = new System.Drawing.Size(151, 13); - this.lbCopyright.TabIndex = 22; - this.lbCopyright.Text = "Copyright (c) 2017 by Serraniel"; - // - // lbGithub - // - this.lbGithub.AutoSize = true; - this.lbGithub.Location = new System.Drawing.Point(169, 274); - this.lbGithub.Name = "lbGithub"; - this.lbGithub.Size = new System.Drawing.Size(40, 13); - this.lbGithub.TabIndex = 23; - this.lbGithub.TabStop = true; - this.lbGithub.Text = "GitHub"; - this.lbGithub.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lbGithub_LinkClicked); - // - // lbAbout - // - this.lbAbout.AutoSize = true; - this.lbAbout.Location = new System.Drawing.Point(223, 274); - this.lbAbout.Name = "lbAbout"; - this.lbAbout.Size = new System.Drawing.Size(35, 13); - this.lbAbout.TabIndex = 24; - this.lbAbout.TabStop = true; - this.lbAbout.Text = "About"; - this.lbAbout.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lbAbout_LinkClicked); - // - // lbVersion - // - this.lbVersion.Location = new System.Drawing.Point(328, 274); - this.lbVersion.Name = "lbVersion"; - this.lbVersion.Size = new System.Drawing.Size(100, 15); - this.lbVersion.TabIndex = 26; - this.lbVersion.TabStop = true; - this.lbVersion.Text = "version"; - this.lbVersion.TextAlign = System.Drawing.ContentAlignment.TopRight; - this.lbVersion.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lbVersion_LinkClicked); - // - // MainForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(439, 296); - this.Controls.Add(this.lbVersion); - this.Controls.Add(this.lbAbout); - this.Controls.Add(this.lbGithub); - this.Controls.Add(this.lbCopyright); - this.Controls.Add(this.cbSkip); - this.Controls.Add(this.lbDownload); - this.Controls.Add(this.lbScanCount); - this.Controls.Add(this.nupThreadCount); - this.Controls.Add(this.lbThread); - this.Controls.Add(this.btnDownload); - this.Controls.Add(this.tbxPath); - this.Controls.Add(this.lbPath); - this.Controls.Add(this.btnSearch); - this.Controls.Add(this.dtpLimit); - this.Controls.Add(this.cbLimitDate); - this.Controls.Add(this.cbChannels); - this.Controls.Add(this.lbChannel); - this.Controls.Add(this.lbUsername); - this.Controls.Add(this.cbGuilds); - this.Controls.Add(this.lbGuild); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.Name = "MainForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Discord Media Loader"; - this.Shown += new System.EventHandler(this.MainForm_Shown); - ((System.ComponentModel.ISupportInitialize)(this.nupThreadCount)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label lbPath; - private System.Windows.Forms.Button btnSearch; - private System.Windows.Forms.TextBox tbxPath; - private System.Windows.Forms.DateTimePicker dtpLimit; - private System.Windows.Forms.Button btnDownload; - private System.Windows.Forms.CheckBox cbLimitDate; - private System.Windows.Forms.Label lbThread; - private System.Windows.Forms.ComboBox cbChannels; - private System.Windows.Forms.NumericUpDown nupThreadCount; - private System.Windows.Forms.Label lbChannel; - private System.Windows.Forms.Label lbScanCount; - private System.Windows.Forms.Label lbUsername; - private System.Windows.Forms.Label lbDownload; - private System.Windows.Forms.ComboBox cbGuilds; - private System.Windows.Forms.Label lbGuild; - private System.Windows.Forms.CheckBox cbSkip; - private System.Windows.Forms.Label lbCopyright; - private System.Windows.Forms.LinkLabel lbGithub; - private System.Windows.Forms.LinkLabel lbAbout; - private System.Windows.Forms.LinkLabel lbVersion; - } -} \ No newline at end of file diff --git a/Discord Media Loader/MainForm.cs b/Discord Media Loader/MainForm.cs deleted file mode 100644 index 72b1dfe..0000000 --- a/Discord Media Loader/MainForm.cs +++ /dev/null @@ -1,317 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows.Forms; -using Discord; -using Discord.Net; -using Discord_Media_Loader.Helper; -using ConnectionState = Discord.ConnectionState; - -namespace Discord_Media_Loader -{ - public partial class MainForm : Form - { - private DiscordClient Client { get; } = new DiscordClient(); - private event EventHandler UpdateProgress; - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public MainForm() - { - InitializeComponent(); - - UpdateProgress += (s, e) => - { - SetControlPropertyThreadSafe(lbDownload, "Text", $"Files downloaded: {e.Downloaded}"); - SetControlPropertyThreadSafe(lbScanCount, "Text", $"Messages scanned: {e.Scanned}"); - }; - } - - private delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue); - - private static void SetControlPropertyThreadSafe(Control control, string propertyName, object propertyValue) - { - if (control.InvokeRequired) - { - control.Invoke(new SetControlPropertyThreadSafeDelegate(SetControlPropertyThreadSafe), control, propertyName, propertyValue); - - } - else - { - control.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, null, control, new[] { propertyValue }); - } - } - - public async Task Login() - { - var email = Properties.Settings.Default.email; - var abort = false; - - while (Client.State != ConnectionState.Connected && !abort) - { - string password; - - if (LoginForm.Exec(ref email, out password)) - { - try - { - Cursor = Cursors.WaitCursor; - try - { - await Client.Connect(email, password); - - Properties.Settings.Default.email = email; - Properties.Settings.Default.Save(); - } - finally - { - Cursor = Cursors.Default; - } - } - catch (HttpException) - { - // ignore http exception on invalid login - } - } - else - { - abort = true; - } - } - - return !abort; - } - - private async void MainForm_Shown(object sender, EventArgs e) - { - lbVersion.Text = $"v{VersionHelper.CurrentVersion}"; - SetEnabled(false); - await CheckForUpdates(); - - if (!await Login()) - { - Close(); - } - else - { - cbGuilds.Items.AddRange((from g in Client.Servers orderby g.Name select g.Name).ToArray()); - cbGuilds.SelectedIndex = 0; - lbUsername.Text = $"Username: {Client.CurrentUser.Name}#{Client.CurrentUser.Discriminator}"; - - SetEnabled(true); - } - } - - private Server FindServerByName(string name) - { - return (from s in Client.Servers where s.Name == name select s).FirstOrDefault(); - } - - private Channel FindChannelByName(Server server, string name) - { - return (from c in server.TextChannels where c.Name == name select c).FirstOrDefault(); - } - - private void SetEnabled(bool enabled) - { - foreach (Control c in Controls) - { - SetControlPropertyThreadSafe(c, "Enabled", enabled); - } - } - - private void cbGuilds_SelectedIndexChanged(object sender, EventArgs e) - { - Cursor = Cursors.WaitCursor; - try - { - Server guild = FindServerByName(cbGuilds.Text); - - if (guild != null) - { - cbChannels.Items.Clear(); - cbChannels.Items.AddRange((from c in guild.TextChannels orderby c.Position select c.Name).ToArray()); - - cbChannels.SelectedIndex = 0; - } - } - finally - { - Cursor = Cursors.Default; - } - } - - private void cbLimitDate_CheckedChanged(object sender, EventArgs e) - { - dtpLimit.Enabled = cbLimitDate.Checked; - } - - private void btnSearch_Click(object sender, EventArgs e) - { - var dlg = new FolderBrowserDialog(); - if (dlg.ShowDialog() == DialogResult.OK) - { - tbxPath.Text = dlg.SelectedPath; - } - } - - private void OnUpdateProgress(UpdateProgessEventArgs e) - { - EventHandler handler = UpdateProgress; - handler?.Invoke(this, e); - } - - private static long DateTimeToUnixTimeStamp(DateTime dateTime) - { - TimeSpan elapsedTime = dateTime - Epoch; - return (long)elapsedTime.TotalSeconds; - } - - - private void btnDownload_Click(object sender, EventArgs e) - { - var path = tbxPath.Text; - var useStopDate = cbLimitDate.Checked; - var stopDate = dtpLimit.Value; - var threadLimit = nupThreadCount.Value; - var skipExisting = cbSkip.Checked; - - if (!Directory.Exists(path)) - { - MessageBox.Show("Please enter an existing directory."); - return; - } - - SetEnabled(false); - - var guild = FindServerByName(cbGuilds.Text); - var channel = FindChannelByName(guild, cbChannels.Text); - - var clients = new List(); - - var limit = 100; - var stop = false; - var lastId = ulong.MaxValue; - var isFirst = true; - - ulong msgScanCount = 0; - ulong downloadCount = 0; - var locker = new object(); - - Task.Run(async () => - { - - while (!stop) - { - Discord.Message[] messages; - - if (isFirst) - messages = await channel.DownloadMessages(limit, null); - else - messages = await channel.DownloadMessages(limit, lastId); - - isFirst = false; - - foreach (var m in messages) - { - if (m.Id < lastId) - lastId = m.Id; - - if (useStopDate && m.Timestamp < stopDate.Date) - { - stop = true; - continue; - } - - foreach (var a in m.Attachments) - { - if (!path.EndsWith(@"\")) - path += @"\"; - - var fname = $"{guild.Name}_{channel.Name}_{DateTimeToUnixTimeStamp(m.Timestamp)}_{a.Filename}"; - fname = Path.GetInvalidFileNameChars().Aggregate(fname, (current, c) => current.Replace(c, '-')); - fname = path + fname; - - if (skipExisting && File.Exists(fname)) - continue; - - while (clients.Count >= threadLimit) - { - // wait - } - var wc = new WebClient(); - clients.Add(wc); - - wc.DownloadFileCompleted += (wcSender, wcE) => - { - clients.Remove(wc); - lock (locker) - { - downloadCount++; - OnUpdateProgress(new UpdateProgessEventArgs() { Downloaded = downloadCount, Scanned = msgScanCount }); - } - }; - - wc.DownloadFileAsync(new Uri(a.Url), fname); - } - - msgScanCount++; - OnUpdateProgress(new UpdateProgessEventArgs() { Downloaded = downloadCount, Scanned = msgScanCount }); - } - - stop = stop || messages.Length < limit; - } - - await Task.Run(() => - { - while (clients.Count > 0) - { - // wait until download finished - } - }); - - Process.Start(path); - SetEnabled(true); - }); - } - - private void lbGithub_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - Process.Start("https://github.com/Serraniel/DiscordMediaLoader/releases"); - } - - private void lbAbout_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - MessageBox.Show(Properties.Resources.AboutString); - } - - private async Task CheckForUpdates(bool manually = false) - { - if (VersionHelper.CurrentVersion < await VersionHelper.GetLatestReleaseVersion()) - { - if (MessageBox.Show("A new version is available, do you want to update now?", "Update available", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - - Process.Start(await VersionHelper.DownloadLatestReleaseVersion()); - } - else if (manually) - { - MessageBox.Show("You already use the newest version."); - } - } - - private void lbVersion_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - CheckForUpdates(true); - } - } - - internal class UpdateProgessEventArgs : EventArgs - { - internal ulong Scanned { get; set; } - internal ulong Downloaded { get; set; } - } -} diff --git a/Discord Media Loader/Program.cs b/Discord Media Loader/Program.cs index bd15ed5..7ce9201 100644 --- a/Discord Media Loader/Program.cs +++ b/Discord Media Loader/Program.cs @@ -1,20 +1,32 @@ using System; using System.Windows.Forms; -using Discord_Media_Loader.Helper; +using DML.Application.Classes; +using Nito.AsyncEx; namespace Discord_Media_Loader { static class Program { - /// - /// Der Haupteinstiegspunkt für die Anwendung. - /// [STAThread] - static void Main() - { + static void Main(string[] paramStrings) + { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm()); + + var splashScreen = new FrmSplash(); + if (splashScreen.ShowDialog() == DialogResult.OK) + { + DoLaunch(paramStrings); + } + else + { + Application.Restart(); + } + } + + private static void DoLaunch(string[] paramStrings) + { + AsyncContext.Run(() => Core.Run(paramStrings)); } } } diff --git a/Discord Media Loader/Properties/AssemblyInfo.cs b/Discord Media Loader/Properties/AssemblyInfo.cs index 4760545..0f2431c 100644 --- a/Discord Media Loader/Properties/AssemblyInfo.cs +++ b/Discord Media Loader/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Allgemeine Informationen über eine Assembly werden über die folgenden @@ -7,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyTitle("Discord Media Loader")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("Serraniel")] [assembly: AssemblyProduct("Discord Media Loader")] [assembly: AssemblyCopyright("Copyright © 2017 by Serraniel")] [assembly: AssemblyTrademark("")] @@ -19,7 +20,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird -[assembly: Guid("83e64057-7010-4df3-a011-f7e536508407")] +[assembly: Guid("edc92554-dbc1-4f9c-9317-379a8bf441e8")] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // @@ -31,5 +32,5 @@ using System.Runtime.InteropServices; // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.99.0")] -[assembly: AssemblyFileVersion("0.2.99.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Discord Media Loader/Properties/Resources.Designer.cs b/Discord Media Loader/Properties/Resources.Designer.cs index d6f62aa..aa3b55e 100644 --- a/Discord Media Loader/Properties/Resources.Designer.cs +++ b/Discord Media Loader/Properties/Resources.Designer.cs @@ -61,19 +61,12 @@ namespace Discord_Media_Loader.Properties { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Discord Media Loader by Serraniel - Apache 2.0 License - ///https://github.com/Serraniel/DiscordMediaLoader/ - /// - ///Made with: - ///Discord.Net (Copyright (c) 2015 RogueException - MIT License) - ///Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) - ///Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) - ///RestSharp (Copyright (c) restsharp - Apache 2.0 License) - ///WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) ähnelt. + /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. /// - internal static string AboutString { + internal static System.Drawing.Bitmap Serraniel_Logo4_NO_BG { get { - return ResourceManager.GetString("AboutString", resourceCulture); + object obj = ResourceManager.GetObject("Serraniel_Logo4_NO_BG", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } } } diff --git a/Discord Media Loader/Properties/Resources.resx b/Discord Media Loader/Properties/Resources.resx index d6b0a66..3691894 100644 --- a/Discord Media Loader/Properties/Resources.resx +++ b/Discord Media Loader/Properties/Resources.resx @@ -117,15 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Discord Media Loader by Serraniel - Apache 2.0 License -https://github.com/Serraniel/DiscordMediaLoader/ - -Made with: -Discord.Net (Copyright (c) 2015 RogueException - MIT License) -Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) -Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) -RestSharp (Copyright (c) restsharp - Apache 2.0 License) -WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) + + + ..\Resources\Serraniel-Logo4-NO-BG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/Discord Media Loader/Properties/Settings.Designer.cs b/Discord Media Loader/Properties/Settings.Designer.cs index 89a3e69..8dd3b2e 100644 --- a/Discord Media Loader/Properties/Settings.Designer.cs +++ b/Discord Media Loader/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Discord_Media_Loader.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -22,17 +22,5 @@ namespace Discord_Media_Loader.Properties { return defaultInstance; } } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string email { - get { - return ((string)(this["email"])); - } - set { - this["email"] = value; - } - } } } diff --git a/Discord Media Loader/Properties/Settings.settings b/Discord Media Loader/Properties/Settings.settings index 1b74a55..3964565 100644 --- a/Discord Media Loader/Properties/Settings.settings +++ b/Discord Media Loader/Properties/Settings.settings @@ -1,9 +1,7 @@  - - - - - - - - \ No newline at end of file + + + + + + diff --git a/Discord Media Loader/Resources/Serraniel-Logo4-NO-BG.png b/Discord Media Loader/Resources/Serraniel-Logo4-NO-BG.png new file mode 100644 index 0000000..aeb98e3 Binary files /dev/null and b/Discord Media Loader/Resources/Serraniel-Logo4-NO-BG.png differ diff --git a/Discord Media Loader/Serraniel-Logo4-ABGERUNDET.ico b/Discord Media Loader/Serraniel-Logo4-ABGERUNDET.ico new file mode 100644 index 0000000..896024e Binary files /dev/null and b/Discord Media Loader/Serraniel-Logo4-ABGERUNDET.ico differ diff --git a/Discord Media Loader/packages.config b/Discord Media Loader/packages.config index 6f2b978..eff2573 100644 --- a/Discord Media Loader/packages.config +++ b/Discord Media Loader/packages.config @@ -1,9 +1,64 @@  - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index ed3e4fa..76946ef 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,14 @@ Apache License 2.0 ## Requirements * [.Net Framework 4.6](https://www.microsoft.com/en-us/download/details.aspx?id=48137) by Microsoft + +## How to use +First things first: **Do not use if you have MFA enabled** as long as login is only supported via username and password. That might get you lost your account. A switch to a token based login will come soon™! + +Otherwise you may just do the following steps: + 1. Login + 2. Select a guild + 3. Select a channel + 4. Select a directory to save the files + 5. Do other settings if wished + 6. Press the magic button to download stuff