From 6be4d2b495a0d298886be873ae89fdc6a5a324ae Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:40:37 +0300 Subject: [PATCH 1/8] [WIP] Options and change how logger works [WIP] Options menu - Add Small Image type [Fix] Refactoring and Auto logging type selector --- .github/workflows/build-dev.yml | 6 +- AMDiscordRPC/AMDiscordRPC.cs | 12 ++-- AMDiscordRPC/AMDiscordRPC.csproj | 7 ++ AMDiscordRPC/AppleMusic.cs | 4 +- AMDiscordRPC/Covers.cs | 2 + AMDiscordRPC/Discord.cs | 3 +- AMDiscordRPC/Globals.cs | 71 +++++++++++++++---- AMDiscordRPC/UI.cs | 15 ++++ AMDiscordRPC/UIComponents/InputWindow.xaml.cs | 4 +- AMDiscordRPC/UIComponents/OptionsWindow.xaml | 21 ++++++ .../UIComponents/OptionsWindow.xaml.cs | 23 ++++++ AMDiscordRPC/log4netconf.xml | 15 ---- README.md | 1 + 13 files changed, 140 insertions(+), 44 deletions(-) create mode 100644 AMDiscordRPC/UIComponents/OptionsWindow.xaml create mode 100644 AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 37dc5a7..da6a692 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -18,9 +18,9 @@ jobs: - name: Restore NuGet Packages run: nuget restore AMDiscordRPC.sln - name: Build - run: msbuild AMDiscordRPC.sln -property:Configuration=Release -property:platform="x64" + run: msbuild AMDiscordRPC.sln -property:Configuration=Debug -property:platform="x64" - name: Zip - run: powershell Compress-Archive -Path ./AMDiscordRPC/bin/x64/Release -DestinationPath Release.zip + run: powershell Compress-Archive -Path ./AMDiscordRPC/bin/x64/Debug -DestinationPath Dev-Release.zip - name: Release uses: softprops/action-gh-release@v2 with: @@ -31,4 +31,4 @@ jobs: Don't use this version if you want more stable experience. prerelease: true files: | - Release.zip \ No newline at end of file + Dev-Release.zip \ No newline at end of file diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index a807bb9..07dabcb 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -20,11 +20,11 @@ internal class AMDiscordRPC private static string oldAlbumnArtist; static void Main(string[] args) { + ConfigureLogger(); InitRegion(); CreateUI(); - ConfigureLogger(); - InitializeDiscordRPC(); - AttachToAppleMusic(); + InitDiscordRPC(); + AttachToAM(); AMSongDataEvent.SongChanged += async (sender, x) => { log.Info($"Song: {x.SongName} \\ Artist and Album: {x.ArtistandAlbumName}"); @@ -249,7 +249,7 @@ static void AMEvent() client.ClearPresence(); while (!AMAttached) { - AttachToAppleMusic(); + AttachToAM(); Thread.Sleep(1000); } AMEvent(); @@ -262,7 +262,7 @@ static void AMEvent() client.ClearPresence(); while (!AMAttached) { - AttachToAppleMusic(); + AttachToAM(); Thread.Sleep(1000); } AMEvent(); @@ -272,7 +272,7 @@ static void AMEvent() { while (!AMAttached) { - AttachToAppleMusic(); + AttachToAM(); Thread.Sleep(1000); } AMEvent(); diff --git a/AMDiscordRPC/AMDiscordRPC.csproj b/AMDiscordRPC/AMDiscordRPC.csproj index 4d4c56a..53f51a0 100644 --- a/AMDiscordRPC/AMDiscordRPC.csproj +++ b/AMDiscordRPC/AMDiscordRPC.csproj @@ -250,6 +250,9 @@ InputWindow.xaml + + OptionsWindow.xaml + @@ -279,6 +282,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + diff --git a/AMDiscordRPC/AppleMusic.cs b/AMDiscordRPC/AppleMusic.cs index 4eb1dd3..519fa23 100644 --- a/AMDiscordRPC/AppleMusic.cs +++ b/AMDiscordRPC/AppleMusic.cs @@ -6,13 +6,13 @@ namespace AMDiscordRPC { internal class AppleMusic { - public static void AttachToAppleMusic() + public static void AttachToAM() { try { AppleMusicProc = Application.Attach("AppleMusic.exe"); AMAttached = true; - log.Info($"Attached to Process Id: {AppleMusicProc.ProcessId}"); + log.Info($"Attached to PID: {AppleMusicProc.ProcessId}"); } catch (Exception e) { diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index 5c69e07..02f867e 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -60,6 +60,7 @@ private static async Task AsyncFetchiTunes(string album, string public static async Task AsyncAMFetch(string album, string searchStr) { + log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); try { HttpResponseMessage AMRequest = await hclient.GetAsync($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); @@ -93,6 +94,7 @@ public static async Task CheckAnimatedCover(string album, string url, Cancellati try { var appleMusicDom = await hclient.GetAsync(url); + log.Debug($"Animated Cover Request: {url}"); if (appleMusicDom.IsSuccessStatusCode) { string DOMasAString = await appleMusicDom.Content.ReadAsStringAsync(); diff --git a/AMDiscordRPC/Discord.cs b/AMDiscordRPC/Discord.cs index 124d731..8912d27 100644 --- a/AMDiscordRPC/Discord.cs +++ b/AMDiscordRPC/Discord.cs @@ -13,10 +13,11 @@ public class Discord private static Thread thread = null; public static CancellationTokenSource animatedCoverCts; - public static void InitializeDiscordRPC() + public static void InitDiscordRPC() { client = new DiscordRpcClient("1308911584164319282"); client.Initialize(); + log.Debug("Discord RPC initialized."); } public static void ChangeTimestamps(DateTime start = new DateTime(), DateTime end = new DateTime()) diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index dad6c88..231b826 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -2,7 +2,12 @@ using DiscordRPC; using DiscordRPC.Helper; using log4net; +using log4net.Appender; using log4net.Config; +using log4net.Core; +using log4net.Filter; +using log4net.Layout; +using log4net.Repository.Hierarchy; using System; using System.Collections.Generic; using System.Data.SQLite; @@ -34,22 +39,9 @@ public static class Globals public static string ffmpegPath; public static S3_Creds S3_Credentials; private static List newMatchesArr; - public enum S3ConnectionStatus - { - Connected, - Disconnected, - Error - } - public enum AudioFormat - { - Lossless, - Dolby_Atmos, - Dolby_Audio, - AAC - } public static S3ConnectionStatus S3Status = S3ConnectionStatus.Disconnected; public static string AMRegion; - + public static SmallImage SelectedSmallImage = SmallImage.LossDolby; public static void ConfigureLogger() { @@ -57,9 +49,36 @@ public static void ConfigureLogger() { XmlConfigurator.Configure(stream); } + + LevelRangeFilter lrf = new LevelRangeFilter + { + LevelMax = Level.Fatal, + LevelMin = Level.Info + }; + #if DEBUG + lrf.LevelMin = Level.Debug; + #endif + lrf.ActivateOptions(); + + PatternLayout pl = new PatternLayout + { + ConversionPattern = "[%date{HH:mm:ss.fff}] %level (%method:%line) - %message%newline" + }; + pl.ActivateOptions(); + + RollingFileAppender rfa = new RollingFileAppender + { + AppendToFile = false, + File = @"logs/latest.log", + Layout = pl, + }; + rfa.AddFilter(lrf); + rfa.ActivateOptions(); + + ((Hierarchy)LogManager.GetRepository()).Root.AddAppender(rfa); } - public static async void InitRegion() + public static void InitRegion() { HttpClientHandler HClientHandlerhandler = new HttpClientHandler(); CookieContainer cookies = new CookieContainer(); @@ -72,6 +91,7 @@ public static async void InitRegion() AMRegion = cookies.GetCookies(new Uri("https://music.apple.com/")).Cast() .Where(cookie => cookie.Name == "geo").ToList()[0].Value; + log.Info($"Region selected as: {AMRegion.ToLower()}"); } catch (Exception e) { @@ -173,6 +193,27 @@ public static async void CheckFFmpeg() else FFmpegDialog(); } + public enum S3ConnectionStatus + { + Connected, + Disconnected, + Error + } + public enum AudioFormat + { + Lossless, + Dolby_Atmos, + Dolby_Audio, + AAC + } + + public enum SmallImage + { + LossDolby, + Artist, + None + } + public class SongData : EventArgs { public string SongName { get; set; } diff --git a/AMDiscordRPC/UI.cs b/AMDiscordRPC/UI.cs index 4af808e..1043fff 100644 --- a/AMDiscordRPC/UI.cs +++ b/AMDiscordRPC/UI.cs @@ -16,6 +16,7 @@ namespace AMDiscordRPC internal class UI { private static InputWindow inputWindow; + private static OptionsWindow optionsWindow; private static Application app; private static Thread mainThread = Thread.CurrentThread; @@ -31,6 +32,7 @@ public static void CreateUI() thread.SetApartmentState(ApartmentState.STA); thread.Start(); + log.Debug("Tray thread started."); } public static void FFmpegDialog() @@ -73,6 +75,7 @@ public class AMDiscordRPCTray private static ContextMenu contextMenu = new ContextMenu(); public static MenuItem notifySongState = new MenuItem(); public MenuItem s3Menu = new MenuItem(); + public MenuItem optionsMenu = new MenuItem(); public AMDiscordRPCTray() { @@ -91,11 +94,23 @@ public AMDiscordRPCTray() }); }); + optionsMenu.Text = "Options"; + optionsMenu.Index = 2; + optionsMenu.Click += new EventHandler((object sender, EventArgs e) => + { + app.Dispatcher.Invoke(() => + { + optionsWindow = new OptionsWindow(); + optionsWindow.Show(); + }); + }); + contextMenu.MenuItems.AddRange( new MenuItem[] { notifySongState, s3Menu, + optionsMenu, new MenuItem("Show Latest Log", (s,e) => { Process.Start("notepad", $"{Path.Combine(Directory.GetCurrentDirectory(), @"logs\latest.log")}"); }), new MenuItem("Exit", (s, e) => { Environment.Exit(0); }) } diff --git a/AMDiscordRPC/UIComponents/InputWindow.xaml.cs b/AMDiscordRPC/UIComponents/InputWindow.xaml.cs index 7ff2261..723d161 100644 --- a/AMDiscordRPC/UIComponents/InputWindow.xaml.cs +++ b/AMDiscordRPC/UIComponents/InputWindow.xaml.cs @@ -72,13 +72,13 @@ private void SetS3Button_Click(object sender, RoutedEventArgs e) } } - private static void PutValues(S3_Creds creds, ShowMode mode = ShowMode.Hide) + private void PutValues(S3_Creds creds, ShowMode mode = ShowMode.Hide) { List Instances = new List { Instance.AccessKeyIDBox, Instance.SecretKeyBox, Instance.EndpointBox, Instance.BucketNameBox, Instance.PublicBucketURLBox }; List Keys = new List() { creds.accessKey, creds.secretKey, creds.serviceURL, creds.bucketName, creds.bucketURL }; foreach (var (item, index) in Instances.Select((v, i) => (v, i))) { - PlaceholderAdorner adorner = Helpers.TextBoxHelper.GetPlaceholderAdorner(item); + PlaceholderAdorner adorner = GetPlaceholderAdorner(item); item.Text = (mode == ShowMode.Show) ? Keys[index] : new string('*', Keys[index].Length); item.IsEnabled = (mode == ShowMode.Show) ? true : false; if (Keys[index].Length > 0) diff --git a/AMDiscordRPC/UIComponents/OptionsWindow.xaml b/AMDiscordRPC/UIComponents/OptionsWindow.xaml new file mode 100644 index 0000000..37fdd44 --- /dev/null +++ b/AMDiscordRPC/UIComponents/OptionsWindow.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs new file mode 100644 index 0000000..4e4e68c --- /dev/null +++ b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs @@ -0,0 +1,23 @@ + +using System.Management.Instrumentation; +using System.Windows; + +namespace AMDiscordRPC.UIComponents +{ + /// + /// Interaction logic for OptionsWindow.xaml + /// + public partial class OptionsWindow : Window + { + private static OptionsWindow Instance; + public OptionsWindow() + { + InitializeComponent(); + Instance = this; + Instance.Loaded += (s, e) => + { + + }; + } + } +} diff --git a/AMDiscordRPC/log4netconf.xml b/AMDiscordRPC/log4netconf.xml index 30837d1..8f1ebc9 100644 --- a/AMDiscordRPC/log4netconf.xml +++ b/AMDiscordRPC/log4netconf.xml @@ -4,23 +4,8 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 8e17c67..3b9cb6a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@

AMDiscordRPC

An another Apple Music Discord RPC

+[Support Server](https://discord.gg/5trvjuqgm8) ## Usage [Download](https://github.com/CrawLeyYou/AMDiscordRPC/releases/latest) latest release and make sure you have [.NET Framework 4.7.2](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472). From 7b0eb9b5cbbeb0b956b3c674ee9ad2169e4137d9 Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Tue, 9 Sep 2025 20:13:59 +0300 Subject: [PATCH 2/8] Push not finished features and bug fixes --- AMDiscordRPC/AMDiscordRPC.cs | 8 +++----- AMDiscordRPC/Covers.cs | 20 ++++++++++++++----- AMDiscordRPC/Database.cs | 17 +++++++++++----- AMDiscordRPC/Discord.cs | 3 +++ AMDiscordRPC/Globals.cs | 13 ++++++++---- AMDiscordRPC/UIComponents/InputWindow.xaml.cs | 5 ++++- AMDiscordRPC/UIComponents/OptionsWindow.xaml | 2 +- .../UIComponents/OptionsWindow.xaml.cs | 15 +++++++++++++- 8 files changed, 61 insertions(+), 22 deletions(-) diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index 07dabcb..0657ea7 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -45,7 +45,7 @@ static void Main(string[] args) } }; CheckDatabaseIntegrity(); - InitDBCreds(); + ConfigureFromDB(); CheckFFmpeg(); InitS3(); AMEvent(); @@ -144,7 +144,7 @@ static void AMEvent() if (oldValue == 0) oldValue = slider.AsSlider().Value; DateTime currentTime = DateTime.UtcNow; DateTime startTime = currentTime.Subtract(subractThis); - DateTime endTime = currentTime.AddSeconds(slider.AsSlider().Maximum).Subtract(subractThis); + DateTime endTime = startTime.AddSeconds(slider.AsSlider().Maximum); DateTime oldEndTime = DateTime.MinValue; DateTime oldStartTime = DateTime.MinValue; bool isSingle = dashSplit[dashSplit.Length - 1].Contains("Single"); @@ -214,8 +214,6 @@ static void AMEvent() } else format = AudioFormat.AAC; oldValue = 0; - startTime = currentTime.Subtract(subractThis); - endTime = currentTime.AddSeconds(slider.AsSlider().Maximum).Subtract(subractThis); oldStartTime = startTime; oldEndTime = endTime; AMSongDataEvent.ChangeSong(new SongData(currentSong, (isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()), currentArtistAlbum.Split('—').Length <= 1, startTime, endTime, format)); @@ -256,7 +254,7 @@ static void AMEvent() } Thread.Sleep(20); } - if (!AMAttached & AppleMusicProc.HasExited != true) + if (!AMAttached && AppleMusicProc.HasExited != true) { log.Info("Something happened which needs to reattach"); client.ClearPresence(); diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index 02f867e..4a9e667 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using AngleSharp.Dom; using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; using static AMDiscordRPC.Playlist; @@ -30,9 +31,10 @@ private static async Task AsyncFetchiTunes(string album, string ( imageRes["results"][0]["artworkUrl100"].ToString(), imageRes["results"][0]["trackViewUrl"].ToString(), - imageRes["results"][0]["collectionName"].ToString() + imageRes["results"][0]["collectionName"].ToString(), + imageRes["results"][0]["artistViewUrl"].ToString() ); - Database.UpdateAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL)); + UpdateAlbum(new SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); CoverThread = null; return webRes; } @@ -58,6 +60,11 @@ private static async Task AsyncFetchiTunes(string album, string } } + public static async Task AsyncArtistProfileFetch(string url) + { + return null; + } + public static async Task AsyncAMFetch(string album, string searchStr) { log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); @@ -68,12 +75,15 @@ public static async Task AsyncAMFetch(string album, string sear { string DOMasAString = await AMRequest.Content.ReadAsStringAsync(); IHtmlDocument document = parser.ParseDocument(DOMasAString); + WebSongResponse webRes = new WebSongResponse( - document.DocumentElement.QuerySelectorAll("div.top-search-lockup__artwork > div > picture > source")[1].GetAttribute("srcset").Split(' ')[0], - document.DocumentElement.QuerySelector("div.top-search-lockup__action > a").GetAttribute("href") + document.DocumentElement.QuerySelectorAll("div.track-lockup__artwork-wrapper > div > picture > source")[1].GetAttribute("srcset").Split(',')[1].Split(' ')[0], + document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href"), + null, + document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href") ); CoverThread = null; - Database.UpdateAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL)); + UpdateAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); return webRes; } else diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index 2594fb2..684c33e 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -11,9 +11,10 @@ internal class Database private static SQLiteConnection sqlite; public static readonly Dictionary sqlMap = new Dictionary() { - {"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, + {"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, {"creds", "S3_accessKey TEXT, S3_secretKey TEXT, S3_serviceURL TEXT, S3_bucketName TEXT, S3_bucketURL TEXT, S3_isSpecificKey BOOLEAN CHECK (S3_isSpecificKey IN (0,1)), FFmpegPath TEXT" }, - {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" } + {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, + {"clientSettings", "smallImage INTEGER"} }; private static void InitDatabase() @@ -57,6 +58,7 @@ private static void CreateDatabase() } } + // Note: source, streamurl, animated, animatedUrl can be stored in external table so we decrease the file size of database private static void CheckForeignKeys() { ExecuteNonQueryCommand("PRAGMA foreign_keys = on"); @@ -119,7 +121,9 @@ public static SQLCoverResponse GetAlbumDataFromSQL(string album) reader.GetString(2), ((!reader.IsDBNull(3)) ? reader.GetBoolean(3) : null), ((!reader.IsDBNull(4)) ? reader.GetString(4) : null), - ((!reader.IsDBNull(5)) ? reader.GetString(5) : null)); + ((!reader.IsDBNull(5)) ? reader.GetString(5) : null), + reader.GetString(6) + ); } } return null; @@ -155,7 +159,7 @@ private static void CheckColumns() // Recovery functionality will be added next release. } } - else if (column == null) + else if (column == null && !item.Value.primaryKey) { ExecuteNonQueryCommand($"ALTER TABLE {table} ADD COLUMN {SQLInfo}"); } @@ -255,8 +259,10 @@ public class SQLCoverResponse public bool? animated { get; set; } public string streamURL { get; set; } public string animatedURL { get; set; } + public string artistRedirURL { get; set; } - public SQLCoverResponse(string album = null, string source = null, string redirURL = null, bool? animated = null, string streamURL = null, string animatedURL = null) + + public SQLCoverResponse(string album = null, string source = null, string redirURL = null, bool? animated = null, string streamURL = null, string animatedURL = null, string artistRedirURL = null) { this.album = album; this.source = source; @@ -264,6 +270,7 @@ public SQLCoverResponse(string album = null, string source = null, string redirU this.animated = animated; this.streamURL = streamURL; this.animatedURL = animatedURL; + this.artistRedirURL = artistRedirURL; } public List GetNotNullKeys() diff --git a/AMDiscordRPC/Discord.cs b/AMDiscordRPC/Discord.cs index 8912d27..715c973 100644 --- a/AMDiscordRPC/Discord.cs +++ b/AMDiscordRPC/Discord.cs @@ -80,6 +80,7 @@ public static void SetPresence(SongData x, WebSongResponse resp) { Type = ActivityType.Listening, Details = ConvertToValidString(x.SongName), + StateUrl = resp.artistURL, StatusDisplay = StatusDisplayType.State, State = (x.IsMV) ? x.ArtistandAlbumName : ConvertToValidString(x.ArtistandAlbumName.Split('—')[0]), Assets = new Assets() @@ -99,6 +100,8 @@ public static void SetPresence(SongData x, WebSongResponse resp) End = x.EndTime, } }; + if (oldData.Assets.LargeImageText.Length == 1) + oldData.Assets.LargeImageText = $"{oldData.Assets.LargeImageText}‍"; // THIS HAS U+200D AT THE END OF STRING TO FIX '"large_text" length must be at least 2 characters long' ERROR client.SetPresence(oldData); if (resp.artworkURL != null && !resp.artworkURL.Contains((S3_Credentials != null) ? (S3_Credentials.GetNullKeys().Count == 0) ? S3_Credentials.bucketURL : "" : "")) { diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index 231b826..c4fbd51 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -121,9 +121,9 @@ public static string ConvertToValidString(string data) return data; } - public static void InitDBCreds() + public static void ConfigureFromDB() { - using (SQLiteDataReader dbResp = Database.ExecuteReaderCommand($"SELECT {string.Join(", ", Regex.Matches(Database.sqlMap["creds"], @"S3_\w+").FilterRepeatMatches())} FROM creds LIMIT 1")) + using (SQLiteDataReader dbResp = ExecuteReaderCommand($"SELECT {string.Join(", ", Regex.Matches(sqlMap["creds"], @"S3_\w+").FilterRepeatMatches())} FROM creds LIMIT 1")) { while (dbResp.Read()) { @@ -136,6 +136,8 @@ public static void InitDBCreds() ((!dbResp.IsDBNull(5)) ? dbResp.GetBoolean(5) : null)); } } + + SelectedSmallImage = (SmallImage)Convert.ToInt32(ExecuteScalarCommand("SELECT smallImage FROM clientSettings")); } private static void StartFFmpegProcess(string filename) @@ -279,12 +281,14 @@ public class WebSongResponse public string artworkURL { get; set; } public string trackURL { get; set; } public string trackName { get; set; } + public string artistURL { get; set; } - public WebSongResponse(string artworkURL = null, string trackURL = null, string trackName = null) + public WebSongResponse(string artworkURL = null, string trackURL = null, string trackName = null, string artistURL = null) { this.artworkURL = artworkURL; this.trackURL = trackURL; this.trackName = trackName; + this.artistURL = artistURL; } public override bool Equals(object obj) @@ -292,7 +296,8 @@ public override bool Equals(object obj) return obj is WebSongResponse other && artworkURL == other.artworkURL && trackURL == other.trackURL && - trackName == other.trackName; + trackName == other.trackName && + artistURL == other.artistURL; } } } diff --git a/AMDiscordRPC/UIComponents/InputWindow.xaml.cs b/AMDiscordRPC/UIComponents/InputWindow.xaml.cs index 723d161..9d57c8a 100644 --- a/AMDiscordRPC/UIComponents/InputWindow.xaml.cs +++ b/AMDiscordRPC/UIComponents/InputWindow.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; + +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Windows; @@ -79,6 +80,8 @@ private void PutValues(S3_Creds creds, ShowMode mode = ShowMode.Hide) foreach (var (item, index) in Instances.Select((v, i) => (v, i))) { PlaceholderAdorner adorner = GetPlaceholderAdorner(item); + if (Keys[index] == null) + return; item.Text = (mode == ShowMode.Show) ? Keys[index] : new string('*', Keys[index].Length); item.IsEnabled = (mode == ShowMode.Show) ? true : false; if (Keys[index].Length > 0) diff --git a/AMDiscordRPC/UIComponents/OptionsWindow.xaml b/AMDiscordRPC/UIComponents/OptionsWindow.xaml index 37fdd44..8c4a0af 100644 --- a/AMDiscordRPC/UIComponents/OptionsWindow.xaml +++ b/AMDiscordRPC/UIComponents/OptionsWindow.xaml @@ -8,7 +8,7 @@ Icon="../Resources/Logo Black 32.ico" d:DesignHeight="450" d:DesignWidth="800" d:MaxHeight="450" d:MaxWidth="800" d:ResizeMode="NoResize" ResizeMode="NoResize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Title="Options"> - + diff --git a/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs index 4e4e68c..eb3c5de 100644 --- a/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs +++ b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs @@ -1,6 +1,10 @@  +using System; using System.Management.Instrumentation; +using System.Text.RegularExpressions; using System.Windows; +using System.Windows.Controls; +using static AMDiscordRPC.Globals; namespace AMDiscordRPC.UIComponents { @@ -16,8 +20,17 @@ public OptionsWindow() Instance = this; Instance.Loaded += (s, e) => { - + smallImage.SelectedIndex = (int)SelectedSmallImage; }; } + + private void SmallImage_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedSmallImage = (SmallImage)smallImage.SelectedIndex; + if (Database.ExecuteScalarCommand("SELECT smallImage FROM clientSettings") == null) + Database.ExecuteNonQueryCommand($"INSERT INTO clientSettings (smallImage) VALUES ({smallImage.SelectedIndex})"); + else + Database.ExecuteNonQueryCommand($"UPDATE clientSettings SET (smallImage) = ({smallImage.SelectedIndex})"); + } } } From f74a9d19378c76fb8665bae1b6be1f9f0c3d13bd Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:57:06 +0300 Subject: [PATCH 3/8] Add new database base and rewrite checks --- AMDiscordRPC/Covers.cs | 6 ++--- AMDiscordRPC/Database.cs | 55 +++++++++++++++++++++++++++++++++------- AMDiscordRPC/Globals.cs | 12 ++++++--- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index 4a9e667..0d207e1 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -1,10 +1,10 @@ -using AngleSharp.Html.Dom; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using AngleSharp.Dom; using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; using static AMDiscordRPC.Playlist; @@ -129,7 +129,7 @@ public static async Task GetCover(string album, string searchSt { try { - log.Debug($"https://music.apple.com/us/search?term={searchStr}"); + log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); SQLCoverResponse cover = GetAlbumDataFromSQL(album); if (cover != null) { diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index 684c33e..46a17ac 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -1,4 +1,5 @@ -using System; +using Amazon.Runtime.Internal.Transform; +using System; using System.Collections.Generic; using System.Data.SQLite; using System.Linq; @@ -11,7 +12,11 @@ internal class Database private static SQLiteConnection sqlite; public static readonly Dictionary sqlMap = new Dictionary() { - {"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, + //{"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, + {"coverTableNew", "coverID INTEGER PRIMARY KEY AUTOINCREMENT, staticCoverURL TEXT NOT NULL, isAnimated BOOLEAN CHECK (isAnimated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT"}, + {"artistTable", "artistID INTEGER PRIMARY KEY AUTOINCREMENT, artistName TEXT NOT NULL, artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistProfileSource TEXT"}, + {"albumTable", "albumID INTEGER PRIMARY KEY AUTOINCREMENT, albumName TEXT NOT NULL, albumURL TEXT UNIQUE, isSingle BOOLEAN CHECK (isSingle IN (0,1)), coverID INTEGER, artistID INTEGER, FOREIGN KEY (coverID) REFERENCES coverTableNew(coverID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, + {"songTable", "songTitle TEXT, songURL TEXT, albumID INTEGER, artistID INTEGER, FOREIGN KEY (albumID) REFERENCES albumTable(albumID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, {"creds", "S3_accessKey TEXT, S3_secretKey TEXT, S3_serviceURL TEXT, S3_bucketName TEXT, S3_bucketURL TEXT, S3_isSpecificKey BOOLEAN CHECK (S3_isSpecificKey IN (0,1)), FFmpegPath TEXT" }, {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, {"clientSettings", "smallImage INTEGER"} @@ -39,7 +44,7 @@ public static void CheckDatabaseIntegrity() { try { - //CheckForeignKeys(); we don't have use case for relationships rn so no need to waste resources on this check + CheckForeignKeys(); CheckTables(); CheckColumns(); } @@ -133,12 +138,21 @@ private static void CheckColumns() { foreach (var table in sqlMap.Keys) { - SQLiteDataReader data = ExecuteReaderCommand($"PRAGMA table_info({table})"); + SQLiteDataReader data = ExecuteReaderCommand($"SELECT * FROM sqlite_master"); Dictionary tableData = new Dictionary(); while (data.Read()) { - tableData.Add(data.GetString(1), new ColumnInfo(data.GetString(2), data.GetBoolean(3), (!data.IsDBNull(4)) ? data.GetString(4) : null, data.GetBoolean(5))); + if (data.GetString(0) == "table" && data.GetString(2) == table) + { + string sqlStr = string.Join("(", data.GetString(4).Split(new[] { "CREATE TABLE " }, StringSplitOptions.None)[1].Split('(').Skip(1)).TrimEnd(1); + var temp = ConvertSQLStringToColumnInfo(sqlStr); + foreach (var keyValuePair in temp) + { + //log.Debug($"{keyValuePair.Key}, autoIncrement: {keyValuePair.Value.isAutoIncrementing}, defaultValue: {keyValuePair.Value.defaultValue}, foreignKey: [Key: {keyValuePair.Value.foreignKey?.key}, refColumn: {keyValuePair.Value.foreignKey?.refColumn}, refTable: {keyValuePair.Value.foreignKey?.refTable}], nullCheck: {keyValuePair.Value.nullCheck}, primaryKey: {keyValuePair.Value.primaryKey}, type: {keyValuePair.Value.type}"); + tableData.Add(keyValuePair.Key, keyValuePair.Value); + } + } } foreach (var item in ConvertSQLStringToColumnInfo(sqlMap[table])) @@ -148,7 +162,7 @@ private static void CheckColumns() if (!item.Value.Equals(column) && column != null) { log.Debug($"Corrupted/Outdated column:{SQLInfo.Split(' ')[0]} found."); - if (!item.Value.primaryKey && ((item.Value.nullCheck && item.Value.defaultValue != null) || !item.Value.nullCheck)) + if (!item.Value.primaryKey && ((item.Value.nullCheck && item.Value.defaultValue != null) || !item.Value.nullCheck) && item.Value.foreignKey == null) { ExecuteNonQueryCommand($"ALTER TABLE {table} DROP COLUMN {SQLInfo.Split(' ')[0]}"); ExecuteNonQueryCommand($"ALTER TABLE {table} ADD COLUMN {SQLInfo}"); @@ -174,11 +188,14 @@ private static Dictionary ConvertSQLStringToColumnInfo(strin foreach (var column in columns) { string[] splitStr = column.Split(' '); + if (splitStr[0] == "FOREIGN") continue; columnsMap.Add(splitStr[0], new ColumnInfo( splitStr[1], column.Contains("NOT NULL"), (column.Contains("DEFAULT")) ? column.Split(new[] { "DEFAULT " }, StringSplitOptions.None)[1] : null, //This is not a proper way to do this but it works for now (DEFAULT value must be on the last section of the SQL Command) - column.Contains("PRIMARY KEY") + column.Contains("PRIMARY KEY"), + column.Contains("AUTOINCREMENT"), + (sqlStr.Contains($"FOREIGN KEY ({splitStr[0]})")) ? new ForeignKey(splitStr[0], columns.Where(a => a.Contains($"FOREIGN KEY ({splitStr[0]})")).First().Split(new[] { "REFERENCES " }, StringSplitOptions.None)[1].Split('(')[0], columns.Where(a => a.Contains($"FOREIGN KEY ({splitStr[0]})")).First().Split(new[] { "REFERENCES " }, StringSplitOptions.None)[1].Split('(')[1].Split(')')[0]) : null )); } return columnsMap; @@ -232,13 +249,17 @@ private class ColumnInfo public bool nullCheck { get; set; } public string defaultValue { get; set; } public bool primaryKey { get; set; } + public bool isAutoIncrementing { get; set; } + public ForeignKey foreignKey { get; set; } - public ColumnInfo(string type, bool nullCheck, string defaultValue, bool primaryKey) + public ColumnInfo(string type, bool nullCheck, string defaultValue, bool primaryKey, bool isAutoIncrementing, ForeignKey foreignKey = null) { this.type = type; this.nullCheck = nullCheck; this.defaultValue = defaultValue; this.primaryKey = primaryKey; + this.isAutoIncrementing = isAutoIncrementing; + this.foreignKey = foreignKey; } public override bool Equals(object obj) @@ -247,7 +268,23 @@ public override bool Equals(object obj) type == other.type && nullCheck == other.nullCheck && defaultValue == other.defaultValue && - primaryKey == other.primaryKey; + primaryKey == other.primaryKey && + isAutoIncrementing == other.isAutoIncrementing && + foreignKey == other.foreignKey; + } + } + + public class ForeignKey + { + public string key { get; set; } + public string refTable { get; set; } + public string refColumn { get; set; } + + public ForeignKey(string key, string refTable, string refColumn) + { + this.key = key; + this.refTable = refTable; + this.refColumn = refColumn; } } diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index c4fbd51..e0a1bff 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -55,9 +55,9 @@ public static void ConfigureLogger() LevelMax = Level.Fatal, LevelMin = Level.Info }; - #if DEBUG - lrf.LevelMin = Level.Debug; - #endif +#if DEBUG + lrf.LevelMin = Level.Debug; +#endif lrf.ActivateOptions(); PatternLayout pl = new PatternLayout @@ -296,9 +296,13 @@ public override bool Equals(object obj) return obj is WebSongResponse other && artworkURL == other.artworkURL && trackURL == other.trackURL && - trackName == other.trackName && + trackName == other.trackName && artistURL == other.artistURL; } } + public static String TrimEnd(this String str, int count) + { + return str.Substring(0, str.Length - count); + } } } \ No newline at end of file From 7e85228dc8aee0ed7c7a4a501d34fb7165f0d75f Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Sun, 5 Oct 2025 15:30:06 +0300 Subject: [PATCH 4/8] Add FullScreen Tweak This fixes the issue with Apple Music is not going fullscreen on non primary monitors. --- AMDiscordRPC/AMDiscordRPC.cs | 2 +- AMDiscordRPC/Globals.cs | 99 ++++++++++++++++++++++++++++++++++++ AMDiscordRPC/UI.cs | 44 +++++++++++++++- 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index 0657ea7..8305796 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -88,7 +88,7 @@ static void AMEvent() { for (var i = 0; i < windows.Length; i++) { - if (windows[i].Name == "Apple Music") window = windows[i]; + if (windows[i].Name == "Apple Music" && windows[i].FindFirstChild().Name == "Non Client Input Sink Window") window = windows[i]; } } else if (windows.Length == 1) diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index e0a1bff..ee644c5 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -216,6 +216,105 @@ public enum SmallImage None } + public enum GWLP { + EXSTYLE = -20, + HINSTANCE = -6, + HWNDPARENT = -8, + ID = -12, + STYLE = -16, + USERDATA = -21, + WNDPROC = -4 + } + + public enum WS : long + { + BORDER = 0x00800000L, + CAPTION = 0x00C00000L, + CHILD = 0x40000000L, + CHILDWINDOW = 0x40000000L, + CLIPCHILDREN = 0x02000000L, + CLIPSIBLINGS = 0x04000000L, + DISABLED = 0x08000000L, + DLGFRAME = 0x00400000L, + GROUP = 0x00020000L, + HSCROLL = 0x00100000L, + ICONIC = 0x20000000L, + MAXIMIZE = 0x01000000L, + MAXIMIZEBOX = 0x00010000L, + MINIMIZE = 0x20000000L, + MINIMIZEBOX = 0x00020000L, + OVERLAPPED = 0x00000000L, + OVERLAPPEDWINDOW = (OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX), + POPUP = 0x80000000L, + POPUPWINDOW = (POPUP | BORDER | SYSMENU), + SIZEBOX = 0x00040000L, + SYSMENU = 0x00080000L, + TABSTOP = 0x00010000L, + THICKFRAME = 0x00040000L, + TILED = 0x00000000L, + TILEDWINDOW = (OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX), + VISIBLE = 0x10000000L, + VSCROLL = 0x00200000L + } + + public enum WS_EX : long + { + ACCEPTFILES = 0x00000010L, + APPWINDOW = 0x00040000L, + CLIENTEDGE = 0x00000200L, + COMPOSITED = 0x02000000L, + CONTEXTHELP = 0x00000400L, + CONTROLPARENT = 0x00010000L, + DLGMODALFRAME = 0x00000001L, + LAYERED = 0x00080000L, + LAYOUTRTL = 0x00400000L, + LEFT = 0x00000000L, + LEFTSCROLLBAR = 0x00004000L, + LTRREADING = 0x00000000L, + MDICHILD = 0x00000040L, + NOACTIVATE = 0x08000000L, + NOINHERITLAYOUT = 0x00100000L, + NOPARENTNOTIFY = 0x00000004L, + NOREDIRECTIONBITMAP = 0x00200000L, + OVERLAPPEDWINDOW = (WINDOWEDGE | CLIENTEDGE), + PALETTEWINDOW = (WINDOWEDGE | TOOLWINDOW | TOPMOST), + RIGHT = 0x00001000L, + RIGHTSCROLLBAR = 0x00000000L, + RTLREADING = 0x00002000L, + STATICEDGE = 0x00020000L, + TOOLWINDOW = 0x00000080L, + TOPMOST = 0x00000008L, + TRANSPARENT = 0x00000020L, + WINDOWEDGE = 0x00000100L + } + + public enum HWND + { + BOTTOM = 1, + NOTOPMOST = -2, + TOP = 0, + TOPMOST = -1 + } + + public enum SWP + { + ASYNCWINDOWPOS = 0x4000, + DEFERERASE = 0x2000, + DRAWFRAME = 0x0020, + FRAMECHANGED = 0x0020, + HIDEWINDOW = 0x0080, + NOACTIVATE = 0x0010, + NOCOPYBITS = 0x0100, + NOMOVE = 0x0002, + NOOWNERZORDER = 0x0200, + NOREDRAW = 0x0008, + NOREPOSITION = 0x0200, + NOSENDCHANGING = 0x0400, + NOSIZE = 0x0001, + NOZORDER = 0x0004, + SHOWWINDOW = 0x0040 + } + public class SongData : EventArgs { public string SongName { get; set; } diff --git a/AMDiscordRPC/UI.cs b/AMDiscordRPC/UI.cs index 1043fff..2b4a651 100644 --- a/AMDiscordRPC/UI.cs +++ b/AMDiscordRPC/UI.cs @@ -1,8 +1,9 @@ using AMDiscordRPC.UIComponents; +using FlaUI.UIA3; using System; using System.Diagnostics; -using System.Drawing; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Forms; @@ -10,6 +11,7 @@ using static AMDiscordRPC.Globals; using Application = System.Windows.Application; using OpenFileDialog = Microsoft.Win32.OpenFileDialog; +using Window = FlaUI.Core.AutomationElements.Window; namespace AMDiscordRPC { @@ -20,6 +22,15 @@ internal class UI private static Application app; private static Thread mainThread = Thread.CurrentThread; + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern long GetWindowLongPtrA(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern long SetWindowLongPtrA(IntPtr hWnd, int nIndex, long dwNewLong); + public static void CreateUI() { Thread thread = new Thread(() => @@ -69,6 +80,36 @@ public static void FFmpegDialog() thread.Start(); } + public static void FullScreenTweak() + { + using (var automation = new UIA3Automation()) + { + Window lyricScreenWindow = null; + foreach (var window in AppleMusicProc.GetAllTopLevelWindows(automation)) + { + if (window.FindFirstChild().Name != "Non Client Input Sink Window" && window.Name == "Apple Music") lyricScreenWindow = window; + } + + if (lyricScreenWindow != null) + { + IntPtr lyricsScreenHandler = (IntPtr)lyricScreenWindow.Properties.NativeWindowHandle; + Screen lyricsScreenHandlerCurrentMonitor = Screen.FromHandle(lyricsScreenHandler); + long style = GetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.STYLE); + style &= ~((long) WS.CAPTION | (long) WS.THICKFRAME); + SetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.STYLE, style); + + long exStyle = GetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.EXSTYLE); + exStyle &= ~((long) WS_EX.DLGMODALFRAME | (long) WS_EX.CLIENTEDGE | (long) WS_EX.STATICEDGE); + SetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.EXSTYLE, exStyle); + + SetWindowPos(lyricsScreenHandler, (int) HWND.TOPMOST, lyricsScreenHandlerCurrentMonitor.Bounds.Left, + lyricsScreenHandlerCurrentMonitor.Bounds.Top, lyricsScreenHandlerCurrentMonitor.Bounds.Width, + lyricsScreenHandlerCurrentMonitor.Bounds.Height, + (uint) SWP.NOOWNERZORDER | (uint) SWP.FRAMECHANGED | (uint) SWP.SHOWWINDOW); + } + } + } + public class AMDiscordRPCTray { private static NotifyIcon notifyIcon = new NotifyIcon(); @@ -111,6 +152,7 @@ public AMDiscordRPCTray() notifySongState, s3Menu, optionsMenu, + new MenuItem("Fix Fullscreen", (s,e) => FullScreenTweak()), new MenuItem("Show Latest Log", (s,e) => { Process.Start("notepad", $"{Path.Combine(Directory.GetCurrentDirectory(), @"logs\latest.log")}"); }), new MenuItem("Exit", (s, e) => { Environment.Exit(0); }) } From 9e6e9375d56f3e2f003b7153f7da7e794aac5a5b Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:55:35 +0300 Subject: [PATCH 5/8] Fix the not registering to database issue on albums with apostrophe also + Add WebView2 (in the near future we will switch to React based UI prob.) + Change how albums gets registered in first place + New data types for new database structure --- AMDiscordRPC/AMDiscordRPC.cs | 5 +- AMDiscordRPC/AMDiscordRPC.csproj | 14 +++ AMDiscordRPC/App.config | 4 + AMDiscordRPC/Covers.cs | 4 +- AMDiscordRPC/Database.cs | 146 +++++++++++++++++++++++++++---- AMDiscordRPC/packages.config | 1 + 6 files changed, 153 insertions(+), 21 deletions(-) diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index 8305796..9aa03e6 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -26,7 +26,7 @@ static void Main(string[] args) InitDiscordRPC(); AttachToAM(); AMSongDataEvent.SongChanged += async (sender, x) => - { + { log.Info($"Song: {x.SongName} \\ Artist and Album: {x.ArtistandAlbumName}"); AMDiscordRPCTray.ChangeSongState($"{x.ArtistandAlbumName.Split('—')[0]} - {x.SongName}"); if (x.ArtistandAlbumName == oldAlbumnArtist && oldData.Assets.LargeImageKey != null) @@ -43,7 +43,7 @@ static void Main(string[] args) SetPresence(x, httpRes); oldAlbumnArtist = x.ArtistandAlbumName; } - }; + }; CheckDatabaseIntegrity(); ConfigureFromDB(); CheckFFmpeg(); @@ -181,7 +181,6 @@ static void AMEvent() } else log.Debug("Continue"); string idontknowwhatshouldinamethisbutitsaboutalbum = (isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()); - CheckAndInsertAlbum(idontknowwhatshouldinamethisbutitsaboutalbum.Split('—')[1]); Task t = new Task(async () => { httpRes = await GetCover(idontknowwhatshouldinamethisbutitsaboutalbum.Split('—')[1], Uri.EscapeDataString((isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()) + $" {currentSong}")); diff --git a/AMDiscordRPC/AMDiscordRPC.csproj b/AMDiscordRPC/AMDiscordRPC.csproj index 53f51a0..1178e86 100644 --- a/AMDiscordRPC/AMDiscordRPC.csproj +++ b/AMDiscordRPC/AMDiscordRPC.csproj @@ -100,6 +100,9 @@ Resources\Logo Black.ico + + false + ..\packages\AngleSharp.1.3.0\lib\net472\AngleSharp.dll @@ -138,6 +141,15 @@ ..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.7\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Core.dll + + + ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.WinForms.dll + + + ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Wpf.dll + ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll @@ -317,8 +329,10 @@ + + \ No newline at end of file diff --git a/AMDiscordRPC/App.config b/AMDiscordRPC/App.config index 2012abb..64a9a5f 100644 --- a/AMDiscordRPC/App.config +++ b/AMDiscordRPC/App.config @@ -49,6 +49,10 @@ + + + + diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index 0d207e1..294c511 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -34,7 +34,7 @@ private static async Task AsyncFetchiTunes(string album, string imageRes["results"][0]["collectionName"].ToString(), imageRes["results"][0]["artistViewUrl"].ToString() ); - UpdateAlbum(new SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); + InsertAlbum(new SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); CoverThread = null; return webRes; } @@ -83,7 +83,7 @@ public static async Task AsyncAMFetch(string album, string sear document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href") ); CoverThread = null; - UpdateAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); + InsertAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); return webRes; } else diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index 46a17ac..03ec51a 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -1,5 +1,4 @@ -using Amazon.Runtime.Internal.Transform; -using System; +using System; using System.Collections.Generic; using System.Data.SQLite; using System.Linq; @@ -12,12 +11,12 @@ internal class Database private static SQLiteConnection sqlite; public static readonly Dictionary sqlMap = new Dictionary() { - //{"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, + {"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, // will be replaced {"coverTableNew", "coverID INTEGER PRIMARY KEY AUTOINCREMENT, staticCoverURL TEXT NOT NULL, isAnimated BOOLEAN CHECK (isAnimated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT"}, {"artistTable", "artistID INTEGER PRIMARY KEY AUTOINCREMENT, artistName TEXT NOT NULL, artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistProfileSource TEXT"}, {"albumTable", "albumID INTEGER PRIMARY KEY AUTOINCREMENT, albumName TEXT NOT NULL, albumURL TEXT UNIQUE, isSingle BOOLEAN CHECK (isSingle IN (0,1)), coverID INTEGER, artistID INTEGER, FOREIGN KEY (coverID) REFERENCES coverTableNew(coverID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, {"songTable", "songTitle TEXT, songURL TEXT, albumID INTEGER, artistID INTEGER, FOREIGN KEY (albumID) REFERENCES albumTable(albumID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, - {"creds", "S3_accessKey TEXT, S3_secretKey TEXT, S3_serviceURL TEXT, S3_bucketName TEXT, S3_bucketURL TEXT, S3_isSpecificKey BOOLEAN CHECK (S3_isSpecificKey IN (0,1)), FFmpegPath TEXT" }, + {"creds", "S3_accessKey TEXT, S3_secretKey TEXT, S3_serviceURL TEXT, S3_bucketName TEXT, S3_bucketURL TEXT, S3_isSpecificKey BOOLEAN CHECK (S3_isSpecificKey IN (0,1)), FFmpegPath TEXT, LastFMToken TEXT" }, {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, {"clientSettings", "smallImage INTEGER"} }; @@ -105,18 +104,18 @@ private static void CheckTables() public static void UpdateAlbum(SQLCoverResponse data) { - ExecuteNonQueryCommand($"UPDATE coverTable SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) WHERE album = '{data.album}'"); + ExecuteNonQueryCommand($"UPDATE coverTable SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) WHERE album = @album", new[] { new SQLiteParameter("@album", data.album)}); } - public static void CheckAndInsertAlbum(string album) + public static void InsertAlbum(SQLCoverResponse data) { - if (ExecuteScalarCommand($"SELECT album from coverTable WHERE album = '{album}'") == null) - ExecuteNonQueryCommand($"INSERT INTO coverTable(album) VALUES ('{album}')"); + if (ExecuteScalarCommand($"SELECT album from coverTable WHERE album = @album", new[] { new SQLiteParameter("@album", data.album) }) == null) + ExecuteNonQueryCommand($@"INSERT INTO coverTable(album, {string.Join(", ", data.GetNotNullKeys())}) VALUES (@album, {string.Join(", ", data.GetNotNullValues())})", new[] {new SQLiteParameter("@album", data.album)}); } public static SQLCoverResponse GetAlbumDataFromSQL(string album) { - using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT * FROM coverTable WHERE album = '{album}' LIMIT 1")) + using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT * FROM coverTable WHERE album = @album LIMIT 1", new[] { new SQLiteParameter("@album", album) })) { while (reader.Read()) { @@ -134,6 +133,32 @@ public static SQLCoverResponse GetAlbumDataFromSQL(string album) return null; } + public static SQLSongResponse GetSongFromDB(string song, string album, string artist) + { + string cmd = @" + SELECT + IIF(coverTableNew.isAnimated = 1, coverTableNew.animatedURL, coverTableNew.staticCoverURL) as coverURL, + artistTable.artistRedirURL as artistRedirURL, + artistTable.artistProfileSource as artistProfileSource, + albumTable.albumURL, + songTable.songURL + FROM songTable + INNER JOIN albumTable on albumTable.albumID = songTable.albumID + INNER JOIN artistTable on artistTable.artistID = albumTable.artistID + INNER JOIN coverTableNew on coverTableNew.coverID = albumTable.coverID + WHERE + artistTable.artistName = @artist + AND songTable.songTitle = @song + AND albumTable.albumName = @album; + "; + using (SQLiteDataReader reader = ExecuteReaderCommand(cmd, new[] { new SQLiteParameter("@album", album), new SQLiteParameter("@song", song), new SQLiteParameter("@artist", artist) })) + { + + } + + return null; + } + private static void CheckColumns() { foreach (var table in sqlMap.Keys) @@ -201,11 +226,12 @@ private static Dictionary ConvertSQLStringToColumnInfo(strin return columnsMap; } - public static object ExecuteScalarCommand(string command) + public static object ExecuteScalarCommand(string command, SQLiteParameter[] parameters = null) { try { - SQLiteCommand cmd = new SQLiteCommand($@"{command}", sqlite); + SQLiteCommand cmd = new SQLiteCommand(command, sqlite); + if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteScalar(); } catch (Exception ex) @@ -215,11 +241,12 @@ public static object ExecuteScalarCommand(string command) } } - public static SQLiteDataReader ExecuteReaderCommand(string command) + public static SQLiteDataReader ExecuteReaderCommand(string command, SQLiteParameter[] parameters = null) { try { - SQLiteCommand cmd = new SQLiteCommand($@"{command}", sqlite); + SQLiteCommand cmd = new SQLiteCommand(command, sqlite); + if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteReader(); } catch (Exception ex) @@ -229,11 +256,12 @@ public static SQLiteDataReader ExecuteReaderCommand(string command) } } - public static int ExecuteNonQueryCommand(string command) + public static int ExecuteNonQueryCommand(string command, SQLiteParameter[] parameters = null) { try { - SQLiteCommand cmd = new SQLiteCommand($@"{command}", sqlite); + SQLiteCommand cmd = new SQLiteCommand(command, sqlite); + if (parameters != null) cmd.Parameters.AddRange(parameters); return cmd.ExecuteNonQuery(); } catch (Exception ex) @@ -274,7 +302,7 @@ public override bool Equals(object obj) } } - public class ForeignKey + private class ForeignKey { public string key { get; set; } public string refTable { get; set; } @@ -321,5 +349,91 @@ public List GetNotNullValues() } } + + public class SQLSongResponse + { + public SQLCoverData? cover; + public SQLAlbumData? album; + public SQLArtistData? artist; + public SQLSongData? song; + + public SQLSongResponse(SQLCoverData cover, SQLAlbumData album, SQLArtistData artist, SQLSongData song) + { + this.cover = cover; + this.album = album; + this.artist = artist; + this.song = song; + } + } + + public class SQLCoverData + { + public int id; + public string staticCoverURL; + public bool? isAnimated; + public string? streamURL; + public string? animatedURL; + + public SQLCoverData(int id, string staticCoverURL, bool? isAnimated, string? streamURL, string? animatedURL) + { + this.id = id; + this.staticCoverURL = staticCoverURL; + this.isAnimated = isAnimated; + this.streamURL = streamURL; + this.animatedURL = animatedURL; + } + } + + public class SQLArtistData + { + public int id; + public string artistName; + public string artistRedirURL; + public string? artistProfileSource; + + public SQLArtistData(int id, string artistName, string artistRedirURL, string? artistProfileSource) + { + this.id = id; + this.artistName = artistName; + this.artistRedirURL = artistRedirURL; + this.artistProfileSource = artistProfileSource; + } + } + + public class SQLAlbumData + { + public int id; + public string albumName; + public string albumURL; + public bool isSingle; + public int? coverID; // Foreign Key + public int? artistID; // Foreign Key + + public SQLAlbumData(int id, string albumName, string albumURL, bool isSingle, int coverID, int artistID) + { + this.id = id; + this.albumName = albumName; + this.albumURL = albumURL; + this.isSingle = isSingle; + this.coverID = coverID; + this.artistID = artistID; + } + } + + public class SQLSongData + { + public string songTitle; + public string songURL; + public int? albumID; // Foreign Key + public int? artistID; // Foreign Key + + public SQLSongData(string songTitle, string songURL, int albumID, int artistID) + { + this.songTitle = songTitle; + this.songURL = songURL; + this.albumID = albumID; + this.artistID = artistID; + } + } } } diff --git a/AMDiscordRPC/packages.config b/AMDiscordRPC/packages.config index 5d6c8c1..79da35e 100644 --- a/AMDiscordRPC/packages.config +++ b/AMDiscordRPC/packages.config @@ -11,6 +11,7 @@ + From bd9e48f45e0188253433d54b37db475f3b6794b7 Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:41:25 +0300 Subject: [PATCH 6/8] =?UTF-8?q?man=20i=20can't=20even=20find=20commit=20su?= =?UTF-8?q?mmary=20for=20this=20but=20a=20lot=20of=20feature=20works=20wit?= =?UTF-8?q?h=20new=20db=20and=20a=20lot=20of=20them=20are=20also=20not=20w?= =?UTF-8?q?orking=20in=20addition=20there=20is=20Cloudflare=20implementati?= =?UTF-8?q?on=20in=20this=20which=20isn't=20complete=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AMDiscordRPC/AMDiscordRPC.cs | 39 ++-- AMDiscordRPC/AMDiscordRPC.csproj | 12 +- AMDiscordRPC/App.config | 2 +- AMDiscordRPC/Cloudflare.cs | 44 +++++ AMDiscordRPC/Covers.cs | 153 +++++++++++++++- AMDiscordRPC/Database.cs | 127 +++++++++++-- AMDiscordRPC/Discord.cs | 28 +-- AMDiscordRPC/Globals.cs | 168 +++++++++++++++++- AMDiscordRPC/Properties/AssemblyInfo.cs | 2 +- AMDiscordRPC/Properties/Resources.Designer.cs | 14 +- AMDiscordRPC/packages.config | 2 +- 11 files changed, 539 insertions(+), 52 deletions(-) create mode 100644 AMDiscordRPC/Cloudflare.cs diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index 9aa03e6..05d99f9 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using static AMDiscordRPC.AppleMusic; +using static AMDiscordRPC.Cloudflare; using static AMDiscordRPC.Covers; using static AMDiscordRPC.Database; using static AMDiscordRPC.Discord; @@ -21,10 +22,12 @@ internal class AMDiscordRPC static void Main(string[] args) { ConfigureLogger(); + // SetToken(); InitRegion(); CreateUI(); InitDiscordRPC(); - AttachToAM(); + AttachToAM(); + // ListBuckets(); AMSongDataEvent.SongChanged += async (sender, x) => { log.Info($"Song: {x.SongName} \\ Artist and Album: {x.ArtistandAlbumName}"); @@ -35,10 +38,16 @@ static void Main(string[] args) } else { - if (httpRes.Equals(new WebSongResponse()) || CoverThread != null) - { - httpRes = await GetCover(x.ArtistandAlbumName.Split('—')[1], Uri.EscapeDataString(x.ArtistandAlbumName + $" {x.SongName}")); - log.Debug($"Set Cover: {((httpRes.artworkURL != null) ? httpRes.artworkURL : null)}"); + if (httpRes.Equals(new SQLRPCResponse()) || CoverThread != null) + { + httpRes = await GetCover( + new AppleMusicScrapedData( + x.ArtistandAlbumName.Split(new string[] { " — " }, StringSplitOptions.None)[0], + x.SongName, + x.ArtistandAlbumName.Split(new string[] { " — " }, StringSplitOptions.None)[1], + (x.isSingle) ? SecondaryType.Single : (x.IsMV) ? SecondaryType.MV : (x.ArtistandAlbumName.Contains(" - EP") ? SecondaryType.EP : SecondaryType.Album) + )); + log.Debug($"Set Cover: {((httpRes.coverURL != null) ? httpRes.coverURL : null)}"); } SetPresence(x, httpRes); oldAlbumnArtist = x.ArtistandAlbumName; @@ -50,7 +59,6 @@ static void Main(string[] args) InitS3(); AMEvent(); } - static void AMEvent() { using (var automation = new UIA3Automation()) @@ -127,6 +135,7 @@ static void AMEvent() string previousSong = string.Empty; string previousArtistAlbum = string.Empty; string lastFetchedArtistAlbum = string.Empty; + string lastFetchedSong = string.Empty; AudioFormat format = AudioFormat.AAC; bool resetStatus = false; double oldValue = 0; @@ -137,7 +146,7 @@ static void AMEvent() { try { - var currentSong = listeningInfo[0].Name; + var currentSong = (listeningInfo[0].Properties.Name.IsSupported == true && listeningInfo[0].Name != "Connecting…") ? listeningInfo[0].Name : lastFetchedSong; var currentArtistAlbum = (listeningInfo[1].Properties.Name.IsSupported == true) ? listeningInfo[1].Name : lastFetchedArtistAlbum; var dashSplit = currentArtistAlbum.Split('-'); var subractThis = TimeSpan.FromSeconds(slider.AsSlider().Value + 1); @@ -172,7 +181,7 @@ static void AMEvent() oldValue = slider.AsSlider().Value; } - if (currentArtistAlbum != lastFetchedArtistAlbum) + if (currentArtistAlbum != lastFetchedArtistAlbum || currentSong != lastFetchedSong) { if (CoverThread != null) { @@ -183,12 +192,20 @@ static void AMEvent() string idontknowwhatshouldinamethisbutitsaboutalbum = (isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()); Task t = new Task(async () => { - httpRes = await GetCover(idontknowwhatshouldinamethisbutitsaboutalbum.Split('—')[1], Uri.EscapeDataString((isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()) + $" {currentSong}")); - log.Debug($"Set Cover: {((httpRes.artworkURL != null) ? httpRes.artworkURL : null)}"); + httpRes = await GetCover( + new AppleMusicScrapedData( + idontknowwhatshouldinamethisbutitsaboutalbum.Split(new string[] { " — " }, StringSplitOptions.None)[0], + currentSong, + idontknowwhatshouldinamethisbutitsaboutalbum.Split(new string[] { " — " }, StringSplitOptions.None)[1], + (isSingle) ? SecondaryType.Single : (idontknowwhatshouldinamethisbutitsaboutalbum.Split('—').Length <= 1) ? SecondaryType.MV : (idontknowwhatshouldinamethisbutitsaboutalbum.Contains(" - EP") ? SecondaryType.EP : SecondaryType.Album) + ) + ); + log.Debug($"Set Cover: {((httpRes.coverURL != null) ? httpRes.coverURL : null)}"); }); CoverThread = t; t.Start(); lastFetchedArtistAlbum = currentArtistAlbum; + lastFetchedSong = currentSong; } if (slider.AsSlider().Maximum != 0 && slider.AsSlider().Value != 0 && endTime != startTime && (currentSong != previousSong || currentArtistAlbum != previousArtistAlbum) && oldEndTime != endTime && oldStartTime != startTime) @@ -215,7 +232,7 @@ static void AMEvent() oldValue = 0; oldStartTime = startTime; oldEndTime = endTime; - AMSongDataEvent.ChangeSong(new SongData(currentSong, (isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()), currentArtistAlbum.Split('—').Length <= 1, startTime, endTime, format)); + AMSongDataEvent.ChangeSong(new SongData(currentSong, (isSingle) ? string.Join("-", dashSplit.Take(dashSplit.Length - 1).ToArray()) : string.Join("—", currentArtistAlbum.Split('—').Take(2).ToArray()), currentArtistAlbum.Split('—').Length <= 1, startTime, endTime, format, isSingle)); } if (playButton?.Name != null && (localizedPlay != null && localizedPlay == playButton?.Name || localizedStop != null && localizedStop != playButton?.Name)) diff --git a/AMDiscordRPC/AMDiscordRPC.csproj b/AMDiscordRPC/AMDiscordRPC.csproj index 1178e86..028c1cc 100644 --- a/AMDiscordRPC/AMDiscordRPC.csproj +++ b/AMDiscordRPC/AMDiscordRPC.csproj @@ -70,7 +70,7 @@ latest prompt true - 0 + 1 bin\x64\Release\ @@ -104,6 +104,7 @@ false + ..\packages\AngleSharp.1.3.0\lib\net472\AngleSharp.dll @@ -113,8 +114,8 @@ ..\packages\AWSSDK.S3.4.0.6.2\lib\net472\AWSSDK.S3.dll - - ..\packages\DiscordRichPresence.1.5.0.51\lib\net45\DiscordRPC.dll + + ..\packages\DiscordRichPresence.1.6.1.70\lib\net45\DiscordRPC.dll ..\packages\EntityFramework.6.5.1\lib\net45\EntityFramework.dll @@ -186,6 +187,7 @@ ..\packages\System.Drawing.Common.9.0.7\lib\net462\System.Drawing.Common.dll + ..\packages\System.IO.Pipelines.9.0.7\lib\net462\System.IO.Pipelines.dll @@ -201,6 +203,7 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Security.AccessControl.6.0.1\lib\net461\System.Security.AccessControl.dll @@ -245,6 +248,7 @@ + @@ -285,7 +289,7 @@ - ResXFileCodeGenerator + PublicResXFileCodeGenerator Resources.Designer.cs diff --git a/AMDiscordRPC/App.config b/AMDiscordRPC/App.config index 64a9a5f..1da3d7b 100644 --- a/AMDiscordRPC/App.config +++ b/AMDiscordRPC/App.config @@ -69,4 +69,4 @@ - \ No newline at end of file + diff --git a/AMDiscordRPC/Cloudflare.cs b/AMDiscordRPC/Cloudflare.cs new file mode 100644 index 0000000..7d480dc --- /dev/null +++ b/AMDiscordRPC/Cloudflare.cs @@ -0,0 +1,44 @@ +using Amazon.Runtime.Endpoints; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using static AMDiscordRPC.Globals; + +namespace AMDiscordRPC +{ + internal class Cloudflare : Globals.CloudflareTypes + { + public static HttpClient CfHttpClient = new HttpClient(); + + public static async void SetToken() + { + CfHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {CfAccountCredentials.api_token}"); + await VerifyToken(); + } + + private static async Task VerifyToken() + { + var response = await CfHttpClient.GetAsync($"{endpointV4}/accounts/{CfAccountCredentials.account_id}/tokens/verify"); + Response deserialized = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + if (deserialized.result.status == "active") + log.Info("Token valid and active"); + else + log.Error("Token is not valid."); + } + + public static async Task ListBuckets() + { + CfHttpClient.DefaultRequestHeaders.Add("cf-r2-jurisdiction", "eu"); + var response = await CfHttpClient.GetAsync($"{endpointV4}/accounts/{CfAccountCredentials.account_id}/r2/buckets"); + Response deserialized = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + if (deserialized.success) + deserialized.result = JsonConvert.DeserializeObject>(deserialized.result.buckets.ToString()); + return deserialized; + } + } +} diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index 294c511..e019ea2 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -1,10 +1,13 @@ -using AngleSharp.Dom; +using Amazon.Runtime.Documents; +using AngleSharp.Dom; using AngleSharp.Html.Dom; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Windows.Markup; +using System.Xml.Linq; using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; using static AMDiscordRPC.Playlist; @@ -15,13 +18,58 @@ public class Covers { public static Task CoverThread; + private static async Task AsyncFetchiTunes(AppleMusicScrapedData data) + { + try + { + //idk why but sometimes when you search as "Artist - Album Track" and if Album and Track named same it returns random song from album + //ex: "Poppy - Negative Spaces negative spaces" Returns "Poppy - New Way Out" as a track link + HttpResponseMessage iTunesReq = await hclient.GetAsync($"https://itunes.apple.com/search?term={data.GetSearchString()}&limit=1&entity=song&country={AMRegion}"); + if (iTunesReq.IsSuccessStatusCode) + { + dynamic imageRes = JObject.Parse(await iTunesReq.Content.ReadAsStringAsync()); + if (imageRes["resultCount"] != 0) + { + SQLSongResponse songData = new SQLSongResponse( + new SQLCoverData(0, imageRes["results"][0]["artworkUrl100"].ToString(), null, null, null), + new SQLAlbumData(0, data.AlbumName, imageRes["results"][0]["trackViewUrl"].ToString().Split(new string[] { "?i=" }, StringSplitOptions.None)[0], data.Type == SecondaryType.Single, 0, 0), + new SQLArtistData(0, data.ArtistName, imageRes["results"][0]["artistViewUrl"].ToString().Split(new string[] { "?uo=" }, StringSplitOptions.None)[0], await AsyncArtistProfileFetch(imageRes["results"][0]["artistViewUrl"].ToString().Split(new string[] { "?uo=" }, StringSplitOptions.None)[0])), + new SQLSongData(data.SongName, imageRes["results"][0]["trackViewUrl"].ToString().Split(new string[] { "&uo=" }, StringSplitOptions.None)[0], 0, 0) + ); + InsertNew(songData); + CoverThread = null; + return songData; + } + else + { + log.Warn("iTunes no image found"); + CoverThread = null; + return null; + } + } + else + { + log.Warn("iTunes request failed"); + CoverThread = null; + return null; + } + } + catch (Exception e) + { + log.Error($"iTunes Exception {e.Message}"); + CoverThread = null; + return null; + } + } + + /* private static async Task AsyncFetchiTunes(string album, string searchStr) { try { //idk why but sometimes when you search as "Artist - Album Track" and if Album and Track named same it returns random song from album //ex: "Poppy - Negative Spaces negative spaces" Returns "Poppy - New Way Out" as a track link - HttpResponseMessage iTunesReq = await hclient.GetAsync($"https://itunes.apple.com/search?term={searchStr}&limit=1&entity=song"); + HttpResponseMessage iTunesReq = await hclient.GetAsync($"https://itunes.apple.com/search?term={searchStr}&limit=1&entity=song&country={AMRegion}"); if (iTunesReq.IsSuccessStatusCode) { dynamic imageRes = JObject.Parse(await iTunesReq.Content.ReadAsStringAsync()); @@ -60,11 +108,32 @@ private static async Task AsyncFetchiTunes(string album, string } } + */ public static async Task AsyncArtistProfileFetch(string url) { + try + { + HttpResponseMessage AMRequest = await hclient.GetAsync(url); + if (AMRequest.IsSuccessStatusCode) + { + string DOMasAString = await AMRequest.Content.ReadAsStringAsync(); + IHtmlDocument document = parser.ParseDocument(DOMasAString); + if (document.QuerySelectorAll("div.artwork-component > picture > img")[1].GetAttribute("alt") == "") return null; + return document.QuerySelectorAll("div.artwork-component > picture > source")[1].GetAttribute("srcset").Split(',')[1].Split(' ')[0]; + } + else + { + log.Error($"Apple Music Artist request failed returned: {AMRequest.StatusCode}"); + } + } + catch (Exception e) + { + log.Error($"Apple Music Artist Request failed. {e}"); + } return null; } + /* public static async Task AsyncAMFetch(string album, string searchStr) { log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); @@ -98,6 +167,43 @@ public static async Task AsyncAMFetch(string album, string sear return await AsyncFetchiTunes(album, searchStr); } } + */ + + public static async Task AsyncAMFetch(AppleMusicScrapedData data) + { + log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={data.GetSearchString()}"); + try + { + HttpResponseMessage AMRequest = await hclient.GetAsync($"https://music.apple.com/{AMRegion.ToLower()}/search?term={data.GetSearchString()}"); + if (AMRequest.IsSuccessStatusCode) + { + string DOMasAString = await AMRequest.Content.ReadAsStringAsync(); + IHtmlDocument document = parser.ParseDocument(DOMasAString); + + // These can be simplified later but it works rn. ALSO ALSO SOMEHOW 2 CHECKS ISNT ENOUGH FOR SOME CASES BUT IM NOT GONNA COVER ALL CASES ATLEAST NOW + SQLSongResponse songData = new SQLSongResponse( + new SQLCoverData(0, document.DocumentElement.QuerySelectorAll("div.track-lockup__artwork-wrapper > div > picture > source")[1].GetAttribute("srcset").Split(',')[1].Split(' ')[0], null, null, null), + new SQLAlbumData(0, data.AlbumName, (document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].ParentElement.ParentElement.ParentElement.QuerySelectorAll("div.product-lockup__content > div > div > div > span > a")[0].TextContent == data.AlbumName) ? document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].GetAttribute("href") : (document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[1].ParentElement.ParentElement.ParentElement.QuerySelectorAll("div.product-lockup__content > div > div > div > span > a")[0].TextContent == data.AlbumName) ? document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[1].GetAttribute("href") : document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].GetAttribute("href"), data.Type == SecondaryType.Single, 0, 0), + new SQLArtistData(0, data.ArtistName, document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href"), (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href") == document.DocumentElement.QuerySelectorAll("div.artwork")[0].ParentElement.ParentElement.GetAttribute("href")) ? document.DocumentElement.QuerySelectorAll("div.artwork > div > picture > source")[0].GetAttribute("srcset").Split(',')[1].Split(' ')[0].Replace("webp", "jpg") : await AsyncArtistProfileFetch(document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href"))), + new SQLSongData(data.SongName, (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].TextContent == data.SongName) ? document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href") : (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[1].TextContent == data.SongName) ? document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[1].GetAttribute("href") : document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href"), 0, 0) + ); + + InsertNew(songData); + CoverThread = null; + return songData; + } + else + { + log.Error($"Apple Music request failed returned: {AMRequest.StatusCode}"); + return await AsyncFetchiTunes(data); + } + } + catch (Exception e) + { + log.Error($"Apple Music Request failed. {e}"); + return await AsyncFetchiTunes(data); + } + } public static async Task CheckAnimatedCover(string album, string url, CancellationToken ct) { @@ -125,6 +231,8 @@ public static async Task CheckAnimatedCover(string album, string url, Cancellati } } + /* + * old method will deleted public static async Task GetCover(string album, string searchStr) { try @@ -152,11 +260,50 @@ public static async Task GetCover(string album, string searchSt return await AsyncAMFetch(album, searchStr); } } + */ - + public static async Task GetCover(AppleMusicScrapedData data) + { + try + { + SQLRPCResponse cover = GetSongFromDB(data.SongName, data.AlbumName, data.ArtistName); + if (cover != null) + { + CoverThread = null; + return cover; + } + else + { + SQLSongResponse songData = await AsyncAMFetch(data); + if (songData == null) return new SQLRPCResponse(); + return new SQLRPCResponse + { + coverURL = songData.cover.staticCoverURL, + artistRedirURL = songData.artist.artistRedirURL, + artistProfileSource = songData.artist.artistProfileSource, + albumURL = songData.album.albumURL, + songURL = songData.song.songURL + }; + } + } + catch (Exception ex) + { + SQLSongResponse songData = await AsyncAMFetch(data); + if (songData == null) return new SQLRPCResponse(); + return new SQLRPCResponse + { + coverURL = songData.cover.staticCoverURL, + artistRedirURL = songData.artist.artistRedirURL, + artistProfileSource = songData.artist.artistProfileSource, + albumURL = songData.album.albumURL, + songURL = songData.song.songURL + }; + } + } /* I realized we don't need Last.fm API to be here, bc we are making Apple Music RPC aren't we? so i decided to just use iTunes and go on. * might add later for the situation where iTunes api is down. + * probably not gonna add it since prefered source is now Apple Music and we have iTunes as a backup. public static async Task FetchImage(string ArtistAndAlbum, string lastFMAPIKey) { diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index 03ec51a..d34aed3 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -6,16 +6,16 @@ namespace AMDiscordRPC { - internal class Database + public class Database { private static SQLiteConnection sqlite; public static readonly Dictionary sqlMap = new Dictionary() { {"coverTable", "album TEXT PRIMARY KEY NOT NULL, source TEXT, redirURL TEXT DEFAULT 'https://music.apple.com/home', artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistSource TEXT, animated BOOLEAN CHECK (animated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT" }, // will be replaced - {"coverTableNew", "coverID INTEGER PRIMARY KEY AUTOINCREMENT, staticCoverURL TEXT NOT NULL, isAnimated BOOLEAN CHECK (isAnimated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT"}, + {"coverTableNew", "coverID INTEGER PRIMARY KEY AUTOINCREMENT, staticCoverURL TEXT NOT NULL UNIQUE, isAnimated BOOLEAN CHECK (isAnimated IN (0,1)) DEFAULT NULL, streamURL TEXT, animatedURL TEXT"}, {"artistTable", "artistID INTEGER PRIMARY KEY AUTOINCREMENT, artistName TEXT NOT NULL, artistRedirURL TEXT DEFAULT 'https://music.apple.com/home', artistProfileSource TEXT"}, {"albumTable", "albumID INTEGER PRIMARY KEY AUTOINCREMENT, albumName TEXT NOT NULL, albumURL TEXT UNIQUE, isSingle BOOLEAN CHECK (isSingle IN (0,1)), coverID INTEGER, artistID INTEGER, FOREIGN KEY (coverID) REFERENCES coverTableNew(coverID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, - {"songTable", "songTitle TEXT, songURL TEXT, albumID INTEGER, artistID INTEGER, FOREIGN KEY (albumID) REFERENCES albumTable(albumID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, + {"songTable", "songTitle TEXT, songURL TEXT UNIQUE, albumID INTEGER, artistID INTEGER, FOREIGN KEY (albumID) REFERENCES albumTable(albumID), FOREIGN KEY (artistID) REFERENCES artistTable(artistID)"}, {"creds", "S3_accessKey TEXT, S3_secretKey TEXT, S3_serviceURL TEXT, S3_bucketName TEXT, S3_bucketURL TEXT, S3_isSpecificKey BOOLEAN CHECK (S3_isSpecificKey IN (0,1)), FFmpegPath TEXT, LastFMToken TEXT" }, {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, {"clientSettings", "smallImage INTEGER"} @@ -113,6 +113,84 @@ public static void InsertAlbum(SQLCoverResponse data) ExecuteNonQueryCommand($@"INSERT INTO coverTable(album, {string.Join(", ", data.GetNotNullKeys())}) VALUES (@album, {string.Join(", ", data.GetNotNullValues())})", new[] {new SQLiteParameter("@album", data.album)}); } + public static int InsertAlbumNew(SQLAlbumData data) + { + using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT albumID FROM albumTable WHERE albumURL = @albumURL", new[] { new SQLiteParameter("@albumURL", data.albumURL) })) + { + if (!reader.HasRows) + { + return Convert.ToInt32(ExecuteScalarCommand($@"INSERT INTO albumTable(albumName, albumURL, isSingle, coverID, artistID) VALUES (@albumName, @albumURL, @isSingle, @coverID, @artistID); SELECT last_insert_rowid();", new[] { new SQLiteParameter("@albumName", data.albumName), new SQLiteParameter("@albumURL", data.albumURL), new SQLiteParameter("@isSingle", data.isSingle), new SQLiteParameter("@coverID", data.coverID), new SQLiteParameter("@artistID", data.artistID) })); + } + else + { + reader.Read(); + return reader.GetInt32(0); + } + } + } + + public static int InsertArtist(SQLArtistData data) + { + using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT artistID FROM artistTable WHERE artistRedirUrl = @artistRedirURL", new[] { new SQLiteParameter("@artistRedirURL", data.artistRedirURL) })) + { + if (!reader.HasRows) + { + return Convert.ToInt32(ExecuteScalarCommand($@"INSERT INTO artistTable(artistName, artistRedirURL, artistProfileSource) VALUES (@artistName, @artistRedirURL, @artistProfileSource); SELECT last_insert_rowid();", new[] { new SQLiteParameter("@artistName", data.artistName), new SQLiteParameter("@artistRedirURL", data.artistRedirURL), new SQLiteParameter("@artistProfileSource", data.artistProfileSource) })); + } + else + { + reader.Read(); + return reader.GetInt32(0); + } + } + } + + public static void InsertSong(SQLSongData data) + { + if (ExecuteScalarCommand($"SELECT songTitle FROM songTable WHERE songURL = @songURL AND albumID = @albumID AND artistID = @artistID", new[] { new SQLiteParameter("@songURL", data.songURL), new SQLiteParameter("@albumID", data.albumID), new SQLiteParameter("@artistID", data.artistID) }) == null) + ExecuteNonQueryCommand($@"INSERT INTO songTable(songTitle, songURL, albumID, artistID) VALUES (@songTitle, @songURL, @albumID, @artistID)", new[] { new SQLiteParameter("@songTitle", data.songTitle), new SQLiteParameter("@songURL", data.songURL), new SQLiteParameter("@albumID", data.albumID), new SQLiteParameter("@artistID", data.artistID) }); + } + + public static int InsertCover(SQLCoverData data) + { + using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT coverID FROM coverTableNew WHERE staticCoverURL = @staticCoverURL", new[] { new SQLiteParameter("@staticCoverURL", data.staticCoverURL) })) + { + if (!reader.HasRows) + { + return Convert.ToInt32(ExecuteScalarCommand($@"INSERT INTO coverTableNew(staticCoverURL, isAnimated, streamURL, animatedURL) VALUES (@staticCoverURL, @isAnimated, @streamURL, @animatedURL); SELECT last_insert_rowid();", new[] { new SQLiteParameter("@staticCoverURL", data.staticCoverURL), new SQLiteParameter("@isAnimated", data.isAnimated), new SQLiteParameter("@streamURL", data.streamURL), new SQLiteParameter("@animatedURL", data.animatedURL) })); + } + else + { + reader.Read(); + return reader.GetInt32(0); + } + } + } + + public static void InsertNew(SQLSongResponse SQLSongResponse) + { + using (var transaction = sqlite.BeginTransaction()) + { + try + { + int coverID = InsertCover(SQLSongResponse.cover); + int artistID = InsertArtist(SQLSongResponse.artist); + SQLSongResponse.album.coverID = coverID; + SQLSongResponse.album.artistID = artistID; + int albumID = InsertAlbumNew(SQLSongResponse.album); + SQLSongResponse.song.albumID = albumID; + SQLSongResponse.song.artistID = artistID; + InsertSong(SQLSongResponse.song); + transaction.Commit(); + } + catch (Exception ex) + { + log.Error($"An error occured while inserting data: {ex}"); + transaction.Rollback(); + } + } + } + public static SQLCoverResponse GetAlbumDataFromSQL(string album) { using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT * FROM coverTable WHERE album = @album LIMIT 1", new[] { new SQLiteParameter("@album", album) })) @@ -121,11 +199,11 @@ public static SQLCoverResponse GetAlbumDataFromSQL(string album) { return new SQLCoverResponse( reader.GetString(0), - ((!reader.IsDBNull(1)) ? reader.GetString(1) : null), + !reader.IsDBNull(1) ? reader.GetString(1) : null, reader.GetString(2), - ((!reader.IsDBNull(3)) ? reader.GetBoolean(3) : null), - ((!reader.IsDBNull(4)) ? reader.GetString(4) : null), - ((!reader.IsDBNull(5)) ? reader.GetString(5) : null), + !reader.IsDBNull(3) ? reader.GetBoolean(3) : null, + !reader.IsDBNull(4) ? reader.GetString(4) : null, + !reader.IsDBNull(5) ? reader.GetString(5) : null, reader.GetString(6) ); } @@ -133,7 +211,7 @@ public static SQLCoverResponse GetAlbumDataFromSQL(string album) return null; } - public static SQLSongResponse GetSongFromDB(string song, string album, string artist) + public static SQLRPCResponse GetSongFromDB(string song, string album, string artist) { string cmd = @" SELECT @@ -149,13 +227,23 @@ FROM songTable WHERE artistTable.artistName = @artist AND songTable.songTitle = @song - AND albumTable.albumName = @album; + AND albumTable.albumName = @album + LIMIT 1; "; using (SQLiteDataReader reader = ExecuteReaderCommand(cmd, new[] { new SQLiteParameter("@album", album), new SQLiteParameter("@song", song), new SQLiteParameter("@artist", artist) })) { - + if (!reader.HasRows) return null; + while (reader.Read()) + { + return new SQLRPCResponse( + reader.IsDBNull(0) ? null : reader.GetString(0), + reader.IsDBNull(1) ? null : reader.GetString(1), + reader.IsDBNull(2) ? null : reader.GetString(2), + reader.IsDBNull(3) ? null : reader.GetString(3), + reader.IsDBNull(4) ? null : reader.GetString(4) + ); + } } - return null; } @@ -365,6 +453,23 @@ public SQLSongResponse(SQLCoverData cover, SQLAlbumData album, SQLArtistData art this.song = song; } } + public class SQLRPCResponse + { + public string? coverURL { get; set; } + public string? artistRedirURL { get; set; } + public string? artistProfileSource { get; set; } + public string? albumURL { get; set; } + public string? songURL { get; set; } + + public SQLRPCResponse(string? coverURL = null, string? artistRedirURL = null, string? artistProfileSource = null, string? albumURL = null, string? songURL = null) + { + this.coverURL = coverURL; + this.artistRedirURL = artistRedirURL; + this.artistProfileSource = artistProfileSource; + this.albumURL = albumURL; + this.songURL = songURL; + } + } public class SQLCoverData { diff --git a/AMDiscordRPC/Discord.cs b/AMDiscordRPC/Discord.cs index 715c973..86b94f1 100644 --- a/AMDiscordRPC/Discord.cs +++ b/AMDiscordRPC/Discord.cs @@ -1,9 +1,12 @@ using DiscordRPC; +using DiscordRPC.IO; +using DiscordRPC.IO; using System; using System.Threading; using System.Threading.Tasks; using System.Web; using static AMDiscordRPC.Covers; +using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; namespace AMDiscordRPC @@ -12,7 +15,7 @@ public class Discord { private static Thread thread = null; public static CancellationTokenSource animatedCoverCts; - + public static void InitDiscordRPC() { client = new DiscordRpcClient("1308911584164319282"); @@ -51,12 +54,14 @@ public static void SetPresence(SongData x) private static async Task AsyncSetButton(SongData x) { + /* WebSongResponse resp = await GetCover(x.ArtistandAlbumName.Split('—')[1], HttpUtility.UrlEncode(x.ArtistandAlbumName + $" {x.SongName}")); oldData.Buttons = new Button[] { new Button() { Label = "Listen on Apple Music", Url = (resp.trackURL != null) ? resp.trackURL.Replace("https://", "music://") : "music://music.apple.com/home"} }; client.SetPresence(oldData); + */ thread = null; } @@ -67,7 +72,7 @@ public static async Task SetCover(string coverURL) animatedCoverCts = null; } - public static void SetPresence(SongData x, WebSongResponse resp) + public static void SetPresence(SongData x, SQLRPCResponse resp) { log.Debug($"Timestamps {x.StartTime}/{x.EndTime}"); if (thread != null) thread.Abort(); @@ -80,19 +85,22 @@ public static void SetPresence(SongData x, WebSongResponse resp) { Type = ActivityType.Listening, Details = ConvertToValidString(x.SongName), - StateUrl = resp.artistURL, + DetailsUrl = resp.songURL, + StateUrl = resp.artistRedirURL, StatusDisplay = StatusDisplayType.State, State = (x.IsMV) ? x.ArtistandAlbumName : ConvertToValidString(x.ArtistandAlbumName.Split('—')[0]), Assets = new Assets() { - LargeImageKey = (resp.artworkURL != null) ? resp.artworkURL : "", - LargeImageText = (x.IsMV && resp.trackName != null) ? resp.trackName : ConvertToValidString(x.ArtistandAlbumName.Split('—')[1]), - SmallImageKey = (x.format == AudioFormat.Lossless) ? "lossless" : (x.format == AudioFormat.Dolby_Atmos || x.format == AudioFormat.Dolby_Audio) ? "dolbysimplified" : null, - SmallImageText = (x.format == AudioFormat.Lossless) ? "Lossless" : (x.format == AudioFormat.Dolby_Atmos) ? "Dolby Atmos" : (x.format == AudioFormat.Dolby_Audio) ? "Dolby Audio" : null, + LargeImageKey = (resp.coverURL != null) ? resp.coverURL : "", + LargeImageUrl = resp.albumURL, + LargeImageText = (x.IsMV && x.SongName != null) ? x.SongName : ConvertToValidString(x.ArtistandAlbumName.Split('—')[1]), + SmallImageKey = (SelectedSmallImage == SmallImage.Artist) ? resp.artistProfileSource : (x.format == AudioFormat.Lossless) ? "lossless" : (x.format == AudioFormat.Dolby_Atmos || x.format == AudioFormat.Dolby_Audio) ? "dolbysimplified" : null, + SmallImageText = (SelectedSmallImage == SmallImage.Artist) ? ConvertToValidString(x.ArtistandAlbumName.Split('—')[0]) : (x.format == AudioFormat.Lossless) ? "Lossless" : (x.format == AudioFormat.Dolby_Atmos) ? "Dolby Atmos" : (x.format == AudioFormat.Dolby_Audio) ? "Dolby Audio" : null, + SmallImageUrl = (SelectedSmallImage == SmallImage.Artist) ? resp.artistRedirURL : null }, Buttons = new Button[] { - new Button() { Label = "Listen on Apple Music", Url = (resp.trackURL != null) ? resp.trackURL.Replace("https://", "music://") : "music://music.apple.com/home"} + new Button() { Label = "Listen on Apple Music", Url = (resp.songURL != null) ? resp.songURL.Replace("https://", "music://") : "music://music.apple.com/home"} }, Timestamps = new Timestamps() { @@ -103,10 +111,10 @@ public static void SetPresence(SongData x, WebSongResponse resp) if (oldData.Assets.LargeImageText.Length == 1) oldData.Assets.LargeImageText = $"{oldData.Assets.LargeImageText}‍"; // THIS HAS U+200D AT THE END OF STRING TO FIX '"large_text" length must be at least 2 characters long' ERROR client.SetPresence(oldData); - if (resp.artworkURL != null && !resp.artworkURL.Contains((S3_Credentials != null) ? (S3_Credentials.GetNullKeys().Count == 0) ? S3_Credentials.bucketURL : "" : "")) + if (resp.coverURL != null && !resp.coverURL.Contains((S3_Credentials != null) ? (S3_Credentials.GetNullKeys().Count == 0) ? S3_Credentials.bucketURL : "" : "")) { animatedCoverCts = new CancellationTokenSource(); - Task t = new Task(() => CheckAnimatedCover(ConvertToValidString(x.ArtistandAlbumName.Split('—')[1]), resp.trackURL, animatedCoverCts.Token)); + Task t = new Task(() => CheckAnimatedCover(ConvertToValidString(x.ArtistandAlbumName.Split('—')[1]), resp.songURL, animatedCoverCts.Token)); t.Start(); } } diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index ee644c5..7b76a44 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -8,7 +8,10 @@ using log4net.Filter; using log4net.Layout; using log4net.Repository.Hierarchy; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using System; +using System.Collections; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; @@ -16,8 +19,10 @@ using System.Net; using System.Net.Http; using System.Reflection; +using System.Runtime.Serialization; using System.Text; using System.Text.RegularExpressions; +using System.Web; using static AMDiscordRPC.Database; using static AMDiscordRPC.UI; @@ -35,13 +40,18 @@ public static class Globals public static readonly Assembly assembly = Assembly.GetExecutingAssembly(); public static HtmlParser parser = new HtmlParser(); public static RichPresence oldData = new RichPresence(); - public static WebSongResponse httpRes = new WebSongResponse(); + public static SQLRPCResponse httpRes = new SQLRPCResponse(); public static string ffmpegPath; public static S3_Creds S3_Credentials; private static List newMatchesArr; public static S3ConnectionStatus S3Status = S3ConnectionStatus.Disconnected; public static string AMRegion; public static SmallImage SelectedSmallImage = SmallImage.LossDolby; + public static CloudflareTypes.AccountCredentials CfAccountCredentials = new CloudflareTypes.AccountCredentials( + "", + "", + "" + ); public static void ConfigureLogger() { @@ -96,7 +106,7 @@ public static void InitRegion() catch (Exception e) { log.Error($"Error happened while trying to select region, falling back to US Apple Music. Cause: {e}"); - AMRegion = "US"; + AMRegion = "us"; } } @@ -216,6 +226,14 @@ public enum SmallImage None } + public enum SecondaryType + { + EP, + Album, + Single, + MV + } + public enum GWLP { EXSTYLE = -20, HINSTANCE = -6, @@ -324,7 +342,9 @@ public class SongData : EventArgs public DateTime EndTime { get; set; } public AudioFormat format { get; set; } - public SongData(string SongName, string ArtistandAlbumName, bool IsMV, DateTime StartTime, DateTime EndTime, AudioFormat format) + public bool isSingle { get; set; } + + public SongData(string SongName, string ArtistandAlbumName, bool IsMV, DateTime StartTime, DateTime EndTime, AudioFormat format, bool isSingle) { this.SongName = SongName; this.ArtistandAlbumName = ArtistandAlbumName; @@ -332,6 +352,7 @@ public SongData(string SongName, string ArtistandAlbumName, bool IsMV, DateTime this.StartTime = StartTime; this.EndTime = EndTime; this.format = format; + this.isSingle = isSingle; } } @@ -375,6 +396,28 @@ public List GetNotNullValues() } } + public class AppleMusicScrapedData + { + public string ArtistName { get; set; } + public string SongName { get; set; } + public string AlbumName { get; set; } + + public SecondaryType? Type { get; set; } + + public AppleMusicScrapedData(string ArtistName = null, string SongName = null, string AlbumName = null, SecondaryType? Type = null) + { + this.ArtistName = ArtistName; + this.SongName = SongName; + this.AlbumName = AlbumName; + this.Type = Type; + } + + public string GetSearchString() + { + return Uri.EscapeDataString($"{SongName} {ArtistName} — {AlbumName} - {Type.ToString()}"); + } + } + public class WebSongResponse { public string artworkURL { get; set; } @@ -399,6 +442,125 @@ public override bool Equals(object obj) artistURL == other.artistURL; } } + + [JsonConverter(typeof(StringEnumConverter))] + public class CloudflareTypes + { + public static readonly string endpointV4 = "https://api.cloudflare.com/client/v4"; + + public enum Jurisdiction + { + [EnumMember(Value = "default")] + Default = 0, + + [EnumMember(Value = "eu")] + EU = 1, + + [EnumMember(Value = "fedramp")] + FedRAMP = 2 + } + + public enum Location + { + [EnumMember(Value = "apac")] + APAC, + + [EnumMember(Value = "eeur")] + EEUR, + + [EnumMember(Value = "enam")] + ENAM, + + [EnumMember(Value = "weur")] + WEUR, + + [EnumMember(Value = "wnam")] + WNAM, + + [EnumMember(Value = "oc")] + OC + } + + public enum StorageClass + { + [EnumMember(Value = "standard")] + Standard, + + [EnumMember(Value = "infrequent_access")] + InfrequentAccess + } + + public class ResponseInfo + { + public int code { get; set; } + public string message { get; set; } + public string documentation_url { get; set; } + public Source source { get; set; } + + public ResponseInfo(int code, string message, string documentation_url, Source source) + { + this.code = code; + this.message = message; + this.documentation_url = documentation_url; + this.source = source; + } + } + + public class Response + { + public List errors { get; set; } + public dynamic messages { get; set; } + public dynamic result { get; set; } + public bool success { get; set; } + + public Response(List errors, dynamic messages, dynamic result, bool success) + { + this.errors = errors; + this.messages = messages; + this.result = result; + this.success = success; + } + } + + public class Source + { + public string pointer { get; set; } + } + + public class Bucket + { + public DateTime creation_date { get; set; } + public Jurisdiction jurisdiction { get; set; } + public Location location { get; set; } + public string name { get; set; } + public StorageClass storage_class { get; set; } + + [JsonConstructor] + public Bucket(DateTime creation_date, Jurisdiction jurisdiction, Location location, string name, StorageClass storage_class) + { + this.creation_date = creation_date; + this.jurisdiction = jurisdiction; + this.location = location; + this.name = name; + this.storage_class = storage_class; + } + } + + public class AccountCredentials + { + public string api_token { get; set; } + public string zone_id { get; set; } + public string account_id { get; set; } + + public AccountCredentials(string api_token, string zone_id, string account_id) + { + this.api_token = api_token; + this.zone_id = zone_id; + this.account_id = account_id; + } + } + } + public static String TrimEnd(this String str, int count) { return str.Substring(0, str.Length - count); diff --git a/AMDiscordRPC/Properties/AssemblyInfo.cs b/AMDiscordRPC/Properties/AssemblyInfo.cs index 6cf49e4..e651e22 100644 --- a/AMDiscordRPC/Properties/AssemblyInfo.cs +++ b/AMDiscordRPC/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("CrawLeyYou")] [assembly: AssemblyProduct("AMDiscordRPC")] -[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/AMDiscordRPC/Properties/Resources.Designer.cs b/AMDiscordRPC/Properties/Resources.Designer.cs index 91b7f95..a7ca0b1 100644 --- a/AMDiscordRPC/Properties/Resources.Designer.cs +++ b/AMDiscordRPC/Properties/Resources.Designer.cs @@ -19,10 +19,10 @@ namespace AMDiscordRPC.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AMDiscordRPC.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal Resources() { /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap Logo_Black { + public static System.Drawing.Bitmap Logo_Black { get { object obj = ResourceManager.GetObject("Logo_Black", resourceCulture); return ((System.Drawing.Bitmap)(obj)); @@ -73,7 +73,7 @@ internal static System.Drawing.Bitmap Logo_Black { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Logo_Black_32 { + public static System.Drawing.Icon Logo_Black_32 { get { object obj = ResourceManager.GetObject("Logo_Black_32", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -83,7 +83,7 @@ internal static System.Drawing.Icon Logo_Black_32 { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Logo_Black1 { + public static System.Drawing.Icon Logo_Black1 { get { object obj = ResourceManager.GetObject("Logo_Black1", resourceCulture); return ((System.Drawing.Icon)(obj)); diff --git a/AMDiscordRPC/packages.config b/AMDiscordRPC/packages.config index 79da35e..e8d4ce6 100644 --- a/AMDiscordRPC/packages.config +++ b/AMDiscordRPC/packages.config @@ -3,7 +3,7 @@ - + From b649f76063f28ca0dcfcf366633b0d3a71308091 Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:10:25 +0300 Subject: [PATCH 7/8] magic (animated covers are now stored in new structure) --- AMDiscordRPC/Covers.cs | 128 +++------------------------------------ AMDiscordRPC/Database.cs | 82 +++++++++++++++++++------ AMDiscordRPC/Discord.cs | 2 +- AMDiscordRPC/Playlist.cs | 6 +- 4 files changed, 74 insertions(+), 144 deletions(-) diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index e019ea2..fea5b0e 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -61,54 +61,7 @@ private static async Task AsyncFetchiTunes(AppleMusicScrapedDat return null; } } - - /* - private static async Task AsyncFetchiTunes(string album, string searchStr) - { - try - { - //idk why but sometimes when you search as "Artist - Album Track" and if Album and Track named same it returns random song from album - //ex: "Poppy - Negative Spaces negative spaces" Returns "Poppy - New Way Out" as a track link - HttpResponseMessage iTunesReq = await hclient.GetAsync($"https://itunes.apple.com/search?term={searchStr}&limit=1&entity=song&country={AMRegion}"); - if (iTunesReq.IsSuccessStatusCode) - { - dynamic imageRes = JObject.Parse(await iTunesReq.Content.ReadAsStringAsync()); - if (imageRes["resultCount"] != 0) - { - WebSongResponse webRes = new WebSongResponse - ( - imageRes["results"][0]["artworkUrl100"].ToString(), - imageRes["results"][0]["trackViewUrl"].ToString(), - imageRes["results"][0]["collectionName"].ToString(), - imageRes["results"][0]["artistViewUrl"].ToString() - ); - InsertAlbum(new SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); - CoverThread = null; - return webRes; - } - else - { - log.Warn("iTunes no image found"); - CoverThread = null; - return new WebSongResponse(); - } - } - else - { - log.Warn("iTunes request failed"); - CoverThread = null; - return new WebSongResponse(); - } - } - catch (Exception e) - { - log.Error($"iTunes Exception {e.Message}"); - CoverThread = null; - return new WebSongResponse(); - } - } - - */ + public static async Task AsyncArtistProfileFetch(string url) { try @@ -132,43 +85,7 @@ public static async Task AsyncArtistProfileFetch(string url) } return null; } - - /* - public static async Task AsyncAMFetch(string album, string searchStr) - { - log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); - try - { - HttpResponseMessage AMRequest = await hclient.GetAsync($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); - if (AMRequest.IsSuccessStatusCode) - { - string DOMasAString = await AMRequest.Content.ReadAsStringAsync(); - IHtmlDocument document = parser.ParseDocument(DOMasAString); - - WebSongResponse webRes = new WebSongResponse( - document.DocumentElement.QuerySelectorAll("div.track-lockup__artwork-wrapper > div > picture > source")[1].GetAttribute("srcset").Split(',')[1].Split(' ')[0], - document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href"), - null, - document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href") - ); - CoverThread = null; - InsertAlbum(new Database.SQLCoverResponse(album, webRes.artworkURL, webRes.trackURL, null, null, null, webRes.artistURL)); - return webRes; - } - else - { - log.Error($"Apple Music request failed returned: {AMRequest.StatusCode}"); - return await AsyncFetchiTunes(album, searchStr); - } - } - catch (Exception e) - { - log.Error($"Apple Music Request failed. {e}"); - return await AsyncFetchiTunes(album, searchStr); - } - } - */ - + public static async Task AsyncAMFetch(AppleMusicScrapedData data) { log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={data.GetSearchString()}"); @@ -205,17 +122,17 @@ public static async Task AsyncAMFetch(AppleMusicScrapedData dat } } - public static async Task CheckAnimatedCover(string album, string url, CancellationToken ct) + public static async Task CheckAnimatedCover(string albumUrl, CancellationToken ct) { try { - var appleMusicDom = await hclient.GetAsync(url); - log.Debug($"Animated Cover Request: {url}"); + var appleMusicDom = await hclient.GetAsync(albumUrl); + log.Debug($"Animated Cover Request: {albumUrl}"); if (appleMusicDom.IsSuccessStatusCode) { string DOMasAString = await appleMusicDom.Content.ReadAsStringAsync(); IHtmlDocument document = parser.ParseDocument(DOMasAString); - ConvertM3U8(album, document.DocumentElement.QuerySelector("div.video-artwork__container").InnerHtml.Split(new string[] { "src=\"" }, StringSplitOptions.None)[1].Split('"')[0], ct); + ConvertM3U8(albumUrl, document.DocumentElement.QuerySelector("div.video-artwork__container").InnerHtml.Split(new string[] { "src=\"" }, StringSplitOptions.None)[1].Split('"')[0], ct); } else { @@ -227,40 +144,9 @@ public static async Task CheckAnimatedCover(string album, string url, Cancellati { log.Error($"Apple Music animatedCover exception: {e.Message}"); Discord.animatedCoverCts = null; - Database.UpdateAlbum(new Database.SQLCoverResponse(album, null, null, false)); - } - } - - /* - * old method will deleted - public static async Task GetCover(string album, string searchStr) - { - try - { - log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={searchStr}"); - SQLCoverResponse cover = GetAlbumDataFromSQL(album); - if (cover != null) - { - WebSongResponse res = new WebSongResponse - ( - (cover.animated == true && cover.animatedURL != null) ? cover.animatedURL : (cover.source != null) ? cover.source : throw new Exception("Source not found."), - cover.redirURL, - album - ); - CoverThread = null; - return res; - } - else - { - return await AsyncAMFetch(album, searchStr); - } - } - catch (Exception ex) - { - return await AsyncAMFetch(album, searchStr); + UpdateAlbumCover(albumUrl, new SQLCoverData(0, null, false, null, null)); } } - */ public static async Task GetCover(AppleMusicScrapedData data) { diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index d34aed3..4cef8f4 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -20,7 +20,7 @@ public class Database {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, {"clientSettings", "smallImage INTEGER"} }; - + private static void InitDatabase() { try @@ -56,13 +56,44 @@ public static void CheckDatabaseIntegrity() private static void CreateDatabase() { - foreach (var item in sqlMap) + using (var transaction = sqlite.BeginTransaction()) { - ExecuteNonQueryCommand($"CREATE TABLE IF NOT EXISTS {item.Key}({item.Value})"); + try + { + foreach (var item in sqlMap) + { + ExecuteNonQueryCommand($"CREATE TABLE IF NOT EXISTS {item.Key}({item.Value})"); + } + transaction.Commit(); + } + catch (Exception ex) + { + log.Error($"An error occured while creating database: {ex}"); + transaction.Rollback(); + } } + CreateIndexes(); } - // Note: source, streamurl, animated, animatedUrl can be stored in external table so we decrease the file size of database + private static void CreateIndexes() + { + using (var transaction = sqlite.BeginTransaction()) + { + try + { + ExecuteNonQueryCommand($"CREATE INDEX IF NOT EXISTS idx_albumName ON albumTable(albumName)"); + ExecuteNonQueryCommand($"CREATE INDEX IF NOT EXISTS idx_songTitle ON songTable(songTitle)"); + ExecuteNonQueryCommand($"CREATE INDEX IF NOT EXISTS idx_artistName ON artistTable(artistName)"); + transaction.Commit(); + } + catch (Exception ex) + { + log.Error($"An error occured while creating indexes: {ex}"); + transaction.Rollback(); + } + } + } + private static void CheckForeignKeys() { ExecuteNonQueryCommand("PRAGMA foreign_keys = on"); @@ -98,21 +129,20 @@ private static void CheckTables() { ExecuteNonQueryCommand($"CREATE TABLE IF NOT EXISTS {item}({sqlMap[item]})"); } + CreateIndexes(); } else log.Debug("No missing table found."); } - public static void UpdateAlbum(SQLCoverResponse data) + public static void UpdateAlbumCover(string albumURL, SQLCoverData data) { - ExecuteNonQueryCommand($"UPDATE coverTable SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) WHERE album = @album", new[] { new SQLiteParameter("@album", data.album)}); - } - - public static void InsertAlbum(SQLCoverResponse data) - { - if (ExecuteScalarCommand($"SELECT album from coverTable WHERE album = @album", new[] { new SQLiteParameter("@album", data.album) }) == null) - ExecuteNonQueryCommand($@"INSERT INTO coverTable(album, {string.Join(", ", data.GetNotNullKeys())}) VALUES (@album, {string.Join(", ", data.GetNotNullValues())})", new[] {new SQLiteParameter("@album", data.album)}); + int rowsAffected = ExecuteNonQueryCommand($@"UPDATE coverTableNew SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) FROM albumTable WHERE coverTableNew.coverID = albumTable.coverID AND albumURL = @albumURL;", new[] { new SQLiteParameter("@albumURL", albumURL)}); + if (rowsAffected == 0) + { + log.Warn($"This album is not present in database. Skipping {albumURL}"); + } } - + public static int InsertAlbumNew(SQLAlbumData data) { using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT albumID FROM albumTable WHERE albumURL = @albumURL", new[] { new SQLiteParameter("@albumURL", data.albumURL) })) @@ -435,7 +465,6 @@ public List GetNotNullValues() { return GetType().GetProperties().Where(s => s.GetValue(this) != null && s.GetValue(this) != this.album).Select(p => (p.PropertyType == typeof(string)) ? $"'{p.GetValue(this)}'" : p.GetValue(this)).ToList(); } - } public class SQLSongResponse @@ -473,11 +502,11 @@ public SQLRPCResponse(string? coverURL = null, string? artistRedirURL = null, st public class SQLCoverData { - public int id; - public string staticCoverURL; - public bool? isAnimated; - public string? streamURL; - public string? animatedURL; + public int id { get; set; } + public string staticCoverURL { get; set; } + public bool? isAnimated { get; set; } + public string? streamURL { get; set; } + public string? animatedURL { get; set; } public SQLCoverData(int id, string staticCoverURL, bool? isAnimated, string? streamURL, string? animatedURL) { @@ -487,6 +516,21 @@ public SQLCoverData(int id, string staticCoverURL, bool? isAnimated, string? str this.streamURL = streamURL; this.animatedURL = animatedURL; } + public List GetNotNullKeys() + { + return GetType().GetProperties() + .Where(s => s.GetValue(this) != null && !s.GetValue(this).Equals(id) && s.GetValue(this) != staticCoverURL) + .Select(p => p.Name) + .ToList(); + } + + public List GetNotNullValues() + { + return GetType().GetProperties() + .Where(s => s.GetValue(this) != null && !s.GetValue(this).Equals(id) && s.GetValue(this) != staticCoverURL) + .Select(p => (p.PropertyType == typeof(string)) ? $"'{p.GetValue(this)}'" : p.GetValue(this)) + .ToList(); + } } public class SQLArtistData diff --git a/AMDiscordRPC/Discord.cs b/AMDiscordRPC/Discord.cs index 86b94f1..e2369ad 100644 --- a/AMDiscordRPC/Discord.cs +++ b/AMDiscordRPC/Discord.cs @@ -114,7 +114,7 @@ public static void SetPresence(SongData x, SQLRPCResponse resp) if (resp.coverURL != null && !resp.coverURL.Contains((S3_Credentials != null) ? (S3_Credentials.GetNullKeys().Count == 0) ? S3_Credentials.bucketURL : "" : "")) { animatedCoverCts = new CancellationTokenSource(); - Task t = new Task(() => CheckAnimatedCover(ConvertToValidString(x.ArtistandAlbumName.Split('—')[1]), resp.songURL, animatedCoverCts.Token)); + Task t = new Task(() => CheckAnimatedCover(resp.albumURL, animatedCoverCts.Token)); t.Start(); } } diff --git a/AMDiscordRPC/Playlist.cs b/AMDiscordRPC/Playlist.cs index e3520bd..7411cd2 100644 --- a/AMDiscordRPC/Playlist.cs +++ b/AMDiscordRPC/Playlist.cs @@ -18,10 +18,10 @@ namespace AMDiscordRPC { internal class Playlist { - public static async Task ConvertM3U8(string album, string playlistUrl, CancellationToken ct) + public static async Task ConvertM3U8(string albumURL, string playlistUrl, CancellationToken ct) { // ^I thought storing Master Playlist would be better for in case of bucket changes and Apple's codec changes on lowest quality. - Database.UpdateAlbum(new Database.SQLCoverResponse(album, null, null, true, playlistUrl)); + Database.UpdateAlbumCover(albumURL, new Database.SQLCoverData(0, null, true, playlistUrl, null)); StreamInf playlist = await FetchResolution(playlistUrl); if (!ct.IsCancellationRequested && playlist != null) { @@ -48,7 +48,7 @@ public static async Task ConvertM3U8(string album, string playlistUrl, Cancellat if (S3Status == S3ConnectionStatus.Connected) servedPath = await PutGIF(gifPath, fileName.Replace(".mp4", ".gif")); else throw new Exception("S3 is not properly configured."); log.Debug("Put S3 Bucket"); - Database.UpdateAlbum(new Database.SQLCoverResponse(album, null, null, null, null, servedPath)); + Database.UpdateAlbumCover(albumURL, new Database.SQLCoverData(0, null, null, null, servedPath)); if (ct.IsCancellationRequested) throw new Exception("Cancelled"); SetCover(servedPath); log.Debug("Set Animated Cover"); From 2b4c984ea4258bf7ab58d413f267a0b774e784ad Mon Sep 17 00:00:00 2001 From: CrawLeyYou <60201017+CrawLeyYou@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:05:06 +0300 Subject: [PATCH 8/8] works WAY better than expected --- AMDiscordRPC/AMDiscordRPC.cs | 31 ++++---- AMDiscordRPC/Cloudflare.cs | 9 +-- AMDiscordRPC/Covers.cs | 24 +++--- AMDiscordRPC/Database.cs | 71 +++-------------- AMDiscordRPC/Discord.cs | 5 +- AMDiscordRPC/Globals.cs | 77 ++++++++++++++++++- AMDiscordRPC/UI.cs | 16 ++-- .../UIComponents/OptionsWindow.xaml.cs | 6 +- 8 files changed, 119 insertions(+), 120 deletions(-) diff --git a/AMDiscordRPC/AMDiscordRPC.cs b/AMDiscordRPC/AMDiscordRPC.cs index 05d99f9..145ca3f 100644 --- a/AMDiscordRPC/AMDiscordRPC.cs +++ b/AMDiscordRPC/AMDiscordRPC.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using static AMDiscordRPC.AppleMusic; -using static AMDiscordRPC.Cloudflare; using static AMDiscordRPC.Covers; using static AMDiscordRPC.Database; using static AMDiscordRPC.Discord; @@ -22,22 +21,22 @@ internal class AMDiscordRPC static void Main(string[] args) { ConfigureLogger(); - // SetToken(); + // SetToken(); InitRegion(); CreateUI(); InitDiscordRPC(); - AttachToAM(); + AttachToAM(); // ListBuckets(); AMSongDataEvent.SongChanged += async (sender, x) => { - log.Info($"Song: {x.SongName} \\ Artist and Album: {x.ArtistandAlbumName}"); - AMDiscordRPCTray.ChangeSongState($"{x.ArtistandAlbumName.Split('—')[0]} - {x.SongName}"); - if (x.ArtistandAlbumName == oldAlbumnArtist && oldData.Assets.LargeImageKey != null) - { - SetPresence(x); - } - else - { + log.Info($"Song: {x.SongName} \\ Artist and Album: {x.ArtistandAlbumName}"); + AMDiscordRPCTray.ChangeSongState($"{x.ArtistandAlbumName.Split('—')[0]} - {x.SongName}"); + if (x.ArtistandAlbumName == oldAlbumnArtist && oldData.Assets.LargeImageKey != null) + { + SetPresence(x); + } + else + { if (httpRes.Equals(new SQLRPCResponse()) || CoverThread != null) { httpRes = await GetCover( @@ -47,11 +46,11 @@ static void Main(string[] args) x.ArtistandAlbumName.Split(new string[] { " — " }, StringSplitOptions.None)[1], (x.isSingle) ? SecondaryType.Single : (x.IsMV) ? SecondaryType.MV : (x.ArtistandAlbumName.Contains(" - EP") ? SecondaryType.EP : SecondaryType.Album) )); - log.Debug($"Set Cover: {((httpRes.coverURL != null) ? httpRes.coverURL : null)}"); - } - SetPresence(x, httpRes); - oldAlbumnArtist = x.ArtistandAlbumName; - } + log.Debug($"Set Cover: {((httpRes.coverURL != null) ? httpRes.coverURL : null)}"); + } + SetPresence(x, httpRes); + oldAlbumnArtist = x.ArtistandAlbumName; + } }; CheckDatabaseIntegrity(); ConfigureFromDB(); diff --git a/AMDiscordRPC/Cloudflare.cs b/AMDiscordRPC/Cloudflare.cs index 7d480dc..5f4098c 100644 --- a/AMDiscordRPC/Cloudflare.cs +++ b/AMDiscordRPC/Cloudflare.cs @@ -1,11 +1,6 @@ -using Amazon.Runtime.Endpoints; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; +using Newtonsoft.Json; using System.Collections.Generic; -using System.Linq; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using static AMDiscordRPC.Globals; @@ -27,7 +22,7 @@ private static async Task VerifyToken() Response deserialized = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); if (deserialized.result.status == "active") log.Info("Token valid and active"); - else + else log.Error("Token is not valid."); } diff --git a/AMDiscordRPC/Covers.cs b/AMDiscordRPC/Covers.cs index fea5b0e..a29be24 100644 --- a/AMDiscordRPC/Covers.cs +++ b/AMDiscordRPC/Covers.cs @@ -1,13 +1,10 @@ -using Amazon.Runtime.Documents; -using AngleSharp.Dom; -using AngleSharp.Html.Dom; +using AngleSharp.Html.Dom; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using System.Windows.Markup; -using System.Xml.Linq; +using AngleSharp.Dom; using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; using static AMDiscordRPC.Playlist; @@ -61,7 +58,7 @@ private static async Task AsyncFetchiTunes(AppleMusicScrapedDat return null; } } - + public static async Task AsyncArtistProfileFetch(string url) { try @@ -85,7 +82,7 @@ public static async Task AsyncArtistProfileFetch(string url) } return null; } - + public static async Task AsyncAMFetch(AppleMusicScrapedData data) { log.Debug($"https://music.apple.com/{AMRegion.ToLower()}/search?term={data.GetSearchString()}"); @@ -95,14 +92,13 @@ public static async Task AsyncAMFetch(AppleMusicScrapedData dat if (AMRequest.IsSuccessStatusCode) { string DOMasAString = await AMRequest.Content.ReadAsStringAsync(); - IHtmlDocument document = parser.ParseDocument(DOMasAString); - - // These can be simplified later but it works rn. ALSO ALSO SOMEHOW 2 CHECKS ISNT ENOUGH FOR SOME CASES BUT IM NOT GONNA COVER ALL CASES ATLEAST NOW + IElement document = parser.ParseDocument(DOMasAString).DocumentElement; + string[] artistData = document.GetArtist(data); SQLSongResponse songData = new SQLSongResponse( - new SQLCoverData(0, document.DocumentElement.QuerySelectorAll("div.track-lockup__artwork-wrapper > div > picture > source")[1].GetAttribute("srcset").Split(',')[1].Split(' ')[0], null, null, null), - new SQLAlbumData(0, data.AlbumName, (document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].ParentElement.ParentElement.ParentElement.QuerySelectorAll("div.product-lockup__content > div > div > div > span > a")[0].TextContent == data.AlbumName) ? document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].GetAttribute("href") : (document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[1].ParentElement.ParentElement.ParentElement.QuerySelectorAll("div.product-lockup__content > div > div > div > span > a")[0].TextContent == data.AlbumName) ? document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[1].GetAttribute("href") : document.DocumentElement.QuerySelectorAll("a.product-lockup__link")[0].GetAttribute("href"), data.Type == SecondaryType.Single, 0, 0), - new SQLArtistData(0, data.ArtistName, document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href"), (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href") == document.DocumentElement.QuerySelectorAll("div.artwork")[0].ParentElement.ParentElement.GetAttribute("href")) ? document.DocumentElement.QuerySelectorAll("div.artwork > div > picture > source")[0].GetAttribute("srcset").Split(',')[1].Split(' ')[0].Replace("webp", "jpg") : await AsyncArtistProfileFetch(document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > span > a")[0].GetAttribute("href"))), - new SQLSongData(data.SongName, (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].TextContent == data.SongName) ? document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href") : (document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[1].TextContent == data.SongName) ? document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[1].GetAttribute("href") : document.DocumentElement.QuerySelectorAll("div.track-lockup__clamp-wrapper > a")[0].GetAttribute("href"), 0, 0) + new SQLCoverData(0, document.GetCover(data), null, null, null), + new SQLAlbumData(0, data.AlbumName, document.GetAlbum(data), data.Type == SecondaryType.Single, 0, 0), + new SQLArtistData(0, data.ArtistName, artistData[0], artistData[1]), + new SQLSongData(data.SongName, document.GetSong(data), 0, 0) ); InsertNew(songData); diff --git a/AMDiscordRPC/Database.cs b/AMDiscordRPC/Database.cs index 4cef8f4..5b91868 100644 --- a/AMDiscordRPC/Database.cs +++ b/AMDiscordRPC/Database.cs @@ -20,7 +20,7 @@ public class Database {"logs", "timestamp INTEGER, type TEXT, occuredAt TEXT, message TEXT" }, {"clientSettings", "smallImage INTEGER"} }; - + private static void InitDatabase() { try @@ -93,7 +93,7 @@ private static void CreateIndexes() } } } - + private static void CheckForeignKeys() { ExecuteNonQueryCommand("PRAGMA foreign_keys = on"); @@ -136,13 +136,13 @@ private static void CheckTables() public static void UpdateAlbumCover(string albumURL, SQLCoverData data) { - int rowsAffected = ExecuteNonQueryCommand($@"UPDATE coverTableNew SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) FROM albumTable WHERE coverTableNew.coverID = albumTable.coverID AND albumURL = @albumURL;", new[] { new SQLiteParameter("@albumURL", albumURL)}); - if (rowsAffected == 0) - { - log.Warn($"This album is not present in database. Skipping {albumURL}"); - } + int rowsAffected = ExecuteNonQueryCommand($@"UPDATE coverTableNew SET ({string.Join(", ", data.GetNotNullKeys())}) = ({string.Join(", ", data.GetNotNullValues())}) FROM albumTable WHERE coverTableNew.coverID = albumTable.coverID AND albumURL = @albumURL;", new[] { new SQLiteParameter("@albumURL", albumURL) }); + if (rowsAffected == 0) + { + log.Warn($"This album is not present in database. Skipping {albumURL}"); + } } - + public static int InsertAlbumNew(SQLAlbumData data) { using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT albumID FROM albumTable WHERE albumURL = @albumURL", new[] { new SQLiteParameter("@albumURL", data.albumURL) })) @@ -221,26 +221,6 @@ public static void InsertNew(SQLSongResponse SQLSongResponse) } } - public static SQLCoverResponse GetAlbumDataFromSQL(string album) - { - using (SQLiteDataReader reader = ExecuteReaderCommand($"SELECT * FROM coverTable WHERE album = @album LIMIT 1", new[] { new SQLiteParameter("@album", album) })) - { - while (reader.Read()) - { - return new SQLCoverResponse( - reader.GetString(0), - !reader.IsDBNull(1) ? reader.GetString(1) : null, - reader.GetString(2), - !reader.IsDBNull(3) ? reader.GetBoolean(3) : null, - !reader.IsDBNull(4) ? reader.GetString(4) : null, - !reader.IsDBNull(5) ? reader.GetString(5) : null, - reader.GetString(6) - ); - } - } - return null; - } - public static SQLRPCResponse GetSongFromDB(string song, string album, string artist) { string cmd = @" @@ -287,7 +267,7 @@ private static void CheckColumns() while (data.Read()) { if (data.GetString(0) == "table" && data.GetString(2) == table) - { + { string sqlStr = string.Join("(", data.GetString(4).Split(new[] { "CREATE TABLE " }, StringSplitOptions.None)[1].Split('(').Skip(1)).TrimEnd(1); var temp = ConvertSQLStringToColumnInfo(sqlStr); foreach (var keyValuePair in temp) @@ -434,39 +414,6 @@ public ForeignKey(string key, string refTable, string refColumn) } } - public class SQLCoverResponse - { - public string album { get; set; } - public string source { get; set; } - public string redirURL { get; set; } - public bool? animated { get; set; } - public string streamURL { get; set; } - public string animatedURL { get; set; } - public string artistRedirURL { get; set; } - - - public SQLCoverResponse(string album = null, string source = null, string redirURL = null, bool? animated = null, string streamURL = null, string animatedURL = null, string artistRedirURL = null) - { - this.album = album; - this.source = source; - this.redirURL = redirURL; - this.animated = animated; - this.streamURL = streamURL; - this.animatedURL = animatedURL; - this.artistRedirURL = artistRedirURL; - } - - public List GetNotNullKeys() - { - return GetType().GetProperties().Where(s => s.GetValue(this) != null && s.GetValue(this) != this.album).Select(p => p.Name).ToList(); - } - - public List GetNotNullValues() - { - return GetType().GetProperties().Where(s => s.GetValue(this) != null && s.GetValue(this) != this.album).Select(p => (p.PropertyType == typeof(string)) ? $"'{p.GetValue(this)}'" : p.GetValue(this)).ToList(); - } - } - public class SQLSongResponse { public SQLCoverData? cover; diff --git a/AMDiscordRPC/Discord.cs b/AMDiscordRPC/Discord.cs index e2369ad..c68bf54 100644 --- a/AMDiscordRPC/Discord.cs +++ b/AMDiscordRPC/Discord.cs @@ -1,10 +1,7 @@ using DiscordRPC; -using DiscordRPC.IO; -using DiscordRPC.IO; using System; using System.Threading; using System.Threading.Tasks; -using System.Web; using static AMDiscordRPC.Covers; using static AMDiscordRPC.Database; using static AMDiscordRPC.Globals; @@ -15,7 +12,7 @@ public class Discord { private static Thread thread = null; public static CancellationTokenSource animatedCoverCts; - + public static void InitDiscordRPC() { client = new DiscordRpcClient("1308911584164319282"); diff --git a/AMDiscordRPC/Globals.cs b/AMDiscordRPC/Globals.cs index 7b76a44..01c26c2 100644 --- a/AMDiscordRPC/Globals.cs +++ b/AMDiscordRPC/Globals.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; -using System.Collections; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; @@ -22,7 +21,7 @@ using System.Runtime.Serialization; using System.Text; using System.Text.RegularExpressions; -using System.Web; +using AngleSharp.Dom; using static AMDiscordRPC.Database; using static AMDiscordRPC.UI; @@ -234,7 +233,8 @@ public enum SecondaryType MV } - public enum GWLP { + public enum GWLP + { EXSTYLE = -20, HINSTANCE = -6, HWNDPARENT = -8, @@ -417,7 +417,7 @@ public string GetSearchString() return Uri.EscapeDataString($"{SongName} {ArtistName} — {AlbumName} - {Type.ToString()}"); } } - + public class WebSongResponse { public string artworkURL { get; set; } @@ -565,5 +565,74 @@ public static String TrimEnd(this String str, int count) { return str.Substring(0, str.Length - count); } + + public static String[] GetArtist(this IElement element, AppleMusicScrapedData data) + { + string[] returnData = new string[2]; + foreach (IElement innerElement in element.QuerySelectorAll("div.ellipse-lockup-wrapper")) + { + string title = innerElement.QuerySelector("h3.title").TextContent; + if (data.ArtistName == title) + { + returnData[0] = innerElement.QuerySelector("a.click-action").GetAttribute("href"); + returnData[1] = innerElement.QuerySelector("source[type=\"image/jpeg\"]").GetAttribute("srcset") + .Split(' ')[0]; + return returnData; + } + else if (returnData[0] == null && data.ArtistName.Contains(title)) + { + returnData[0] = innerElement.QuerySelector("a.click-action").GetAttribute("href"); + returnData[1] = innerElement.QuerySelector("source[type=\"image/jpeg\"]").GetAttribute("srcset") + .Split(' ')[0]; + } + } + return (returnData[0] == null) ? null : returnData; + + } + + public static String GetAlbum(this IElement element, AppleMusicScrapedData data) + { + foreach (IElement innerElement in element.QuerySelector(@"div[aria-label=""Albums""]").QuerySelectorAll("div > div > section > div > ul > li")) + { + IHtmlCollection texts = innerElement.QuerySelectorAll("span.multiline-clamp__text > a"); + if (texts[0].NormalizedText().Contains(data.AlbumName) && data.ArtistName.Contains(texts[1].NormalizedText())) + { + return texts[0].GetAttribute("href"); + } + } + return null; + } + + public static String NormalizedText(this INode node) + { + return node.TextContent.Replace(" ", " ").Trim(); + } + + public static String GetSong(this IElement element, AppleMusicScrapedData data) + { + foreach (IElement innerElement in element.QuerySelectorAll(@"ul.track-lockup__content")) + { + IHtmlCollection texts = innerElement.QuerySelectorAll(@"div.track-lockup__clamp-wrapper"); + if (texts[0].NormalizedText() == data.SongName && data.ArtistName.Contains(texts[1].QuerySelector("span").NormalizedText())) + { + return texts[0].QuerySelector("a").GetAttribute("href"); + } + } + return null; + } + + public static String GetCover(this IElement element, AppleMusicScrapedData data) + { + foreach (IElement innerElement in element.QuerySelectorAll("div.track-lockup")) + { + if (data.SongName.Contains(innerElement.QuerySelector("ul > li > div > a").NormalizedText()) && + data.ArtistName.Contains(innerElement.QuerySelector("ul > li > div > span > a > span").NormalizedText())) + { + return innerElement.QuerySelectorAll("div > div > div > picture > source")[1].GetAttribute("srcset") + .Split(',')[1].Split(' ')[0]; + } + } + return null; + } } } \ No newline at end of file diff --git a/AMDiscordRPC/UI.cs b/AMDiscordRPC/UI.cs index 2b4a651..52f3d85 100644 --- a/AMDiscordRPC/UI.cs +++ b/AMDiscordRPC/UI.cs @@ -94,18 +94,18 @@ public static void FullScreenTweak() { IntPtr lyricsScreenHandler = (IntPtr)lyricScreenWindow.Properties.NativeWindowHandle; Screen lyricsScreenHandlerCurrentMonitor = Screen.FromHandle(lyricsScreenHandler); - long style = GetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.STYLE); - style &= ~((long) WS.CAPTION | (long) WS.THICKFRAME); - SetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.STYLE, style); + long style = GetWindowLongPtrA(lyricsScreenHandler, (int)GWLP.STYLE); + style &= ~((long)WS.CAPTION | (long)WS.THICKFRAME); + SetWindowLongPtrA(lyricsScreenHandler, (int)GWLP.STYLE, style); - long exStyle = GetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.EXSTYLE); - exStyle &= ~((long) WS_EX.DLGMODALFRAME | (long) WS_EX.CLIENTEDGE | (long) WS_EX.STATICEDGE); - SetWindowLongPtrA(lyricsScreenHandler, (int) GWLP.EXSTYLE, exStyle); + long exStyle = GetWindowLongPtrA(lyricsScreenHandler, (int)GWLP.EXSTYLE); + exStyle &= ~((long)WS_EX.DLGMODALFRAME | (long)WS_EX.CLIENTEDGE | (long)WS_EX.STATICEDGE); + SetWindowLongPtrA(lyricsScreenHandler, (int)GWLP.EXSTYLE, exStyle); - SetWindowPos(lyricsScreenHandler, (int) HWND.TOPMOST, lyricsScreenHandlerCurrentMonitor.Bounds.Left, + SetWindowPos(lyricsScreenHandler, (int)HWND.TOPMOST, lyricsScreenHandlerCurrentMonitor.Bounds.Left, lyricsScreenHandlerCurrentMonitor.Bounds.Top, lyricsScreenHandlerCurrentMonitor.Bounds.Width, lyricsScreenHandlerCurrentMonitor.Bounds.Height, - (uint) SWP.NOOWNERZORDER | (uint) SWP.FRAMECHANGED | (uint) SWP.SHOWWINDOW); + (uint)SWP.NOOWNERZORDER | (uint)SWP.FRAMECHANGED | (uint)SWP.SHOWWINDOW); } } } diff --git a/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs index eb3c5de..87bd260 100644 --- a/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs +++ b/AMDiscordRPC/UIComponents/OptionsWindow.xaml.cs @@ -1,8 +1,4 @@ - -using System; -using System.Management.Instrumentation; -using System.Text.RegularExpressions; -using System.Windows; +using System.Windows; using System.Windows.Controls; using static AMDiscordRPC.Globals;