├── Discord Media Loader ├── Serraniel-64x64.ico ├── Serraniel-Logo4-ABGERUNDET.ico ├── Resources │ └── Serraniel-Logo4-NO-BG.png ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Program.cs ├── Helper │ ├── VersionHelper.cs │ └── TaskBarProgress.cs ├── FrmSplash.cs ├── App.config ├── FrmDownload.Designer.cs ├── FrmSplash.Designer.cs ├── FrmDownload.cs ├── packages.config └── Discord Media Loader.csproj ├── Discord Media Loader.Application ├── Resources │ └── Serraniel-Logo4-NO-BG.png ├── FrmInternalSplash.cs ├── Helper │ └── IdentifiedString.cs ├── Dialogs │ ├── LoginDialog.cs │ └── LoginDialog.Designer.cs ├── Classes │ ├── Settings.cs │ ├── Job.cs │ ├── JobScheduler.cs │ └── Core.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── app.config ├── FrmInternalSplash.Designer.cs ├── packages.config ├── MainForm.cs └── DML.Application.csproj ├── DML.Client ├── DML.Client.csproj └── DMLClient.cs ├── DML.Core ├── DML.Core.Old.csproj ├── Classes │ ├── Settings.cs │ ├── Job.cs │ └── JobScheduler.cs └── Core.cs ├── DML.AppCore └── DML.AppCore.csproj ├── README.md ├── Discord Media Loader.sln ├── .gitattributes ├── .gitignore └── LICENSE /Discord Media Loader/Serraniel-64x64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/DiscordMediaLoader/master/Discord Media Loader/Serraniel-64x64.ico -------------------------------------------------------------------------------- /Discord Media Loader/Serraniel-Logo4-ABGERUNDET.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/DiscordMediaLoader/master/Discord Media Loader/Serraniel-Logo4-ABGERUNDET.ico -------------------------------------------------------------------------------- /Discord Media Loader/Resources/Serraniel-Logo4-NO-BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/DiscordMediaLoader/master/Discord Media Loader/Resources/Serraniel-Logo4-NO-BG.png -------------------------------------------------------------------------------- /Discord Media Loader.Application/Resources/Serraniel-Logo4-NO-BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/DiscordMediaLoader/master/Discord Media Loader.Application/Resources/Serraniel-Logo4-NO-BG.png -------------------------------------------------------------------------------- /DML.Client/DML.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.4 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Discord Media Loader/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/FrmInternalSplash.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace DML.Application 4 | { 5 | public partial class FrmInternalSplash : Form 6 | { 7 | public FrmInternalSplash() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DML.Core/DML.Core.Old.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DML.AppCore/DML.AppCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.4 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Helper/IdentifiedString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DML.Application.Helper 8 | { 9 | internal class IdentifiedString 10 | { 11 | internal T Id { get; set; } 12 | internal string Caption { get; set; } 13 | 14 | internal IdentifiedString(T id, string caption) 15 | { 16 | Id = id; 17 | Caption = caption; 18 | } 19 | 20 | public override string ToString() => Caption; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Discord Media Loader/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using DML.Application.Classes; 4 | using Nito.AsyncEx; 5 | 6 | namespace Discord_Media_Loader 7 | { 8 | static class Program 9 | { 10 | [STAThread] 11 | static void Main(string[] paramStrings) 12 | { 13 | Application.EnableVisualStyles(); 14 | Application.SetCompatibleTextRenderingDefault(false); 15 | 16 | var splashScreen = new FrmSplash(); 17 | if (splashScreen.ShowDialog() == DialogResult.OK) 18 | { 19 | DoLaunch(paramStrings); 20 | } 21 | else 22 | { 23 | Application.Restart(); 24 | } 25 | } 26 | 27 | private static void DoLaunch(string[] paramStrings) 28 | { 29 | AsyncContext.Run(() => Core.Run(paramStrings)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DML.Client/DMLClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Discord; 3 | using Discord.WebSocket; 4 | 5 | namespace DML.Client 6 | { 7 | public static class DMLClient 8 | { 9 | public static DiscordSocketClient Client { get; set; } = new DiscordSocketClient(new DiscordSocketConfig(){DefaultRetryMode = RetryMode.RetryRatelimit|RetryMode.RetryTimeouts}); 10 | 11 | public static async Task Login(string token) 12 | { 13 | await Client.LoginAsync(TokenType.User, token); 14 | await Client.StartAsync(); 15 | await Task.Delay(1000); 16 | 17 | while (Client.LoginState == LoginState.LoggingIn || Client.ConnectionState == ConnectionState.Connecting) 18 | { 19 | // wait 20 | } 21 | 22 | return Client.LoginState == LoginState.LoggedIn && Client.ConnectionState == ConnectionState.Connected; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Discord Media Loader/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion:4.0.30319.42000 5 | // 6 | // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn 7 | // der Code erneut generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Discord_Media_Loader.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Dialogs/LoginDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using DML.Application.Classes; 4 | using static SweetLib.Utils.Logger.Logger; 5 | 6 | namespace DML.Application.Dialogs 7 | { 8 | public partial class LoginDialog : Form 9 | { 10 | public LoginDialog() 11 | { 12 | InitializeComponent(); 13 | } 14 | 15 | private void LoginDialog_Shown(object sender, EventArgs e) 16 | { 17 | Trace("Login dialog shown."); 18 | edToken.Text = Core.Settings.LoginToken; 19 | } 20 | 21 | private void LoginDialog_FormClosing(object sender, FormClosingEventArgs e) 22 | { 23 | Trace($"Closing login dialog. Result: {DialogResult}"); 24 | if (DialogResult != DialogResult.OK) 25 | return; 26 | 27 | Debug("Adjusting login settings..."); 28 | Core.Settings.LoginToken = edToken.Text; 29 | 30 | Core.Settings.Store(); 31 | } 32 | 33 | private void btnOk_Click(object sender, EventArgs e) 34 | { 35 | Trace("btnOk pressed."); 36 | DialogResult = DialogResult.OK; 37 | } 38 | 39 | private void btnAbort_Click(object sender, EventArgs e) 40 | { 41 | Trace("btnAbort pressed."); 42 | DialogResult = DialogResult.Abort; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Media Loader 2 | 3 | # PROBABLY DISCONTINUED!! THIS PROJECT IS ON HOLD DUE TO DISCORD DOUBLING DOWN ON SELFBOTS (THAT DO VIOLATE TOS AFTER ALL). LATEST COMMIT SHOULD REMAIN STABLE!! 4 | 5 | *Discord Media Loader* is a small tool for downloading all attachments of *Discord* servers. 6 | 7 | **[Download](https://github.com/Serraniel/DiscordMediaLoader/releases)** 8 | 9 | 10 | ## License 11 | Apache License 2.0 12 | 13 | 14 | ## Functionality 15 | * Log into your discord account and gathering information about servers (guilds) and their channels 16 | * Downloading attachments 17 | * You can optionally specify a date. Only newer messages will be scanned 18 | * You can specify a destination path 19 | * You can specify amount of parallel workers 20 | * You can skip existing for speeding up repeated scans (messages still must be scanned) 21 | 22 | 23 | ## Requirements 24 | * [.Net Framework 4.6](https://www.microsoft.com/en-us/download/details.aspx?id=48137) by Microsoft 25 | 26 | ## How to use 27 | 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™! 28 | 29 | Otherwise you may just do the following steps: 30 | 1. Login 31 | 2. Select a guild 32 | 3. Select a channel 33 | 4. Select a directory to save the files 34 | 5. Do other settings if wished 35 | 6. Press the magic button to download stuff 36 | -------------------------------------------------------------------------------- /DML.Core/Classes/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace DML.Core.Classes 4 | { 5 | internal class Settings 6 | { 7 | public int Id { get; } = 1; // using always unique ID 8 | public string Email { get; set; } 9 | public string Password { get; set; } 10 | public string LoginToken { get; set; } 11 | public bool UseUserData { get; set; } = false; 12 | public bool SavePassword { get; set; } = false; 13 | public LogLevel ApplicactionLogLevel { get; set; } = LogLevel.Info | LogLevel.Warn | LogLevel.Error; 14 | public string OperatingFolder { get; set; } 15 | public string FileNameScheme { get; set; } = @"%guild%\%channel%\%id%"; 16 | public bool SkipExistingFiles { get; set; } = true; 17 | public int ThreadLimit { get; set; } = 50; 18 | 19 | internal void Store() 20 | { 21 | Trace("Getting settings collection..."); 22 | var settingsDB = DML.Core.Core.Database.GetCollection("settings"); 23 | 24 | Debug("Storing settings to database..."); 25 | 26 | if (settingsDB.Exists(_setting => _setting.Id == Id)) 27 | { 28 | Trace("Updating existing value..."); 29 | settingsDB.Update(this); 30 | } 31 | else 32 | { 33 | Trace("Adding new value..."); 34 | settingsDB.Insert(this); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Classes/Settings.cs: -------------------------------------------------------------------------------- 1 | using SweetLib.Utils.Logger; 2 | 3 | namespace DML.Application.Classes 4 | { 5 | public class Settings 6 | { 7 | public int Id { get; } = 1; // using always unique ID 8 | public string Email { get; set; } 9 | public string Password { get; set; } 10 | public string LoginToken { get; set; } 11 | public bool UseUserData { get; set; } = false; 12 | public bool SavePassword { get; set; } = false; 13 | public LogLevel ApplicactionLogLevel { get; set; } = LogLevel.Info | LogLevel.Warn | LogLevel.Error; 14 | public string OperatingFolder { get; set; } 15 | public string FileNameScheme { get; set; } = @"%guild%\%channel%\%id%"; 16 | public bool SkipExistingFiles { get; set; } = true; 17 | public int ThreadLimit { get; set; } = 50; 18 | 19 | public void Store() 20 | { 21 | Logger.Trace("Getting settings collection..."); 22 | var settingsDB = Core.Database.GetCollection("settings"); 23 | 24 | Logger.Debug("Storing settings to database..."); 25 | 26 | if (settingsDB.Exists(_setting => _setting.Id == Id)) 27 | { 28 | Logger.Trace("Updating existing value..."); 29 | settingsDB.Update(this); 30 | } 31 | else 32 | { 33 | Logger.Trace("Adding new value..."); 34 | settingsDB.Insert(this); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Discord Media Loader/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("Discord Media Loader")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Serraniel")] 12 | [assembly: AssemblyProduct("Discord Media Loader")] 13 | [assembly: AssemblyCopyright("Copyright © 2017 - 2018 by Serraniel")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("edc92554-dbc1-4f9c-9317-379a8bf441e8")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 33 | // übernehmen, indem Sie "*" eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.3.0")] 36 | [assembly: AssemblyFileVersion("1.0.3.0")] 37 | -------------------------------------------------------------------------------- /Discord Media Loader/Helper/VersionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using Octokit; 6 | 7 | namespace Discord_Media_Loader.Helper 8 | { 9 | internal class VersionHelper 10 | { 11 | internal static Version CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version; 12 | 13 | internal static Version AppVersion => AssemblyName.GetAssemblyName("Discord Media Loader.Application.dll").Version; 14 | 15 | internal static async Task GetReleaseVersion() 16 | { 17 | var github = new GitHubClient(new ProductHeaderValue("DiscordMedialLoader")); 18 | 19 | var tag = 20 | (await github.Repository.Release.GetAll("Serraniel", "DiscordMediaLoader")).OrderByDescending(x => x.CreatedAt).First().TagName.Replace("v", "") ?? ""; 21 | 22 | var version = new Version(tag); 23 | return version; 24 | } 25 | 26 | internal static async Task DownloadLatestReleaseVersion() 27 | { 28 | return await DownloadVersion(await GetReleaseVersion()); 29 | } 30 | 31 | internal static async Task DownloadVersion(Version version) 32 | { 33 | var github = new GitHubClient(new ProductHeaderValue("DiscordMediaLoader")); 34 | 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(); 35 | 36 | return releaseVersion.Assets.FirstOrDefault()?.BrowserDownloadUrl; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("Discord Media Loader.Application")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Serraniel")] 12 | [assembly: AssemblyProduct("Discord Media Loader.Application")] 13 | [assembly: AssemblyCopyright("Copyright © 2017 - 2018 by Serraniel")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("c130de6a-3237-42b5-be9f-783d1cd104c6")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 33 | // übernehmen, indem Sie "*" eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.1.2.0")] 36 | [assembly: AssemblyFileVersion("1.1.2.0")] 37 | -------------------------------------------------------------------------------- /Discord Media Loader/FrmSplash.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Windows.Forms; 5 | using Discord_Media_Loader.Helper; 6 | 7 | namespace Discord_Media_Loader 8 | { 9 | public partial class FrmSplash : Form 10 | { 11 | public FrmSplash() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private async void FrmSplash_Shown(object sender, EventArgs e) 17 | { 18 | UseWaitCursor = true; 19 | try 20 | { 21 | var releaseVersion = await VersionHelper.GetReleaseVersion(); 22 | if (releaseVersion > VersionHelper.AppVersion) 23 | { 24 | var tmpFile = Path.GetTempFileName(); 25 | var downloadManager = new FrmDownload(tmpFile, await VersionHelper.DownloadVersion(releaseVersion)); 26 | 27 | downloadManager.StartDownload(); 28 | downloadManager.ShowDialog(); 29 | 30 | var tmpFolder = Path.GetTempFileName(); 31 | tmpFolder = Path.Combine(Path.GetFullPath(tmpFolder).Replace(Path.GetFileName(tmpFolder), ""), Path.GetFileNameWithoutExtension(tmpFolder)); 32 | 33 | var di = Directory.CreateDirectory(tmpFolder); 34 | 35 | ZipFile.ExtractToDirectory(tmpFile, tmpFolder); 36 | 37 | foreach (var f in di.GetFiles()) 38 | { 39 | try 40 | { 41 | var fname = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, f.Name); 42 | File.Copy(f.FullName, fname, true); 43 | } 44 | catch (Exception) { } 45 | } 46 | 47 | File.Delete(tmpFile); 48 | DialogResult = DialogResult.Cancel; 49 | } 50 | } 51 | finally 52 | { 53 | UseWaitCursor = false; 54 | } 55 | 56 | DialogResult = DialogResult.OK; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Discord Media Loader.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord Media Loader", "Discord Media Loader\Discord Media Loader.csproj", "{EDC92554-DBC1-4F9C-9317-379A8BF441E8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DML.Application", "Discord Media Loader.Application\DML.Application.csproj", "{C130DE6A-3237-42B5-BE9F-783D1CD104C6}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DML.Client", "DML.Client\DML.Client.csproj", "{045EB4A1-34E7-47E0-867E-E10C40505095}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {EDC92554-DBC1-4F9C-9317-379A8BF441E8}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C130DE6A-3237-42B5-BE9F-783D1CD104C6}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {045EB4A1-34E7-47E0-867E-E10C40505095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {045EB4A1-34E7-47E0-867E-E10C40505095}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {045EB4A1-34E7-47E0-867E-E10C40505095}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {045EB4A1-34E7-47E0-867E-E10C40505095}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0B742DE0-D6AF-4033-9605-863C32A7FFD8} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Discord Media Loader/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Discord Media Loader/Helper/TaskBarProgress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Discord_Media_Loader.Helper 5 | { 6 | internal static class TaskBarProgress 7 | { 8 | internal enum TaskbarStates 9 | { 10 | NoProgress = 0, 11 | Indeterminate = 0x1, 12 | Normal = 0x2, 13 | Error = 0x4, 14 | Paused = 0x8 15 | } 16 | 17 | [ComImport] 18 | [Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")] 19 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 20 | private interface ITaskbarList3 21 | { 22 | // ITaskbarList 23 | [PreserveSig] 24 | void HrInit(); 25 | [PreserveSig] 26 | void AddTab(IntPtr hwnd); 27 | [PreserveSig] 28 | void DeleteTab(IntPtr hwnd); 29 | [PreserveSig] 30 | void ActivateTab(IntPtr hwnd); 31 | [PreserveSig] 32 | void SetActiveAlt(IntPtr hwnd); 33 | 34 | // ITaskbarList2 35 | [PreserveSig] 36 | void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); 37 | 38 | // ITaskbarList3 39 | [PreserveSig] 40 | void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal); 41 | [PreserveSig] 42 | void SetProgressState(IntPtr hwnd, TaskbarStates state); 43 | } 44 | 45 | [Guid("56FDF344-FD6D-11d0-958A-006097C9A090")] 46 | [ClassInterface(ClassInterfaceType.None)] 47 | [ComImport] 48 | private class TaskbarInstance 49 | { 50 | } 51 | 52 | private static ITaskbarList3 taskbarInstance = (ITaskbarList3)new TaskbarInstance(); 53 | private static bool taskbarSupported = Environment.OSVersion.Version >= new Version(6, 1); 54 | 55 | internal static void SetState(IntPtr windowHandle, TaskbarStates taskbarState) 56 | { 57 | if (taskbarSupported) taskbarInstance.SetProgressState(windowHandle, taskbarState); 58 | } 59 | 60 | internal static void SetValue(IntPtr windowHandle, double progressValue, double progressMax) 61 | { 62 | if (taskbarSupported) taskbarInstance.SetProgressValue(windowHandle, (ulong)progressValue, (ulong)progressMax); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Discord Media Loader/FrmDownload.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Discord_Media_Loader 2 | { 3 | partial class FrmDownload 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmDownload)); 32 | this.pgbProgress = new System.Windows.Forms.ProgressBar(); 33 | this.lbStatus = new System.Windows.Forms.Label(); 34 | this.SuspendLayout(); 35 | // 36 | // pgbProgress 37 | // 38 | this.pgbProgress.Location = new System.Drawing.Point(12, 12); 39 | this.pgbProgress.Name = "pgbProgress"; 40 | this.pgbProgress.Size = new System.Drawing.Size(351, 23); 41 | this.pgbProgress.TabIndex = 0; 42 | // 43 | // lbStatus 44 | // 45 | this.lbStatus.AutoSize = true; 46 | this.lbStatus.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 47 | this.lbStatus.ForeColor = System.Drawing.SystemColors.ControlDarkDark; 48 | this.lbStatus.Location = new System.Drawing.Point(12, 47); 49 | this.lbStatus.Name = "lbStatus"; 50 | this.lbStatus.Size = new System.Drawing.Size(37, 13); 51 | this.lbStatus.TabIndex = 1; 52 | this.lbStatus.Text = "Status"; 53 | // 54 | // FrmDownload 55 | // 56 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 57 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 58 | this.ClientSize = new System.Drawing.Size(375, 70); 59 | this.ControlBox = false; 60 | this.Controls.Add(this.lbStatus); 61 | this.Controls.Add(this.pgbProgress); 62 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 63 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 64 | this.Name = "FrmDownload"; 65 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 66 | this.Text = "Discord Media Loader"; 67 | this.ResumeLayout(false); 68 | this.PerformLayout(); 69 | 70 | } 71 | 72 | #endregion 73 | 74 | private System.Windows.Forms.ProgressBar pgbProgress; 75 | private System.Windows.Forms.Label lbStatus; 76 | } 77 | } -------------------------------------------------------------------------------- /Discord Media Loader/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion:4.0.30319.42000 5 | // 6 | // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn 7 | // der Code erneut generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Discord_Media_Loader.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. 17 | /// 18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert 19 | // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. 20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen 21 | // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Discord_Media_Loader.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle 51 | /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap Serraniel_Logo4_NO_BG { 67 | get { 68 | object obj = ResourceManager.GetObject("Serraniel_Logo4_NO_BG", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/FrmInternalSplash.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace DML.Application 2 | { 3 | partial class FrmInternalSplash 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmInternalSplash)); 32 | this.lblName = new System.Windows.Forms.Label(); 33 | this.pbLogo = new System.Windows.Forms.PictureBox(); 34 | ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).BeginInit(); 35 | this.SuspendLayout(); 36 | // 37 | // lblName 38 | // 39 | this.lblName.Font = new System.Drawing.Font("Microsoft Sans Serif", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 40 | this.lblName.Location = new System.Drawing.Point(11, 129); 41 | this.lblName.Name = "lblName"; 42 | this.lblName.Size = new System.Drawing.Size(379, 35); 43 | this.lblName.TabIndex = 3; 44 | this.lblName.Text = "Discord Media Loader"; 45 | this.lblName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 46 | // 47 | // pbLogo 48 | // 49 | this.pbLogo.Image = global::DML.Application.Properties.Resources.Serraniel_Logo4_NO_BG; 50 | this.pbLogo.Location = new System.Drawing.Point(14, 10); 51 | this.pbLogo.Name = "pbLogo"; 52 | this.pbLogo.Size = new System.Drawing.Size(376, 116); 53 | this.pbLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; 54 | this.pbLogo.TabIndex = 2; 55 | this.pbLogo.TabStop = false; 56 | // 57 | // FrmInternalSplash 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.BackColor = System.Drawing.Color.White; 62 | this.ClientSize = new System.Drawing.Size(400, 175); 63 | this.Controls.Add(this.lblName); 64 | this.Controls.Add(this.pbLogo); 65 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 66 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 67 | this.Name = "FrmInternalSplash"; 68 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 69 | this.Text = "Discord Media Loader"; 70 | ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).EndInit(); 71 | this.ResumeLayout(false); 72 | 73 | } 74 | 75 | #endregion 76 | 77 | private System.Windows.Forms.Label lblName; 78 | private System.Windows.Forms.PictureBox pbLogo; 79 | } 80 | } -------------------------------------------------------------------------------- /Discord Media Loader/FrmSplash.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Discord_Media_Loader 2 | { 3 | partial class FrmSplash 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmSplash)); 32 | this.lblName = new System.Windows.Forms.Label(); 33 | this.pbLogo = new System.Windows.Forms.PictureBox(); 34 | ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).BeginInit(); 35 | this.SuspendLayout(); 36 | // 37 | // lblName 38 | // 39 | this.lblName.Font = new System.Drawing.Font("Microsoft Sans Serif", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 40 | this.lblName.Location = new System.Drawing.Point(9, 131); 41 | this.lblName.Name = "lblName"; 42 | this.lblName.Size = new System.Drawing.Size(379, 35); 43 | this.lblName.TabIndex = 1; 44 | this.lblName.Text = "Discord Media Loader"; 45 | this.lblName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 46 | // 47 | // pbLogo 48 | // 49 | this.pbLogo.Image = global::Discord_Media_Loader.Properties.Resources.Serraniel_Logo4_NO_BG; 50 | this.pbLogo.Location = new System.Drawing.Point(12, 12); 51 | this.pbLogo.Name = "pbLogo"; 52 | this.pbLogo.Size = new System.Drawing.Size(376, 116); 53 | this.pbLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; 54 | this.pbLogo.TabIndex = 0; 55 | this.pbLogo.TabStop = false; 56 | // 57 | // FrmSplash 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.BackColor = System.Drawing.Color.White; 62 | this.ClientSize = new System.Drawing.Size(400, 175); 63 | this.Controls.Add(this.lblName); 64 | this.Controls.Add(this.pbLogo); 65 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 66 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 67 | this.Name = "FrmSplash"; 68 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 69 | this.Text = "Discord Media Loader"; 70 | this.Shown += new System.EventHandler(this.FrmSplash_Shown); 71 | ((System.ComponentModel.ISupportInitialize)(this.pbLogo)).EndInit(); 72 | this.ResumeLayout(false); 73 | 74 | } 75 | 76 | #endregion 77 | 78 | private System.Windows.Forms.PictureBox pbLogo; 79 | private System.Windows.Forms.Label lblName; 80 | } 81 | } -------------------------------------------------------------------------------- /Discord Media Loader/FrmDownload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | 8 | namespace Discord_Media_Loader 9 | { 10 | internal partial class FrmDownload : Form 11 | { 12 | private string FileName { get; } 13 | private string Source { get; } 14 | private bool Finished { get; set; } = false; 15 | 16 | internal FrmDownload(string fileName, string source) 17 | { 18 | InitializeComponent(); 19 | 20 | FileName = fileName; 21 | Source = source; 22 | } 23 | 24 | internal void StartDownload(bool waitForDialog = true) 25 | { 26 | Task.Run(() => 27 | { 28 | while (waitForDialog && !Visible) { } 29 | 30 | var wc = new WebClient 31 | { 32 | CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore) 33 | }; 34 | 35 | SetStatus($"Downloading {FileName}..."); 36 | 37 | SetProgress(0, 100); 38 | 39 | wc.DownloadProgressChanged += Wc_DownloadProgressChanged; 40 | wc.DownloadFileCompleted += Wc_DownloadFileCompleted; 41 | 42 | wc.DownloadFileAsync(new Uri(Source), FileName); 43 | 44 | while (!Finished) 45 | { 46 | // wait for download 47 | } 48 | 49 | RequestClose(); 50 | }); 51 | } 52 | 53 | private void Wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 54 | { 55 | SetStatus("Download finished"); 56 | Finished = true; 57 | } 58 | 59 | private void Wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 60 | { 61 | var bytesIn = double.Parse(e.BytesReceived.ToString(CultureInfo.InvariantCulture)); 62 | var totalBytes = double.Parse(e.TotalBytesToReceive.ToString(CultureInfo.InvariantCulture)); 63 | var percentage = bytesIn / totalBytes * 100; 64 | SetProgress(percentage, 100); 65 | } 66 | 67 | delegate void SetStatusCallback(String status); 68 | private void SetStatus(String status) 69 | { 70 | if (InvokeRequired) 71 | { 72 | var callback = new SetStatusCallback(SetStatus); 73 | Invoke(callback, status); 74 | } 75 | else 76 | { 77 | lbStatus.Text = status; 78 | } 79 | } 80 | 81 | delegate void SetProgressCallback(double current, int max); 82 | private void SetProgress(double current, int max) 83 | { 84 | if (InvokeRequired) 85 | { 86 | var callback = new SetProgressCallback(SetProgress); 87 | Invoke(callback, current, max); 88 | } 89 | else 90 | { 91 | pgbProgress.Maximum = max; 92 | pgbProgress.Value = (int)current; 93 | Helper.TaskBarProgress.SetState(Handle, Helper.TaskBarProgress.TaskbarStates.Normal); 94 | Helper.TaskBarProgress.SetValue(Handle, (int)current, max); 95 | } 96 | } 97 | 98 | delegate void RequestCloseCallback(); 99 | private void RequestClose() 100 | { 101 | if (InvokeRequired) 102 | { 103 | var callback = new RequestCloseCallback(RequestClose); 104 | Invoke(callback, new object[] { }); 105 | } 106 | else 107 | { 108 | Close(); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion:4.0.30319.42000 5 | // 6 | // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn 7 | // der Code erneut generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace DML.Application.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. 17 | /// 18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert 19 | // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. 20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen 21 | // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DML.Application.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle 51 | /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Sucht eine lokalisierte Zeichenfolge, die Discord Media Loader by Serraniel - Apache 2.0 License 65 | ///https://github.com/Serraniel/DiscordMediaLoader/ 66 | /// 67 | ///Made with: 68 | ///SweetLib (Copyright (c) 2017 Serraniel - GNU General Public License v3.0) 69 | ///Discord.Net (Copyright (c) 2015 RogueException - MIT License) 70 | ///Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) 71 | ///Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) 72 | ///RestSharp (Copyright (c) restsharp - Apache 2.0 License) 73 | ///WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) 74 | /// [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. 75 | /// 76 | internal static string AboutString { 77 | get { 78 | return ResourceManager.GetString("AboutString", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. 84 | /// 85 | internal static System.Drawing.Bitmap Serraniel_Logo4_NO_BG { 86 | get { 87 | object obj = ResourceManager.GetObject("Serraniel_Logo4_NO_BG", resourceCulture); 88 | return ((System.Drawing.Bitmap)(obj)); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Discord Media Loader/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /DML.Core/Classes/Job.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Discord; 6 | using Discord.WebSocket; 7 | 8 | namespace DML.Core.Classes 9 | { 10 | public class Job 11 | { 12 | public int Id { get; set; } 13 | public ulong GuildId { get; set; } 14 | public ulong ChannelId { get; set; } 15 | public double KnownTimestamp { get; set; } = 0; 16 | private double StopTimestamp { get; set; } = 0; 17 | private bool IsValid { get; set; } = true; 18 | 19 | internal void Store() 20 | { 21 | Debug("Storing job to database..."); 22 | Trace("Getting jobs collection..."); 23 | var jobDb = DML.Core.Core.Database.GetCollection("jobs"); 24 | 25 | Trace("Adding new value..."); 26 | 27 | if (jobDb.Find(x => x.ChannelId == ChannelId && x.GuildId == GuildId).Any()) 28 | { 29 | jobDb.Update(this); 30 | } 31 | else 32 | { 33 | jobDb.Insert(this); 34 | } 35 | } 36 | 37 | internal void Delete() 38 | { 39 | Debug("Deleting job from database..."); 40 | Trace("Getting jobs collection..."); 41 | var jobDb = DML.Core.Core.Database.GetCollection("jobs"); 42 | 43 | Trace("Deleting value..."); 44 | jobDb.Delete(Id); 45 | } 46 | 47 | private SocketGuild FindServerById(ulong id) 48 | { 49 | Trace($"Trying to find server by Id: {id}"); 50 | return (from s in DML.Core.Core.Client.Guilds where s.Id == id select s).FirstOrDefault(); 51 | } 52 | 53 | private SocketTextChannel FindChannelById(SocketGuild server, ulong id) 54 | { 55 | Trace($"Trying to find channel in {server} by id: {id}"); 56 | return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); 57 | } 58 | 59 | internal async Task> Scan() 60 | { 61 | Debug($"Starting scan of guild {GuildId} channel {ChannelId}..."); 62 | var result = new List(); 63 | 64 | var limit = 100; 65 | var lastId = ulong.MaxValue; 66 | var isFirst = true; 67 | var finished = false; 68 | 69 | var guild = FindServerById(GuildId); 70 | var channel = FindChannelById(guild, ChannelId); 71 | 72 | if (Math.Abs(StopTimestamp) < 0.4) 73 | StopTimestamp = KnownTimestamp; 74 | Trace("Initialized scanning parameters."); 75 | 76 | while (!finished) 77 | { 78 | Trace("Entering scanning loop..."); 79 | SocketMessage[] messages; 80 | 81 | Trace($"Downloading next {limit} messages..."); 82 | if (isFirst) 83 | { 84 | messages = await channel.GetMessagesAsync(limit).ToArray() as SocketMessage[]; 85 | } 86 | else 87 | { 88 | messages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray() as SocketMessage[]; 89 | } 90 | Trace($"Downloaded {messages.Length} messages."); 91 | 92 | isFirst = false; 93 | 94 | foreach (var m in messages) 95 | { 96 | if (!IsValid) 97 | return null; 98 | 99 | Debug($"Processing message {m.Id}"); 100 | if (m.Id < lastId) 101 | { 102 | Trace($"Updating lastId ({lastId}) to {m.Id}"); 103 | lastId = m.Id; 104 | } 105 | 106 | if (SweetUtils.DateTimeToUnixTimeStamp(m.CreatedAt.UtcDateTime) <= StopTimestamp) 107 | { 108 | Debug("Found a message with a known timestamp...Stopping scan."); 109 | finished = true; 110 | continue; 111 | } 112 | 113 | Trace($"Message {m.Id} has {m.Attachments.Count} attachments."); 114 | if (m.Attachments.Count > 0) 115 | { 116 | result.Add(m); 117 | DML.Core.Core.Scheduler.TotalAttachments++; 118 | Trace($"Added message {m.Id}"); 119 | } 120 | Debug($"Finished message {m.Id}"); 121 | 122 | DML.Core.Core.Scheduler.MessagesScanned++; 123 | } 124 | 125 | finished = finished || messages.Length < limit; 126 | } 127 | Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}."); 128 | 129 | Trace("Sorting messages..."); 130 | result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime)); 131 | 132 | if (result.Count > 0) 133 | { 134 | Trace("Updating StopTimestamp for next scan..."); 135 | StopTimestamp = SweetUtils.DateTimeToUnixTimeStamp(result[result.Count - 1].CreatedAt.UtcDateTime); 136 | } 137 | 138 | Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}."); 139 | 140 | return result; 141 | } 142 | 143 | internal void Stop() 144 | { 145 | IsValid = false; 146 | } 147 | 148 | internal static IEnumerable RestoreJobs() 149 | { 150 | Debug("Restoring jobs..."); 151 | Trace("Getting jobs collection..."); 152 | var jobDb = DML.Core.Core.Database.GetCollection("jobs"); 153 | 154 | Trace("Creating new empty job list"); 155 | return jobDb.FindAll(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Dialogs/LoginDialog.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace DML.Application.Dialogs 2 | { 3 | partial class LoginDialog 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginDialog)); 32 | this.pnlButtons = new System.Windows.Forms.Panel(); 33 | this.btnAbort = new System.Windows.Forms.Button(); 34 | this.btnOk = new System.Windows.Forms.Button(); 35 | this.lbHowToToken = new System.Windows.Forms.Label(); 36 | this.edToken = new System.Windows.Forms.TextBox(); 37 | this.lbToken = new System.Windows.Forms.Label(); 38 | this.pnlButtons.SuspendLayout(); 39 | this.SuspendLayout(); 40 | // 41 | // pnlButtons 42 | // 43 | this.pnlButtons.BackColor = System.Drawing.SystemColors.ButtonShadow; 44 | this.pnlButtons.Controls.Add(this.btnAbort); 45 | this.pnlButtons.Controls.Add(this.btnOk); 46 | this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Bottom; 47 | this.pnlButtons.Location = new System.Drawing.Point(0, 168); 48 | this.pnlButtons.Name = "pnlButtons"; 49 | this.pnlButtons.Size = new System.Drawing.Size(426, 51); 50 | this.pnlButtons.TabIndex = 0; 51 | // 52 | // btnAbort 53 | // 54 | this.btnAbort.Location = new System.Drawing.Point(348, 16); 55 | this.btnAbort.Name = "btnAbort"; 56 | this.btnAbort.Size = new System.Drawing.Size(75, 23); 57 | this.btnAbort.TabIndex = 1; 58 | this.btnAbort.Text = "&Abort"; 59 | this.btnAbort.UseVisualStyleBackColor = true; 60 | this.btnAbort.Click += new System.EventHandler(this.btnAbort_Click); 61 | // 62 | // btnOk 63 | // 64 | this.btnOk.Location = new System.Drawing.Point(267, 16); 65 | this.btnOk.Name = "btnOk"; 66 | this.btnOk.Size = new System.Drawing.Size(75, 23); 67 | this.btnOk.TabIndex = 0; 68 | this.btnOk.Text = "&Ok"; 69 | this.btnOk.UseVisualStyleBackColor = true; 70 | this.btnOk.Click += new System.EventHandler(this.btnOk_Click); 71 | // 72 | // lbHowToToken 73 | // 74 | this.lbHowToToken.Location = new System.Drawing.Point(7, 58); 75 | this.lbHowToToken.Name = "lbHowToToken"; 76 | this.lbHowToToken.Size = new System.Drawing.Size(412, 87); 77 | this.lbHowToToken.TabIndex = 5; 78 | this.lbHowToToken.Text = resources.GetString("lbHowToToken.Text"); 79 | // 80 | // edToken 81 | // 82 | this.edToken.Location = new System.Drawing.Point(79, 12); 83 | this.edToken.Name = "edToken"; 84 | this.edToken.Size = new System.Drawing.Size(335, 20); 85 | this.edToken.TabIndex = 4; 86 | // 87 | // lbToken 88 | // 89 | this.lbToken.AutoSize = true; 90 | this.lbToken.Location = new System.Drawing.Point(7, 15); 91 | this.lbToken.Name = "lbToken"; 92 | this.lbToken.Size = new System.Drawing.Size(66, 13); 93 | this.lbToken.TabIndex = 3; 94 | this.lbToken.Text = "Login token:"; 95 | // 96 | // LoginDialog 97 | // 98 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 99 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 100 | this.ClientSize = new System.Drawing.Size(426, 219); 101 | this.Controls.Add(this.lbHowToToken); 102 | this.Controls.Add(this.edToken); 103 | this.Controls.Add(this.lbToken); 104 | this.Controls.Add(this.pnlButtons); 105 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 106 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 107 | this.MaximizeBox = false; 108 | this.MinimizeBox = false; 109 | this.Name = "LoginDialog"; 110 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 111 | this.Text = "LoginForm"; 112 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LoginDialog_FormClosing); 113 | this.Shown += new System.EventHandler(this.LoginDialog_Shown); 114 | this.pnlButtons.ResumeLayout(false); 115 | this.ResumeLayout(false); 116 | this.PerformLayout(); 117 | 118 | } 119 | 120 | #endregion 121 | 122 | private System.Windows.Forms.Panel pnlButtons; 123 | private System.Windows.Forms.Button btnAbort; 124 | private System.Windows.Forms.Button btnOk; 125 | private System.Windows.Forms.Label lbHowToToken; 126 | private System.Windows.Forms.TextBox edToken; 127 | private System.Windows.Forms.Label lbToken; 128 | } 129 | } -------------------------------------------------------------------------------- /Discord Media Loader/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\Serraniel-Logo4-NO-BG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Classes/Job.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Discord; 6 | using Discord.WebSocket; 7 | using DML.Application.Classes; 8 | using DML.Client; 9 | using SweetLib.Utils; 10 | using SweetLib.Utils.Extensions; 11 | using static SweetLib.Utils.Logger.Logger; 12 | 13 | namespace DML.AppCore.Classes 14 | { 15 | public class Job 16 | { 17 | public int Id { get; set; } 18 | public ulong GuildId { get; set; } 19 | public ulong ChannelId { get; set; } 20 | public double KnownTimestamp { get; set; } = 0; 21 | private double StopTimestamp { get; set; } = 0; 22 | private bool IsValid { get; set; } = true; 23 | 24 | internal void Store() 25 | { 26 | Debug("Storing job to database..."); 27 | Trace("Getting jobs collection..."); 28 | var jobDb = Core.Database.GetCollection("jobs"); 29 | 30 | Trace("Adding new value..."); 31 | 32 | if (jobDb.Find(x => x.ChannelId == ChannelId && x.GuildId == GuildId).Any()) 33 | { 34 | jobDb.Update(this); 35 | } 36 | else 37 | { 38 | jobDb.Insert(this); 39 | } 40 | } 41 | 42 | public void Delete() 43 | { 44 | Debug("Deleting job from database..."); 45 | Trace("Getting jobs collection..."); 46 | var jobDb = Core.Database.GetCollection("jobs"); 47 | 48 | Trace("Deleting value..."); 49 | jobDb.Delete(Id); 50 | } 51 | 52 | private SocketGuild FindServerById(ulong id) 53 | { 54 | Trace($"Trying to find server by Id: {id}"); 55 | return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); 56 | } 57 | 58 | private SocketTextChannel FindChannelById(SocketGuild server, ulong id) 59 | { 60 | Trace($"Trying to find channel in {server} by id: {id}"); 61 | return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); 62 | } 63 | 64 | internal async Task> Scan() 65 | { 66 | Debug($"Starting scan of guild {GuildId} channel {ChannelId}..."); 67 | var result = new List(); 68 | 69 | var limit = 100; 70 | var lastId = ulong.MaxValue; 71 | var isFirst = true; 72 | var finished = false; 73 | 74 | var guild = FindServerById(GuildId); 75 | var channel = FindChannelById(guild, ChannelId); 76 | 77 | Debug("Checking channel access"); 78 | //channel.GetUser(channel.Guild.CurrentUser.Id); 79 | if (channel.GetUser(channel.Guild.CurrentUser.Id) == null) 80 | { 81 | Info("Skipping channel without access"); 82 | return result; 83 | } 84 | 85 | if (Math.Abs(StopTimestamp) < 0.4) 86 | StopTimestamp = KnownTimestamp; 87 | Trace("Initialized scanning parameters."); 88 | 89 | while (!finished) 90 | { 91 | Trace("Entering scanning loop..."); 92 | var messages = new List(); 93 | 94 | Trace($"Downloading next {limit} messages..."); 95 | if (isFirst) 96 | { 97 | //messages = await channel.GetMessagesAsync(limit).ToArray() as SocketMessage[]; 98 | var realMessages = await channel.GetMessagesAsync(limit).ToArray(); 99 | 100 | foreach (var realMessageArray in realMessages) 101 | { 102 | foreach (var realMessage in realMessageArray) 103 | { 104 | messages.Add(realMessage); 105 | } 106 | } 107 | } 108 | else 109 | { 110 | var realMessages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray(); 111 | 112 | foreach (var realMessageArray in realMessages) 113 | { 114 | foreach (var realMessage in realMessageArray) 115 | { 116 | messages.Add(realMessage); 117 | } 118 | } 119 | 120 | //messages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray() as SocketMessage[]; 121 | } 122 | Trace($"Downloaded {messages.Count} messages."); 123 | 124 | isFirst = false; 125 | 126 | foreach (var m in messages) 127 | { 128 | if (!IsValid) 129 | return null; 130 | 131 | Core.Scheduler.MessagesScanned++; 132 | 133 | Debug($"Processing message {m.Id}"); 134 | if (m.Id < lastId) 135 | { 136 | Trace($"Updating lastId ({lastId}) to {m.Id}"); 137 | lastId = m.Id; 138 | } 139 | 140 | if (m.CreatedAt.UtcDateTime.ToUnixTimeStamp() <= StopTimestamp) 141 | { 142 | Debug("Found a message with a known timestamp...Stopping scan."); 143 | finished = true; 144 | continue; 145 | } 146 | 147 | Trace($"Message {m.Id} has {m.Attachments.Count} attachments."); 148 | if (m.Attachments.Count > 0) 149 | { 150 | result.Add(m); 151 | Core.Scheduler.TotalAttachments += (ulong)m.Attachments.Count; 152 | Trace($"Added message {m.Id}"); 153 | } 154 | Debug($"Finished message {m.Id}"); 155 | } 156 | 157 | finished = finished || messages.Count < limit; 158 | } 159 | Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}."); 160 | 161 | Trace("Sorting messages..."); 162 | result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime)); 163 | 164 | if (result.Count > 0) 165 | { 166 | Trace("Updating StopTimestamp for next scan..."); 167 | StopTimestamp = result[result.Count - 1].CreatedAt.UtcDateTime.ToUnixTimeStamp(); 168 | } 169 | 170 | Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}."); 171 | 172 | return result; 173 | } 174 | 175 | public void Stop() 176 | { 177 | IsValid = false; 178 | } 179 | 180 | public static IEnumerable RestoreJobs() 181 | { 182 | Debug("Restoring jobs..."); 183 | Trace("Getting jobs collection..."); 184 | var jobDb = Core.Database.GetCollection("jobs"); 185 | 186 | Trace("Creating new empty job list"); 187 | return jobDb.FindAll(); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Discord Media Loader by Serraniel - Apache 2.0 License 122 | https://github.com/Serraniel/DiscordMediaLoader/ 123 | 124 | Made with: 125 | SweetLib (Copyright (c) 2017 Serraniel - GNU General Public License v3.0) 126 | Discord.Net (Copyright (c) 2015 RogueException - MIT License) 127 | Newtonsoft.Json (Copyright (c) 2007 James Newton-King - MIT License) 128 | Nito.AsyncEx (Copyright (c) 2014 StephenCleary - MIT License) 129 | RestSharp (Copyright (c) restsharp - Apache 2.0 License) 130 | WebSocket4Net (Copyright (c) kerryjiang - Apache 2.0 License) 131 | LiteDB (Copyright (c) 2014 - 2015 Mauricio David - MIT License) 132 | Octokit (Copyright (c) 2012 GitHub, Inc - MIT License) 133 | 134 | 135 | 136 | ..\Resources\Serraniel-Logo4-NO-BG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 137 | 138 | -------------------------------------------------------------------------------- /DML.Core/Core.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Discord; 7 | using Discord.Net; 8 | using Discord.WebSocket; 9 | using DML.Core.Classes; 10 | 11 | namespace DML.Core 12 | { 13 | public static class Core 14 | { 15 | internal static DiscordSocketClient Client { get; set; } 16 | internal static LiteDatabase Database { get; set; } 17 | internal static Settings Settings { get; set; } 18 | internal static JobScheduler Scheduler { get; } = new JobScheduler(); 19 | 20 | internal static string DataDirectory 21 | => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader"); 22 | 23 | public static async Task Run(string[] paramStrings) 24 | { 25 | try 26 | { 27 | var splash = new FrmInternalSplash(); 28 | splash.Show(); 29 | System.Windows.Forms.Application.DoEvents(); 30 | 31 | Info("Starting up Discord Media Loader application..."); 32 | var useTrace = false; 33 | #if DEBUG 34 | //temporary add debug log level if debugging... 35 | GlobalLogLevel |= LogLevel.Debug; 36 | Debug("Running in debug configuartion. Added log level debug."); 37 | #endif 38 | 39 | Debug($"Parameters: {string.Join(", ", paramStrings)}"); 40 | if (paramStrings.Contains("--trace") || paramStrings.Contains("-t")) 41 | { 42 | useTrace = true; 43 | GlobalLogLevel |= LogLevel.Trace; 44 | Trace("Trace parameter found. Added log level trace."); 45 | } 46 | 47 | Debug($"Application data folder: {DataDirectory}"); 48 | 49 | Trace("Checking application data folder..."); 50 | if (!Directory.Exists(DataDirectory)) 51 | { 52 | Debug("Creating application data folder..."); 53 | Directory.CreateDirectory(DataDirectory); 54 | Trace("Creating application data folder."); 55 | } 56 | 57 | Trace("Initializing profile optimizations..."); 58 | ProfileOptimization.SetProfileRoot(System.Windows.Forms.Application.UserAppDataPath); 59 | ProfileOptimization.StartProfile("profile.opt"); 60 | Trace("Finished initializing profile optimizations."); 61 | 62 | Trace("Trying to identify log memory..."); 63 | var logMemory = DefaultLogMemory as ArchivableConsoleLogMemory; 64 | if (logMemory != null) 65 | { 66 | var logFolder = Path.Combine(DataDirectory, "logs"); 67 | if (!Directory.Exists(logFolder)) 68 | { 69 | Debug("Creating log folder..."); 70 | Directory.CreateDirectory(logFolder); 71 | Trace("Created log folder."); 72 | } 73 | 74 | 75 | var logFile = Path.Combine(logFolder, 76 | SweetUtils.LegalizeFilename($"{DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern)}.log.zip")); 77 | 78 | Trace($"Setting log file: {logFile}"); 79 | logMemory.AutoArchiveOnDispose = true; 80 | logMemory.ArchiveFile = logFile; 81 | } 82 | 83 | Debug("Loading database..."); 84 | Database = new LiteDatabase(Path.Combine(DataDirectory, "config.db")); 85 | Database.Log.Logging += (message) => Trace($"LiteDB: {message}"); 86 | 87 | Debug("Loading settings collection out of database..."); 88 | var settingsDB = Database.GetCollection("settings"); 89 | if (settingsDB.Count() > 1) 90 | { 91 | Warn("Found more than one setting. Loading first one..."); 92 | } 93 | Settings = settingsDB.FindAll().FirstOrDefault(); 94 | if (Settings == null) 95 | { 96 | Warn("Settings not found. Creating new one. This is normal on first start up..."); 97 | Settings = new Settings(); 98 | Settings.Store(); 99 | } 100 | 101 | Debug("Loading jobs collection out of database..."); 102 | Scheduler.JobList = Job.RestoreJobs().ToList(); 103 | 104 | Info("Loaded settings."); 105 | Debug( 106 | $"Settings: Email: {Settings.Email}, password: {(string.IsNullOrEmpty(Settings.Password) ? "not set" : "is set")}, use username: {Settings.UseUserData}, loginToken: {Settings.LoginToken}"); 107 | 108 | Trace("Updating log level..."); 109 | GlobalLogLevel = Settings.ApplicactionLogLevel; 110 | #if DEBUG 111 | //temporary add debug log level if debugging... 112 | GlobalLogLevel |= LogLevel.Debug; 113 | Debug("Running in debug configuartion. Added log level debug."); 114 | #endif 115 | if (useTrace) 116 | { 117 | GlobalLogLevel |= LogLevel.Trace; 118 | Trace("Creating application data folder."); 119 | } 120 | 121 | Debug("Creating discord client..."); 122 | 123 | Client = new DiscordSocketClient(); 124 | Client.Log += (arg) => 125 | { 126 | var logMessage = $"DiscordClient: {arg.Message}"; 127 | switch (arg.Severity) 128 | { 129 | case LogSeverity.Verbose: 130 | Trace(logMessage); 131 | break; 132 | case LogSeverity.Debug: 133 | Trace(logMessage); 134 | break; 135 | case LogSeverity.Info: 136 | Info(logMessage); 137 | break; 138 | case LogSeverity.Warning: 139 | Warn(logMessage); 140 | break; 141 | case LogSeverity.Error: 142 | Error(logMessage); 143 | break; 144 | } 145 | 146 | return Task.CompletedTask; 147 | }; 148 | 149 | 150 | Info("Trying to log into discord..."); 151 | var abort = false; 152 | 153 | Client.Connected += Client_Connected; 154 | 155 | while (Client.LoginState != LoginState.LoggedIn && !abort) 156 | { 157 | Debug(Client.ConnectionState.ToString()); 158 | Debug(Client.LoginState.ToString()); 159 | 160 | Trace("Entering login loop."); 161 | 162 | try 163 | { 164 | if (Client.ConnectionState == ConnectionState.Connecting) 165 | continue; 166 | 167 | if (!string.IsNullOrEmpty(Settings.LoginToken)) 168 | { 169 | Debug("Trying to login with last known token..."); 170 | await Client.LoginAsync(TokenType.User, Settings.LoginToken); 171 | await Task.Delay(1000); 172 | } 173 | 174 | } 175 | catch (HttpException ex) 176 | { 177 | Warn($"Login seems to have failed or gone wrong: {ex.GetType().Name} - {ex.Message}"); 178 | } 179 | 180 | if (Client.LoginState == LoginState.LoggedOut) 181 | { 182 | Settings.Password = string.Empty; 183 | Debug("Showing dialog for username and password..."); 184 | var loginDlg = new LoginDialog(); 185 | loginDlg.ShowDialog(); 186 | Trace("Dialog closed."); 187 | } 188 | } 189 | 190 | Debug("Start checking for invalid jobs..."); 191 | 192 | //Client 193 | 194 | while (Client.Guilds.Count==0) 195 | { 196 | // wait until guilds are loaded 197 | } 198 | 199 | for (var i = Scheduler.JobList.Count - 1; i >= 0; i--) 200 | { 201 | var job = Scheduler.JobList[i]; 202 | var isError = false; 203 | var guild = FindServerById(job.GuildId); 204 | if (guild == null) 205 | isError = true; 206 | else 207 | { 208 | var channel = FindChannelById(guild, job.ChannelId); 209 | if (channel == null) 210 | isError = true; 211 | } 212 | 213 | if (isError) 214 | { 215 | 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", 216 | MessageBoxButtons.OK, MessageBoxIcon.Warning); 217 | 218 | Scheduler.JobList.Remove(job); 219 | Scheduler.RunningJobs.Remove(job.Id); 220 | job.Stop(); 221 | job.Delete(); 222 | } 223 | } 224 | 225 | splash.Close(); 226 | 227 | Info("Starting scheduler..."); 228 | Scheduler.Start(); 229 | 230 | System.Windows.Forms.Application.Run(new MainForm()); 231 | 232 | Info("Stopping scheduler..."); 233 | Scheduler.Stop(); 234 | } 235 | catch (Exception ex) 236 | { 237 | Error($"{ex.Message} occured at: {ex.StackTrace}"); 238 | } 239 | } 240 | 241 | private static Task Client_Connected() 242 | { 243 | Debug("Connected"); 244 | return Task.CompletedTask; 245 | } 246 | 247 | //TODO: this is thrid time we implement this.....this has to be fixed!!! 248 | private static SocketGuild FindServerById(ulong id) 249 | { 250 | Trace($"Trying to find server by Id: {id}"); 251 | return (from s in Core.Client.Guilds where s.Id == id select s).FirstOrDefault(); 252 | } 253 | 254 | private static SocketTextChannel FindChannelById(SocketGuild server, ulong id) 255 | { 256 | Trace($"Trying to find channel in {server} by id: {id}"); 257 | return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using Discord; 9 | using Discord.WebSocket; 10 | using DML.AppCore.Classes; 11 | using DML.Application.Classes; 12 | using DML.Application.Helper; 13 | using DML.Client; 14 | using static DML.Client.DMLClient; 15 | using static SweetLib.Utils.Logger.Logger; 16 | 17 | namespace DML.Application 18 | { 19 | enum OnlineState 20 | { 21 | Online, 22 | Idle, 23 | DoNotDisturb, 24 | Invisible 25 | } 26 | public partial class MainForm : Form 27 | { 28 | private bool IsInitialized { get; set; } = false; 29 | public MainForm() 30 | { 31 | InitializeComponent(); 32 | } 33 | 34 | private void MainForm_Shown(object sender, System.EventArgs e) 35 | { 36 | Trace("MainForm shown executed."); 37 | RefreshComponents(); 38 | 39 | IsInitialized = true; 40 | } 41 | 42 | private void RefreshComponents() 43 | { 44 | Debug("Refreshing components..."); 45 | 46 | lbVersion.Text = $"v{Assembly.GetExecutingAssembly().GetName().Version} Copyright © by Serraniel"; 47 | 48 | Trace("Refreshing operating folder component..."); 49 | edOperatingFolder.Text = Core.Settings.OperatingFolder; 50 | 51 | Trace("Refreshing name scheme component..."); 52 | edNameScheme.Text = Core.Settings.FileNameScheme; 53 | 54 | Trace("Refreshing skip existing files component..."); 55 | cbSkipExisting.Checked = Core.Settings.SkipExistingFiles; 56 | 57 | Trace("Refreshing thread limit component..."); 58 | edThreadLimit.Value = Core.Settings.ThreadLimit; 59 | 60 | if (cbGuild.Items.Count == 0) 61 | { 62 | Trace("Adding guilds to component..."); 63 | 64 | cbGuild.Items.AddRange(DMLClient.Client.Guilds.Where(g => g.Name != null).OrderBy(g => g.Name).Select(g => new IdentifiedString(g.Id, g.Name)).ToArray()); 65 | 66 | cbGuild.SelectedIndex = 0; 67 | Trace("Guild component initialized."); 68 | } 69 | 70 | Trace("Refreshing job list component..."); 71 | var oldIndex = lbxJobs.SelectedIndex; 72 | lbxJobs.Items.Clear(); 73 | foreach (var job in Core.Scheduler.JobList) 74 | { 75 | lbxJobs.Items.Add(new IdentifiedString(job.Id, $"{FindServerById(job.GuildId)?.Name}:{FindChannelById(FindServerById(job.GuildId), job.ChannelId)?.Name}")); 76 | } 77 | lbxJobs.SelectedIndex = oldIndex; 78 | 79 | lbStatus.Text = DMLClient.Client.CurrentUser.Status.ToString(); 80 | } 81 | 82 | private void DoSomethingChanged(object sender, System.EventArgs e) 83 | { 84 | Debug($"DoSomethingChanged excuted by {sender}."); 85 | if (!IsInitialized) 86 | { 87 | Trace("Form not initialized. Leaving DoSomethingChanged..."); 88 | return; 89 | } 90 | 91 | Trace("Updating operating folder..."); 92 | Core.Settings.OperatingFolder = edOperatingFolder.Text; 93 | 94 | Trace("Updating name scheme..."); 95 | Core.Settings.FileNameScheme = edNameScheme.Text; 96 | 97 | Trace("Updating skip existing files..."); 98 | Core.Settings.SkipExistingFiles = cbSkipExisting.Checked; 99 | 100 | Trace("Updating thread limit..."); 101 | Core.Settings.ThreadLimit = (int)edThreadLimit.Value; 102 | 103 | Trace("Storing new settings..."); 104 | Core.Settings.Store(); 105 | 106 | Info("New settings have been saved."); 107 | } 108 | 109 | private void btnSearchFolders_Click(object sender, System.EventArgs e) 110 | { 111 | Trace("Operating folder button pressed."); 112 | using (var browserDialog = new FolderBrowserDialog()) 113 | { 114 | Debug("Showing file browser dialog for operating folder..."); 115 | 116 | browserDialog.SelectedPath = edOperatingFolder.Text; 117 | browserDialog.ShowNewFolderButton = true; 118 | browserDialog.Description = "Select an operating folder..."; 119 | 120 | if (browserDialog.ShowDialog() == DialogResult.OK) 121 | { 122 | edOperatingFolder.Text = browserDialog.SelectedPath; 123 | Debug("Updated operating folder."); 124 | } 125 | } 126 | } 127 | 128 | private SocketGuild FindServerByName(string name) 129 | { 130 | Trace($"Trying to find server by name: {name}"); 131 | return (from s in DMLClient.Client.Guilds where s.Name == name select s).FirstOrDefault(); 132 | } 133 | 134 | private SocketTextChannel FindChannelByName(SocketGuild server, string name) 135 | { 136 | Trace($"Trying to find channel in {server} by name: {name}"); 137 | return (from c in server.TextChannels where c.Name == name select c).FirstOrDefault(); 138 | } 139 | 140 | private SocketGuild FindServerById(ulong id) 141 | { 142 | Trace($"Trying to find server by Id: {id}"); 143 | return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); 144 | } 145 | 146 | private SocketTextChannel FindChannelById(SocketGuild server, ulong id) 147 | { 148 | Trace($"Trying to find channel in {server} by id: {id}"); 149 | return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); 150 | } 151 | 152 | private void cbGuild_SelectedIndexChanged(object sender, System.EventArgs e) 153 | { 154 | Trace("Guild index changed."); 155 | Debug("Updating channel dropdown component..."); 156 | 157 | UseWaitCursor = true; 158 | try 159 | { 160 | var guild = FindServerById(((IdentifiedString)cbGuild.SelectedItem).Id); 161 | 162 | if (guild != null) 163 | { 164 | Trace("Cleaning channel component from old values..."); 165 | cbChannel.Items.Clear(); 166 | 167 | Trace("Adding new channels..."); 168 | 169 | cbChannel.Items.AddRange(guild.TextChannels.OrderBy(c => c.Position).Select(c => new IdentifiedString(c.Id, c.Name)).ToArray()); 170 | 171 | Trace($"Added {cbChannel.Items.Count} channels."); 172 | 173 | cbChannel.SelectedIndex = 0; 174 | } 175 | else 176 | { 177 | Warn($"Guild {cbGuild.Text} could not be found!"); 178 | } 179 | } 180 | finally 181 | { 182 | UseWaitCursor = false; 183 | } 184 | 185 | Debug("Finished updating channel dropdown component."); 186 | } 187 | 188 | private void btnAddJob_Click(object sender, System.EventArgs e) 189 | { 190 | var job = new Job 191 | { 192 | GuildId = ((IdentifiedString)cbGuild.SelectedItem).Id, 193 | ChannelId = ((IdentifiedString)cbChannel.SelectedItem).Id 194 | }; 195 | 196 | if (!(from j in Core.Scheduler.JobList 197 | where j.GuildId == job.GuildId && j.ChannelId == job.ChannelId 198 | select j).Any()) 199 | { 200 | job.Store(); 201 | Core.Scheduler.JobList.Add(job); 202 | } 203 | 204 | RefreshComponents(); 205 | } 206 | 207 | private void btnDelete_Click(object sender, System.EventArgs e) 208 | { 209 | Trace("Deleting job pressed."); 210 | 211 | if (lbxJobs.SelectedIndex < 0) 212 | { 213 | Warn("No job selected."); 214 | MessageBox.Show("No job has been seleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); 215 | } 216 | 217 | var jobId = ((IdentifiedString)lbxJobs.SelectedItem).Id; 218 | 219 | var job = Core.Scheduler.JobList.FirstOrDefault(j => j.Id == jobId); 220 | if (job != null) 221 | { 222 | Core.Scheduler.JobList.Remove(job); 223 | Core.Scheduler.RunningJobs.Remove(job.Id); 224 | job.Stop(); 225 | job.Delete(); 226 | } 227 | 228 | lbxJobs.SelectedIndex = -1; 229 | RefreshComponents(); 230 | } 231 | 232 | private void tmrRefreshProgress_Tick(object sender, System.EventArgs e) 233 | { 234 | var scanned = Core.Scheduler.MessagesScanned; 235 | var totalAttachments = Core.Scheduler.TotalAttachments; 236 | var done = Core.Scheduler.AttachmentsDownloaded; 237 | 238 | var progress = totalAttachments > 0 ? (int)(100 * done / totalAttachments) : 0; 239 | progress = Math.Min(Math.Max(0, progress), 100); 240 | pgbProgress.Maximum = 100; 241 | pgbProgress.Value = progress; 242 | 243 | lbProgress.Text = $"Scanned: {scanned} Downloaded: {done} Open: {totalAttachments - done}"; 244 | } 245 | 246 | private void aboutToolStripMenuItem_Click(object sender, System.EventArgs e) 247 | { 248 | MessageBox.Show(Properties.Resources.AboutString); 249 | } 250 | 251 | private void visitGithubToolStripMenuItem_Click(object sender, System.EventArgs e) 252 | { 253 | Process.Start("https://github.com/Serraniel/DiscordMediaLoader/"); 254 | } 255 | 256 | private async void toolStripDropDownButton1_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) 257 | { 258 | OnlineState state = (OnlineState)Convert.ToInt32(e.ClickedItem.Tag); 259 | 260 | lbStatus.Text = state.ToString(); 261 | tmrTriggerRefresh.Start(); 262 | 263 | switch (state) 264 | { 265 | case OnlineState.Online: 266 | await DMLClient.Client.SetStatusAsync(UserStatus.Online); 267 | break; 268 | case OnlineState.Idle: 269 | await DMLClient.Client.SetStatusAsync(UserStatus.Idle); 270 | break; 271 | case OnlineState.DoNotDisturb: 272 | await DMLClient.Client.SetStatusAsync(UserStatus.DoNotDisturb); 273 | break; 274 | case OnlineState.Invisible: 275 | await DMLClient.Client.SetStatusAsync(UserStatus.Invisible); 276 | break; 277 | } 278 | } 279 | 280 | private void tmrTriggerRefresh_Tick(object sender, EventArgs e) 281 | { 282 | lbStatus.Text = DMLClient.Client.CurrentUser.Status.ToString(); 283 | tmrTriggerRefresh.Stop(); 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /DML.Core/Classes/JobScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Discord.WebSocket; 7 | 8 | namespace DML.Core.Classes 9 | { 10 | internal class JobScheduler 11 | { 12 | private ulong messageScanned = 0; 13 | private ulong totalAttachments = 0; 14 | private ulong attachmentsDownloaded = 0; 15 | 16 | private bool Run { get; set; } = false; 17 | internal List JobList { get; set; } = new List(); 18 | internal Dictionary> RunningJobs = new Dictionary>(); 19 | internal int RunningThreads { get; set; } = 0; 20 | 21 | internal ulong MessagesScanned 22 | { 23 | get 24 | { 25 | lock (this) 26 | { 27 | return messageScanned; 28 | } 29 | } 30 | set 31 | { 32 | lock (this) 33 | { 34 | messageScanned = value; 35 | } 36 | } 37 | } 38 | 39 | internal ulong TotalAttachments 40 | { 41 | get 42 | { 43 | lock (this) 44 | { 45 | return totalAttachments; 46 | } 47 | } 48 | set 49 | { 50 | lock (this) 51 | { 52 | totalAttachments = value; 53 | } 54 | } 55 | } 56 | 57 | internal ulong AttachmentsDownloaded 58 | { 59 | get 60 | { 61 | lock (this) 62 | { 63 | return attachmentsDownloaded; 64 | } 65 | } 66 | set 67 | { 68 | lock (this) 69 | { 70 | attachmentsDownloaded = value; 71 | } 72 | } 73 | } 74 | 75 | internal ulong AttachmentsToDownload => TotalAttachments - AttachmentsDownloaded; 76 | 77 | internal void Stop() 78 | { 79 | Run = false; 80 | } 81 | 82 | internal void Start() 83 | { 84 | Run = true; 85 | 86 | Task.Run(async () => 87 | { 88 | Info("Started JobScheduler..."); 89 | while (Run) 90 | { 91 | Debug("Entering job list handler loop..."); 92 | //foreach (var job in JobList) 93 | for (var i = JobList.Count - 1; i >= 0; i--) 94 | { 95 | var job = JobList[i]; 96 | Debug($"Checking job {job}"); 97 | var hasJob = false; 98 | 99 | Trace("Locking scheduler..."); 100 | lock (this) 101 | { 102 | Trace("Checking if job is already performed..."); 103 | hasJob = RunningJobs.ContainsKey(job.Id); 104 | } 105 | Trace("Unlocked scheduler."); 106 | 107 | if (!hasJob) 108 | { 109 | Debug("Job is not performed yet...Performing job..."); 110 | var queue = new Queue(); 111 | 112 | Trace("Locking scheduler..."); 113 | lock (this) 114 | { 115 | Trace("Adding job to running jobs."); 116 | RunningJobs.Add(job.Id, queue); 117 | } 118 | Trace("Unlocked scheduler."); 119 | 120 | Trace("Issuing job message scan..."); 121 | var messages = await job.Scan(); 122 | 123 | if (messages == null) 124 | continue; 125 | 126 | Trace($"Adding {messages.Count} messages to queue..."); 127 | foreach (var msg in messages) 128 | { 129 | queue.Enqueue(msg); 130 | } 131 | Trace($"Added {queue.Count} messages to queue."); 132 | 133 | if (messages.Count != queue.Count) 134 | Warn("Not all messages have been added into the queue."); 135 | 136 | var startedDownload = false; 137 | 138 | while (!startedDownload) 139 | { 140 | Debug("Entering loop to check thread availability"); 141 | Trace("Locking scheduler..."); 142 | lock (this) 143 | { 144 | Trace($"Checking thread limit. Running: {RunningThreads}, Max: {DML.Core.Core.Settings.ThreadLimit}"); 145 | if (RunningThreads >= DML.Core.Core.Settings.ThreadLimit) 146 | continue; 147 | 148 | RunningThreads++; 149 | startedDownload = true; 150 | } 151 | Trace("Unlocked scheduler."); 152 | } 153 | 154 | Trace("Start downloading job async."); 155 | Task.Run(() => WorkQueue(job.Id)); // do not await to work parallel 156 | } 157 | } 158 | } 159 | }); 160 | } 161 | 162 | private void WorkQueue(int jobId) 163 | { 164 | try 165 | { 166 | Debug("Beginning job download..."); 167 | Trace("Finding job..."); 168 | var job = (from j in JobList where j.Id == jobId select j).FirstOrDefault(); 169 | 170 | if (job == null) 171 | { 172 | Warn($"Associating job not found! JobId: {jobId}"); 173 | return; 174 | } 175 | Trace("Found job."); 176 | 177 | Queue queue; 178 | Trace("Locking scheduler..."); 179 | lock (this) 180 | { 181 | Trace("Finiding queue..."); 182 | if (!RunningJobs.TryGetValue(jobId, out queue)) 183 | { 184 | Warn($"Queue for job {jobId} not found!"); 185 | return; 186 | } 187 | Trace("Queue found."); 188 | } 189 | Trace("Unlocked scheduler."); 190 | 191 | Debug($"Messages to process for job {jobId}: {queue.Count}"); 192 | while (queue.Count > 0) 193 | { 194 | Trace("Locking scheduler..."); 195 | lock (this) 196 | { 197 | Trace("Checking if still a job..."); 198 | if (!RunningJobs.ContainsKey(jobId)) 199 | { 200 | Warn($"Queue for job {jobId} not found!"); 201 | return; 202 | } 203 | Trace("Continue working..."); 204 | } 205 | Trace("Unlocked scheduler."); 206 | 207 | Trace("Dequeueing message..."); 208 | var message = queue.Dequeue(); 209 | 210 | Debug($"Attachments for message {message.Id}: {message.Attachments.Count}"); 211 | foreach (var a in message.Attachments) 212 | { 213 | try 214 | { 215 | var fileName = Path.Combine(DML.Core.Core.Settings.OperatingFolder, DML.Core.Core.Settings.FileNameScheme); 216 | 217 | Trace("Replacing filename placeholders..."); 218 | 219 | var extensionRequired = !fileName.EndsWith("%name%"); 220 | 221 | var serverName = "unknown"; 222 | 223 | var socketTextChannel = message.Channel as SocketTextChannel; 224 | if (socketTextChannel != null) 225 | { 226 | serverName = socketTextChannel.Guild.Name.Replace(":", "").Replace("/", "") 227 | .Replace("\\", ""); 228 | } 229 | 230 | fileName = 231 | fileName.Replace("%guild%", serverName) 232 | .Replace("%channel%", message.Channel.Name) 233 | .Replace("%timestamp%", SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime).ToString()) 234 | .Replace("%name%", a.Filename) 235 | .Replace("%id%", a.Id.ToString()); 236 | 237 | if (extensionRequired) 238 | fileName += Path.GetExtension(a.Filename); 239 | 240 | Trace($"Detemined file name: {fileName}."); 241 | 242 | 243 | if (File.Exists(fileName) && new FileInfo(fileName).Length == a.Size) 244 | { 245 | Debug($"{fileName} already existing with its estimated size. Skipping..."); 246 | continue; 247 | } 248 | 249 | Trace("Determining directory..."); 250 | var fileDirectory = Path.GetDirectoryName(fileName); 251 | 252 | if (!Directory.Exists(fileDirectory)) 253 | { 254 | Info($"Directory {fileDirectory} does not exist. Creating directory..."); 255 | Directory.CreateDirectory(fileDirectory); 256 | Debug("Created directory."); 257 | } 258 | 259 | var wc = new WebClient(); 260 | Debug($"Starting downloading of attachment {a.Id}..."); 261 | 262 | wc.DownloadFile(new Uri(a.Url), fileName); 263 | Debug($"Downloaded attachment {a.Id}."); 264 | 265 | Trace("Updating known timestamp for job..."); 266 | job.KnownTimestamp = SweetUtils.DateTimeToUnixTimeStamp(message.CreatedAt.UtcDateTime); 267 | job.Store(); 268 | } 269 | finally 270 | { 271 | AttachmentsDownloaded++; 272 | } 273 | } 274 | } 275 | } 276 | finally 277 | { 278 | Trace("Locking scheduler..."); 279 | lock (this) 280 | { 281 | Trace($"Removing {jobId} from running jobs..."); 282 | RunningJobs.Remove(jobId); 283 | Trace("Decreasing thread count..."); 284 | RunningThreads--; 285 | } 286 | Trace("Unlocked scheduler."); 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Classes/JobScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using Discord; 8 | using Discord.WebSocket; 9 | using DML.Application.Classes; 10 | using SweetLib.Utils; 11 | using SweetLib.Utils.Extensions; 12 | using SweetLib.Utils.Logger; 13 | 14 | namespace DML.AppCore.Classes 15 | { 16 | public class JobScheduler 17 | { 18 | private ulong messageScanned = 0; 19 | private ulong totalAttachments = 0; 20 | private ulong attachmentsDownloaded = 0; 21 | 22 | private bool Run { get; set; } = false; 23 | public List JobList { get; set; } = new List(); 24 | public Dictionary> RunningJobs = new Dictionary>(); 25 | internal int RunningThreads { get; set; } = 0; 26 | 27 | internal ulong MessagesScanned 28 | { 29 | get 30 | { 31 | lock (this) 32 | { 33 | return messageScanned; 34 | } 35 | } 36 | set 37 | { 38 | lock (this) 39 | { 40 | messageScanned = value; 41 | } 42 | } 43 | } 44 | 45 | internal ulong TotalAttachments 46 | { 47 | get 48 | { 49 | lock (this) 50 | { 51 | return totalAttachments; 52 | } 53 | } 54 | set 55 | { 56 | lock (this) 57 | { 58 | totalAttachments = value; 59 | } 60 | } 61 | } 62 | 63 | internal ulong AttachmentsDownloaded 64 | { 65 | get 66 | { 67 | lock (this) 68 | { 69 | return attachmentsDownloaded; 70 | } 71 | } 72 | set 73 | { 74 | lock (this) 75 | { 76 | attachmentsDownloaded = value; 77 | } 78 | } 79 | } 80 | 81 | public void Stop() 82 | { 83 | Run = false; 84 | } 85 | 86 | public void Start() 87 | { 88 | Run = true; 89 | 90 | Task.Run(async () => 91 | { 92 | Logger.Info("Started JobScheduler..."); 93 | while (Run) 94 | { 95 | try 96 | { 97 | Logger.Debug("Entering job list handler loop..."); 98 | //foreach (var job in JobList) 99 | for (var i = JobList.Count - 1; i >= 0; i--) 100 | { 101 | var job = JobList[i]; 102 | Logger.Debug($"Checking job {job}"); 103 | var hasJob = false; 104 | 105 | Logger.Trace("Locking scheduler..."); 106 | lock (this) 107 | { 108 | Logger.Trace("Checking if job is already performed..."); 109 | hasJob = RunningJobs.ContainsKey(job.Id); 110 | } 111 | Logger.Trace("Unlocked scheduler."); 112 | 113 | if (!hasJob) 114 | { 115 | Logger.Debug("Job is not performed yet...Performing job..."); 116 | var queue = new Queue(); 117 | 118 | Logger.Trace("Locking scheduler..."); 119 | lock (this) 120 | { 121 | Logger.Trace("Adding job to running jobs."); 122 | RunningJobs.Add(job.Id, queue); 123 | } 124 | Logger.Trace("Unlocked scheduler."); 125 | 126 | Logger.Trace("Issuing job message scan..."); 127 | var messages = await job.Scan(); 128 | 129 | if (messages == null) 130 | continue; 131 | 132 | Logger.Trace($"Adding {messages.Count} messages to queue..."); 133 | foreach (var msg in messages) 134 | { 135 | queue.Enqueue(msg); 136 | } 137 | Logger.Trace($"Added {queue.Count} messages to queue."); 138 | 139 | if (messages.Count != queue.Count) 140 | Logger.Warn("Not all messages have been added into the queue."); 141 | 142 | var startedDownload = false; 143 | 144 | while (!startedDownload) 145 | { 146 | Logger.Debug("Entering loop to check thread availability"); 147 | Logger.Trace("Locking scheduler..."); 148 | lock (this) 149 | { 150 | Logger.Trace( 151 | $"Checking thread limit. Running: {RunningThreads}, Max: {Core.Settings.ThreadLimit}"); 152 | if (RunningThreads >= Core.Settings.ThreadLimit) 153 | continue; 154 | 155 | RunningThreads++; 156 | startedDownload = true; 157 | } 158 | Logger.Trace("Unlocked scheduler."); 159 | } 160 | 161 | Logger.Trace("Start downloading job async."); 162 | Task.Run(() => WorkQueue(job.Id)); // do not await to work parallel 163 | } 164 | } 165 | } 166 | catch (Exception ex) 167 | { 168 | Logger.Error(ex.Message); 169 | } 170 | } 171 | }); 172 | } 173 | 174 | private void WorkQueue(int jobId) 175 | { 176 | try 177 | { 178 | Logger.Debug("Beginning job download..."); 179 | Logger.Trace("Finding job..."); 180 | var job = (from j in JobList where j.Id == jobId select j).FirstOrDefault(); 181 | 182 | if (job == null) 183 | { 184 | Logger.Warn($"Associating job not found! JobId: {jobId}"); 185 | return; 186 | } 187 | Logger.Trace("Found job."); 188 | 189 | Queue queue; 190 | Logger.Trace("Locking scheduler..."); 191 | lock (this) 192 | { 193 | Logger.Trace("Finiding queue..."); 194 | if (!RunningJobs.TryGetValue(jobId, out queue)) 195 | { 196 | Logger.Warn($"Queue for job {jobId} not found!"); 197 | return; 198 | } 199 | Logger.Trace("Queue found."); 200 | } 201 | Logger.Trace("Unlocked scheduler."); 202 | 203 | Logger.Debug($"Messages to process for job {jobId}: {queue.Count}"); 204 | while (queue.Count > 0) 205 | { 206 | Logger.Trace("Locking scheduler..."); 207 | lock (this) 208 | { 209 | Logger.Trace("Checking if still a job..."); 210 | if (!RunningJobs.ContainsKey(jobId)) 211 | { 212 | Logger.Warn($"Queue for job {jobId} not found!"); 213 | return; 214 | } 215 | Logger.Trace("Continue working..."); 216 | } 217 | Logger.Trace("Unlocked scheduler."); 218 | 219 | Logger.Trace("Dequeueing message..."); 220 | var message = queue.Dequeue(); 221 | 222 | Logger.Debug($"Attachments for message {message.Id}: {message.Attachments.Count}"); 223 | foreach (var a in message.Attachments) 224 | { 225 | try 226 | { 227 | var fileName = Path.Combine(Core.Settings.OperatingFolder, Core.Settings.FileNameScheme); 228 | 229 | Logger.Trace("Replacing filename placeholders..."); 230 | 231 | var extensionRequired = !fileName.EndsWith("%name%"); 232 | 233 | var serverName = "unknown"; 234 | 235 | var socketTextChannel = message.Channel as SocketTextChannel; 236 | if (socketTextChannel != null) 237 | { 238 | serverName = socketTextChannel.Guild.Name; 239 | serverName = Path.GetInvalidFileNameChars().Aggregate(serverName, (current, c) => current.Replace(c, ' ')); 240 | } 241 | 242 | var channelName = message.Channel.Name; 243 | channelName = Path.GetInvalidFileNameChars().Aggregate(channelName, (current, c) => current.Replace(c, ' ')); 244 | 245 | fileName = 246 | fileName.Replace("%guild%", serverName) 247 | .Replace("%channel%", channelName) 248 | .Replace("%timestamp%", message.CreatedAt.UtcDateTime.ToUnixTimeStamp().ToString()) 249 | .Replace("%name%", a.Filename) 250 | .Replace("%id%", a.Id.ToString()); 251 | 252 | if (extensionRequired) 253 | fileName += Path.GetExtension(a.Filename); 254 | 255 | Logger.Trace($"Detemined file name: {fileName}."); 256 | 257 | 258 | if (File.Exists(fileName) && new FileInfo(fileName).Length == a.Size) 259 | { 260 | Logger.Debug($"{fileName} already existing with its estimated size. Skipping..."); 261 | continue; 262 | } 263 | 264 | Logger.Trace("Determining directory..."); 265 | var fileDirectory = Path.GetDirectoryName(fileName); 266 | 267 | if (!Directory.Exists(fileDirectory)) 268 | { 269 | Logger.Info($"Directory {fileDirectory} does not exist. Creating directory..."); 270 | Directory.CreateDirectory(fileDirectory); 271 | Logger.Debug("Created directory."); 272 | } 273 | 274 | var wc = new WebClient(); 275 | Logger.Debug($"Starting downloading of attachment {a.Id}..."); 276 | 277 | wc.DownloadFile(new Uri(a.Url), fileName); 278 | Logger.Debug($"Downloaded attachment {a.Id}."); 279 | 280 | Logger.Trace("Updating known timestamp for job..."); 281 | job.KnownTimestamp = message.CreatedAt.UtcDateTime.ToUnixTimeStamp(); 282 | job.Store(); 283 | } 284 | finally 285 | { 286 | AttachmentsDownloaded++; 287 | } 288 | } 289 | } 290 | } 291 | finally 292 | { 293 | Logger.Trace("Locking scheduler..."); 294 | lock (this) 295 | { 296 | Logger.Trace($"Removing {jobId} from running jobs..."); 297 | RunningJobs.Remove(jobId); 298 | Logger.Trace("Decreasing thread count..."); 299 | RunningThreads--; 300 | } 301 | Logger.Trace("Unlocked scheduler."); 302 | } 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/Classes/Core.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using Discord; 9 | using Discord.WebSocket; 10 | using DML.AppCore.Classes; 11 | using DML.Application.Dialogs; 12 | using DML.Client; 13 | using LiteDB; 14 | using SharpRaven; 15 | using SharpRaven.Data; 16 | using SweetLib.Utils; 17 | using SweetLib.Utils.Logger; 18 | using SweetLib.Utils.Logger.Memory; 19 | using Logger = SweetLib.Utils.Logger.Logger; 20 | 21 | namespace DML.Application.Classes 22 | { 23 | public static class Core 24 | { 25 | //internal static DiscordSocketClient Client { get; set; } 26 | internal static LiteDatabase Database { get; set; } 27 | internal static Settings Settings { get; set; } 28 | internal static JobScheduler Scheduler { get; } = new JobScheduler(); 29 | internal static RavenClient Raven = new RavenClient("https://0de964231669473e9098b9f6cc1d6278:79d9f2eb24034de199b2a37cc058e0f2@sentry.io/257114"); 30 | 31 | internal static string DataDirectory 32 | => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader"); 33 | 34 | public static async Task Run(string[] paramStrings) 35 | { 36 | try 37 | { 38 | var splash = new FrmInternalSplash(); 39 | splash.Show(); 40 | System.Windows.Forms.Application.DoEvents(); 41 | 42 | Logger.Info("Starting up Discord Media Loader application..."); 43 | var useTrace = false; 44 | #if DEBUG 45 | //temporary add debug log level if debugging... 46 | Logger.GlobalLogLevel |= LogLevel.Debug; 47 | Logger.Debug("Running in debug configuartion. Added log level debug."); 48 | #endif 49 | 50 | Logger.Debug($"Parameters: {string.Join(", ", paramStrings)}"); 51 | if (paramStrings.Contains("--trace") || paramStrings.Contains("-t")) 52 | { 53 | useTrace = true; 54 | Logger.GlobalLogLevel |= LogLevel.Trace; 55 | Logger.Trace("Trace parameter found. Added log level trace."); 56 | } 57 | 58 | Logger.Debug($"Application data folder: {DataDirectory}"); 59 | 60 | Logger.Trace("Checking application data folder..."); 61 | if (!Directory.Exists(DataDirectory)) 62 | { 63 | Logger.Debug("Creating application data folder..."); 64 | Directory.CreateDirectory(DataDirectory); 65 | Logger.Trace("Creating application data folder."); 66 | } 67 | 68 | Logger.Trace("Initializing profile optimizations..."); 69 | ProfileOptimization.SetProfileRoot(System.Windows.Forms.Application.UserAppDataPath); 70 | ProfileOptimization.StartProfile("profile.opt"); 71 | Logger.Trace("Finished initializing profile optimizations."); 72 | 73 | Logger.Trace("Trying to identify log memory..."); 74 | var logMemory = Logger.DefaultLogMemory as ArchivableConsoleLogMemory; 75 | if (logMemory != null) 76 | { 77 | var logFolder = Path.Combine(DataDirectory, "logs"); 78 | if (!Directory.Exists(logFolder)) 79 | { 80 | Logger.Debug("Creating log folder..."); 81 | Directory.CreateDirectory(logFolder); 82 | Logger.Trace("Created log folder."); 83 | } 84 | 85 | 86 | var logFile = Path.Combine(logFolder, 87 | SweetUtils.LegalizeFilename($"{DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern)}.log.zip")); 88 | 89 | Logger.Trace($"Setting log file: {logFile}"); 90 | logMemory.AutoArchiveOnDispose = true; 91 | logMemory.ArchiveFile = logFile; 92 | } 93 | 94 | Logger.Debug("Loading database..."); 95 | Database = new LiteDatabase(Path.Combine(DataDirectory, "config.db")); 96 | Database.Log.Logging += (message) => Logger.Trace($"LiteDB: {message}"); 97 | 98 | Logger.Debug("Loading settings collection out of database..."); 99 | var settingsDB = Database.GetCollection("settings"); 100 | if (settingsDB.Count() > 1) 101 | { 102 | Logger.Warn("Found more than one setting. Loading first one..."); 103 | } 104 | Settings = settingsDB.FindAll().FirstOrDefault(); 105 | if (Settings == null) 106 | { 107 | Logger.Warn("Settings not found. Creating new one. This is normal on first start up..."); 108 | Settings = new Settings(); 109 | Settings.Store(); 110 | } 111 | 112 | Logger.Debug("Loading jobs collection out of database..."); 113 | Scheduler.JobList = Job.RestoreJobs().ToList(); 114 | 115 | Logger.Info("Loaded settings."); 116 | Logger.Debug( 117 | $"Settings: Email: {Settings.Email}, password: {(string.IsNullOrEmpty(Settings.Password) ? "not set" : "is set")}, use username: {Settings.UseUserData}, loginToken: {Settings.LoginToken}"); 118 | 119 | Logger.Trace("Updating log level..."); 120 | Logger.GlobalLogLevel = Settings.ApplicactionLogLevel; 121 | #if DEBUG 122 | //temporary add debug log level if debugging... 123 | Logger.GlobalLogLevel |= LogLevel.Debug; 124 | Logger.Debug("Running in debug configuartion. Added log level debug."); 125 | #endif 126 | if (useTrace) 127 | { 128 | Logger.GlobalLogLevel |= LogLevel.Trace; 129 | Logger.Trace("Creating application data folder."); 130 | } 131 | 132 | Logger.Debug("Creating discord client..."); 133 | 134 | var config = new DiscordSocketConfig() 135 | { 136 | DefaultRetryMode = RetryMode.AlwaysRetry, 137 | }; 138 | 139 | //Client = new DiscordSocketClient(config); 140 | DMLClient.Client.Log += (arg) => 141 | { 142 | var logMessage = $"DiscordClient: {arg.Message}"; 143 | switch (arg.Severity) 144 | { 145 | case LogSeverity.Verbose: 146 | Logger.Trace(logMessage); 147 | break; 148 | case LogSeverity.Debug: 149 | Logger.Trace(logMessage); 150 | break; 151 | case LogSeverity.Info: 152 | Logger.Info(logMessage); 153 | break; 154 | case LogSeverity.Warning: 155 | Logger.Warn(logMessage); 156 | break; 157 | case LogSeverity.Error: 158 | Logger.Error(logMessage); 159 | break; 160 | } 161 | 162 | return Task.CompletedTask; 163 | }; 164 | 165 | 166 | Logger.Info("Trying to log into discord..."); 167 | var abort = false; 168 | 169 | DMLClient.Client.Connected += Client_Connected; 170 | 171 | var loggedIn = false; 172 | 173 | while (!loggedIn) 174 | { 175 | if (!string.IsNullOrEmpty(Settings.LoginToken)) 176 | { 177 | Logger.Debug("Trying to login with last known token..."); 178 | loggedIn= await DMLClient.Login(Settings.LoginToken); 179 | } 180 | 181 | if (!loggedIn) 182 | { 183 | Logger.Debug("Showing dialog for username and password..."); 184 | var loginDlg = new LoginDialog(); 185 | loginDlg.ShowDialog(); 186 | } 187 | } 188 | 189 | /*while ((Client.LoginState != LoginState.LoggedIn || Client.ConnectionState!=ConnectionState.Connected) && !abort) 190 | { 191 | Logger.Debug(Client.ConnectionState.ToString()); 192 | Logger.Debug(Client.LoginState.ToString()); 193 | 194 | Logger.Trace("Entering login loop."); 195 | 196 | try 197 | { 198 | if (Client.ConnectionState == ConnectionState.Connecting) 199 | continue; 200 | 201 | if (!string.IsNullOrEmpty(Settings.LoginToken)) 202 | { 203 | Logger.Debug("Trying to login with last known token..."); 204 | await Client.LoginAsync(TokenType.User, Settings.LoginToken); 205 | await Client.StartAsync(); 206 | await Task.Delay(1000); 207 | } 208 | 209 | } 210 | catch (HttpException ex) 211 | { 212 | Logger.Warn($"Login seems to have failed or gone wrong: {ex.GetType().Name} - {ex.Message}"); 213 | } 214 | 215 | if (Client.LoginState == LoginState.LoggedOut) 216 | { 217 | Settings.Password = string.Empty; 218 | Logger.Debug("Showing dialog for username and password..."); 219 | var loginDlg = new LoginDialog(); 220 | loginDlg.ShowDialog(); 221 | Logger.Trace("Dialog closed."); 222 | } 223 | }*/ 224 | 225 | Logger.Debug("Start checking for invalid jobs..."); 226 | 227 | //Client 228 | 229 | while (DMLClient.Client.Guilds.Count == 0) 230 | { 231 | // wait until guilds are loaded 232 | } 233 | 234 | for (var i = Scheduler.JobList.Count - 1; i >= 0; i--) 235 | { 236 | var job = Scheduler.JobList[i]; 237 | var isError = false; 238 | var guild = FindServerById(job.GuildId); 239 | if (guild == null) 240 | isError = true; 241 | else 242 | { 243 | var channel = FindChannelById(guild, job.ChannelId); 244 | if (channel == null) 245 | isError = true; 246 | } 247 | 248 | if (isError) 249 | { 250 | 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", 251 | MessageBoxButtons.OK, MessageBoxIcon.Warning); 252 | 253 | Scheduler.JobList.Remove(job); 254 | Scheduler.RunningJobs.Remove(job.Id); 255 | job.Stop(); 256 | job.Delete(); 257 | } 258 | } 259 | 260 | splash.Close(); 261 | 262 | Logger.Info("Starting scheduler..."); 263 | Scheduler.Start(); 264 | 265 | System.Windows.Forms.Application.Run(new MainForm()); 266 | 267 | Logger.Info("Stopping scheduler..."); 268 | Scheduler.Stop(); 269 | } 270 | catch (Exception ex) 271 | { 272 | Logger.Error($"{ex.Message} occured at: {ex.StackTrace}"); 273 | 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) 274 | Raven.Capture(new SentryEvent(ex)); 275 | } 276 | } 277 | 278 | private static Task Client_Connected() 279 | { 280 | Logger.Debug("Connected"); 281 | return Task.CompletedTask; 282 | } 283 | 284 | //TODO: this is thrid time we implement this.....this has to be fixed!!! 285 | private static SocketGuild FindServerById(ulong id) 286 | { 287 | Logger.Trace($"Trying to find server by Id: {id}"); 288 | return (from s in DMLClient.Client.Guilds where s.Id == id select s).FirstOrDefault(); 289 | } 290 | 291 | private static SocketTextChannel FindChannelById(SocketGuild server, ulong id) 292 | { 293 | Logger.Trace($"Trying to find channel in {server} by id: {id}"); 294 | return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /Discord Media Loader/Discord Media Loader.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EDC92554-DBC1-4F9C-9317-379A8BF441E8} 8 | WinExe 9 | Properties 10 | Discord_Media_Loader 11 | Discord Media Loader 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | Serraniel-64x64.ico 38 | 39 | 40 | 41 | 42 | 43 | 44 | ..\packages\Discord.Net.Commands.1.0.2\lib\netstandard1.1\Discord.Net.Commands.dll 45 | 46 | 47 | ..\packages\Discord.Net.Core.1.0.2\lib\net45\Discord.Net.Core.dll 48 | 49 | 50 | ..\packages\Discord.Net.Rest.1.0.2\lib\net45\Discord.Net.Rest.dll 51 | 52 | 53 | ..\packages\Discord.Net.Rpc.1.0.2\lib\net45\Discord.Net.Rpc.dll 54 | 55 | 56 | ..\packages\Discord.Net.Webhook.1.0.2\lib\netstandard1.1\Discord.Net.Webhook.dll 57 | 58 | 59 | ..\packages\Discord.Net.WebSocket.1.0.2\lib\net45\Discord.Net.WebSocket.dll 60 | 61 | 62 | ..\packages\Microsoft.Extensions.DependencyInjection.1.1.1\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll 63 | 64 | 65 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 66 | 67 | 68 | ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll 69 | 70 | 71 | ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll 72 | 73 | 74 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll 75 | True 76 | 77 | 78 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll 79 | True 80 | 81 | 82 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll 83 | True 84 | 85 | 86 | ..\packages\Octokit.0.24.1-alpha0001\lib\net45\Octokit.dll 87 | True 88 | 89 | 90 | 91 | ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll 92 | 93 | 94 | ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 95 | True 96 | 97 | 98 | 99 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 100 | 101 | 102 | 103 | ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll 104 | 105 | 106 | ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll 107 | 108 | 109 | ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll 110 | 111 | 112 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 113 | True 114 | 115 | 116 | 117 | ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll 118 | 119 | 120 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 121 | 122 | 123 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 124 | 125 | 126 | ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll 127 | 128 | 129 | ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll 130 | 131 | 132 | 133 | ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll 134 | True 135 | 136 | 137 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll 138 | 139 | 140 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 141 | 142 | 143 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 144 | 145 | 146 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 158 | 159 | 160 | 161 | 162 | Form 163 | 164 | 165 | FrmDownload.cs 166 | 167 | 168 | Form 169 | 170 | 171 | FrmSplash.cs 172 | 173 | 174 | 175 | 176 | 177 | 178 | FrmDownload.cs 179 | 180 | 181 | FrmSplash.cs 182 | 183 | 184 | ResXFileCodeGenerator 185 | Resources.Designer.cs 186 | Designer 187 | 188 | 189 | True 190 | Resources.resx 191 | True 192 | 193 | 194 | 195 | SettingsSingleFileGenerator 196 | Settings.Designer.cs 197 | 198 | 199 | True 200 | Settings.settings 201 | True 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | {c130de6a-3237-42b5-be9f-783d1cd104c6} 215 | DML.Application 216 | 217 | 218 | 219 | 226 | -------------------------------------------------------------------------------- /Discord Media Loader.Application/DML.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C130DE6A-3237-42B5-BE9F-783D1CD104C6} 8 | Library 9 | Properties 10 | DML.Application 11 | Discord Media Loader.Application 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Discord.Net.Commands.1.0.2\lib\netstandard1.1\Discord.Net.Commands.dll 35 | 36 | 37 | ..\packages\Discord.Net.Core.1.0.2\lib\net45\Discord.Net.Core.dll 38 | 39 | 40 | ..\packages\Discord.Net.Rest.1.0.2\lib\net45\Discord.Net.Rest.dll 41 | 42 | 43 | ..\packages\Discord.Net.Rpc.1.0.2\lib\net45\Discord.Net.Rpc.dll 44 | 45 | 46 | ..\packages\Discord.Net.Webhook.1.0.2\lib\netstandard1.1\Discord.Net.Webhook.dll 47 | 48 | 49 | ..\packages\Discord.Net.WebSocket.1.0.2\lib\net45\Discord.Net.WebSocket.dll 50 | 51 | 52 | ..\packages\LiteDB.3.1.0\lib\net35\LiteDB.dll 53 | True 54 | 55 | 56 | ..\packages\Microsoft.Extensions.DependencyInjection.1.1.1\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll 57 | 58 | 59 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 60 | 61 | 62 | ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll 63 | 64 | 65 | ..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll 66 | 67 | 68 | ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll 69 | 70 | 71 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll 72 | True 73 | 74 | 75 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll 76 | True 77 | 78 | 79 | ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll 80 | True 81 | 82 | 83 | ..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll 84 | True 85 | 86 | 87 | ..\packages\SharpRaven.2.2.0\lib\net45\SharpRaven.dll 88 | 89 | 90 | ..\packages\SweetLib.0.2.1-alpha\lib\netstandard1.3\SweetLib.dll 91 | 92 | 93 | 94 | ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll 95 | 96 | 97 | ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 98 | True 99 | 100 | 101 | 102 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 103 | 104 | 105 | 106 | ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll 107 | 108 | 109 | 110 | ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll 111 | 112 | 113 | ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll 114 | 115 | 116 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 117 | True 118 | 119 | 120 | 121 | ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll 122 | 123 | 124 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 125 | 126 | 127 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 128 | 129 | 130 | ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll 131 | 132 | 133 | ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll 134 | 135 | 136 | 137 | ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll 138 | True 139 | 140 | 141 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll 142 | 143 | 144 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 145 | 146 | 147 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 148 | 149 | 150 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 160 | 161 | 162 | ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll 163 | True 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Form 172 | 173 | 174 | LoginDialog.cs 175 | 176 | 177 | Form 178 | 179 | 180 | FrmInternalSplash.cs 181 | 182 | 183 | 184 | Form 185 | 186 | 187 | MainForm.cs 188 | 189 | 190 | 191 | True 192 | True 193 | Resources.resx 194 | 195 | 196 | 197 | 198 | 199 | LoginDialog.cs 200 | 201 | 202 | FrmInternalSplash.cs 203 | 204 | 205 | MainForm.cs 206 | 207 | 208 | ResXFileCodeGenerator 209 | Resources.Designer.cs 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | {045eb4a1-34e7-47e0-867e-e10c40505095} 219 | DML.Client 220 | 221 | 222 | 223 | 224 | 225 | 226 | 233 | --------------------------------------------------------------------------------