From 279f208c2de4e6bada9c2eb21eb707500650f373 Mon Sep 17 00:00:00 2001 From: Evann Regnault Date: Tue, 2 Apr 2024 03:11:40 +0200 Subject: [PATCH] First Commit --- .drone.yml | 20 +++ .gitignore | 3 + AppleMusicRPC.sln | 25 +++ AppleMusicRPC/AppleMusicRPC.csproj | 36 ++++ AppleMusicRPC/AppleMusicRPC.csproj.user | 4 + AppleMusicRPC/Payload.cs | 84 ++++++++++ AppleMusicRPC/Program.cs | 26 +++ .../Properties/Resources.Designer.cs | 73 ++++++++ AppleMusicRPC/Properties/Resources.resx | 124 ++++++++++++++ AppleMusicRPC/Provider.cs | 154 +++++++++++++++++ AppleMusicRPC/RPCManager.cs | 130 +++++++++++++++ AppleMusicRPC/Resources/TrayIcon.ico | Bin 0 -> 6897 bytes AppleMusicRPC/Scrapper.cs | 157 ++++++++++++++++++ AppleMusicRPC/ServiceApplicationContext.cs | 31 ++++ AppleMusicRPC/TrackExtras.cs | 82 +++++++++ 15 files changed, 949 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 AppleMusicRPC.sln create mode 100644 AppleMusicRPC/AppleMusicRPC.csproj create mode 100644 AppleMusicRPC/AppleMusicRPC.csproj.user create mode 100644 AppleMusicRPC/Payload.cs create mode 100644 AppleMusicRPC/Program.cs create mode 100644 AppleMusicRPC/Properties/Resources.Designer.cs create mode 100644 AppleMusicRPC/Properties/Resources.resx create mode 100644 AppleMusicRPC/Provider.cs create mode 100644 AppleMusicRPC/RPCManager.cs create mode 100644 AppleMusicRPC/Resources/TrayIcon.ico create mode 100644 AppleMusicRPC/Scrapper.cs create mode 100644 AppleMusicRPC/ServiceApplicationContext.cs create mode 100644 AppleMusicRPC/TrackExtras.cs diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..410e39c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,20 @@ +kind: pipeline +type: docker +name: build + +steps: + - name: Build dotnet image + image: mcr.microsoft.com/dotnet/sdk:8.0 + commands: + - apt-get update && apt-get install -y zip + - dotnet build . -r Release + - zip -r build.zip AppleMusicRPC/bin/Release/net8.0-windows10.0.22000.0 + + + - name: gitea_release + image: plugins/gitea-release + settings: + api_key: + from_secret: RELEASE_KEY + base_url: https://forgejo.regnault.dev + files: build.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e16852 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +.vs/ \ No newline at end of file diff --git a/AppleMusicRPC.sln b/AppleMusicRPC.sln new file mode 100644 index 0000000..3cd49d6 --- /dev/null +++ b/AppleMusicRPC.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppleMusicRPC", "AppleMusicRPC\AppleMusicRPC.csproj", "{10830284-9AF1-40E0-8FE7-0A51F222A402}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {10830284-9AF1-40E0-8FE7-0A51F222A402}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10830284-9AF1-40E0-8FE7-0A51F222A402}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10830284-9AF1-40E0-8FE7-0A51F222A402}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10830284-9AF1-40E0-8FE7-0A51F222A402}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {94D0E4DD-4A9E-4BEE-B135-3AE648854766} + EndGlobalSection +EndGlobal diff --git a/AppleMusicRPC/AppleMusicRPC.csproj b/AppleMusicRPC/AppleMusicRPC.csproj new file mode 100644 index 0000000..d58b46c --- /dev/null +++ b/AppleMusicRPC/AppleMusicRPC.csproj @@ -0,0 +1,36 @@ + + + + WinExe + net8.0-windows10.0.22000.0 + enable + enable + true + true + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/AppleMusicRPC/AppleMusicRPC.csproj.user b/AppleMusicRPC/AppleMusicRPC.csproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/AppleMusicRPC/AppleMusicRPC.csproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/AppleMusicRPC/Payload.cs b/AppleMusicRPC/Payload.cs new file mode 100644 index 0000000..b246441 --- /dev/null +++ b/AppleMusicRPC/Payload.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + + +namespace AppleMusicRPC +{ + public class Payload + { + private string? _playerStateValue; + private double _endTimeValue = -1; + private double _duration = -1; + + public event PropertyChangedEventHandler? PropertyChanged; + + public static class ResponseTypes + { + public const string Response = "res"; + public const string Event = "event"; + } + + public static class PlayingStatuses + { + public const string Playing = "playing"; + public const string NotStarted = "not_started"; + public const string Paused = "paused"; + } + + public string? title { get; set; } + public string? album { get; set; } + public string? artist { get; set; } + public string? thumbnailPath { get; set; } + public string? type { get; set; } + + public string? playerState + { + get => _playerStateValue; + set + { + if (value == _playerStateValue) return; + _playerStateValue = value; + NotifyPropertyChanged(); + } + } + + public double endTime + { + get => _endTimeValue; + set + { + if (value == _endTimeValue) return; + _endTimeValue = value; + NotifyPropertyChanged(); + } + } + + public double duration + { + get => _duration; + set + { + if (value == _duration) return; + _duration = value; + NotifyPropertyChanged(); + } + } + + private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public void ResetToInitialState() + { + title = null; + artist = null; + album = null; + thumbnailPath = null; + playerState = PlayingStatuses.NotStarted; + endTime = -1; + duration = -1; + type = ResponseTypes.Event; + } + } +} diff --git a/AppleMusicRPC/Program.cs b/AppleMusicRPC/Program.cs new file mode 100644 index 0000000..dc35813 --- /dev/null +++ b/AppleMusicRPC/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppleMusicRPC +{ + internal class Program + { + + + [STAThread] + private static void Main() + { + + if (System.Diagnostics.Process.GetProcessesByName(System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetEntryAssembly()?.Location)).Length > 1) return; + + _ = new Provider(); + + Application.Run(new ServiceApplicationContext()); + } + + } +} diff --git a/AppleMusicRPC/Properties/Resources.Designer.cs b/AppleMusicRPC/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0f2c768 --- /dev/null +++ b/AppleMusicRPC/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// Ce code a été généré par un outil. +// Version du runtime :4.0.30319.42000 +// +// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si +// le code est régénéré. +// +//------------------------------------------------------------------------------ + +namespace AppleMusicRPC.Properties { + using System; + + + /// + /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées. + /// + // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder + // à l'aide d'un outil, tel que ResGen ou Visual Studio. + // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen + // avec l'option /str ou régénérez votre projet VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppleMusicRPC.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Remplace la propriété CurrentUICulture du thread actuel pour toutes + /// les recherches de ressources à l'aide de cette classe de ressource fortement typée. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Recherche une ressource localisée de type System.Drawing.Icon semblable à (Icône). + /// + public static System.Drawing.Icon TrayIcon { + get { + object obj = ResourceManager.GetObject("TrayIcon", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/AppleMusicRPC/Properties/Resources.resx b/AppleMusicRPC/Properties/Resources.resx new file mode 100644 index 0000000..a03fc62 --- /dev/null +++ b/AppleMusicRPC/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\TrayIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/AppleMusicRPC/Provider.cs b/AppleMusicRPC/Provider.cs new file mode 100644 index 0000000..450f0f4 --- /dev/null +++ b/AppleMusicRPC/Provider.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Windows.Media.Control; + +namespace AppleMusicRPC +{ + internal class Provider + { + private GlobalSystemMediaTransportControlsSessionManager? _sessionManager; + private GlobalSystemMediaTransportControlsSession? _ampSession; + + private const string AMPModelId = "AppleInc.AppleMusicWin"; + + private readonly Payload _payload; + private SemaphoreSlim semaphore = new SemaphoreSlim(1); + + public Provider() + { + _payload = new Payload(); + + UpdateSessionManager(); + UpdateAMPSession(); + } + + private async void UpdateSessionManager() + { + if (_sessionManager != null) return; + + _sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); + _sessionManager.SessionsChanged += OnSessionsChanged; + GetAMPSession(); + } + + private void UpdateAMPSession() + { + UpdateSessionManager(); + GetAMPSession(); + } + + private void GetAMPSession() + { + if (_sessionManager != null) + { + GlobalSystemMediaTransportControlsSession newSession = FindAMPSession(); + SetAMPSession(newSession); + } + + if (_ampSession != null) + { + _ampSession.MediaPropertiesChanged += OnMediaPropertiesChanged; + _ampSession.PlaybackInfoChanged += OnPlaybackInfoChanged; + OnMediaPropertiesChanged(_ampSession, null); + } + else + { + _payload.ResetToInitialState(); + } + } + + private void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession? sender, PlaybackInfoChangedEventArgs? args) + { + Thread.Sleep(1000); + try + { + if (_ampSession == null) + { + _payload.ResetToInitialState(); + RPCManager.SetActivity(_payload); + return; + }; + + var playbackInfo = _ampSession.GetPlaybackInfo(); + var timelineProperties = _ampSession.GetTimelineProperties(); + _payload.playerState = playbackInfo.PlaybackStatus.ToString().ToLower() == Payload.PlayingStatuses.Playing + ? Payload.PlayingStatuses.Playing : Payload.PlayingStatuses.Paused; + + _payload.endTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + + (timelineProperties.EndTime.TotalMilliseconds - timelineProperties.Position.TotalMilliseconds); + _payload.duration = timelineProperties.EndTime.TotalSeconds - timelineProperties.StartTime.TotalSeconds; + + RPCManager.SetActivity(_payload); + } + catch (Exception _) + { + _payload.ResetToInitialState(); + RPCManager.SetActivity(_payload); + } + } + + private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession? sender, MediaPropertiesChangedEventArgs? args) + { + await semaphore.WaitAsync(); + try + { + await sender?.TryGetMediaPropertiesAsync(); + } + catch (Exception _) + { + + } + + var songInfos = ClientScrapper.GetInfos(); + if (songInfos != null) + { + _payload.artist = songInfos.SongArtist; + _payload.album = songInfos.SongAlbum; + _payload.title = songInfos.SongName; + OnPlaybackInfoChanged(null, null); + } + else + { + _payload.ResetToInitialState(); + RPCManager.SetActivity(_payload); + } + semaphore.Release(); + } + + private void SetAMPSession(GlobalSystemMediaTransportControlsSession newSession) + { + if (newSession == null && _ampSession != null) + { + _ampSession = null; + } + else if (_ampSession == null) + { + _ampSession = newSession; + } + } + + private GlobalSystemMediaTransportControlsSession FindAMPSession() + { + IReadOnlyList sessions = _sessionManager.GetSessions(); + foreach (GlobalSystemMediaTransportControlsSession session in sessions) + { + if (session.SourceAppUserModelId.Contains(AMPModelId)) + { + return session; + } + } + + return null; + } + + private void OnSessionsChanged(object sender, SessionsChangedEventArgs e) + { + UpdateAMPSession(); + } + + private static async Task? GetMediaProperties(GlobalSystemMediaTransportControlsSession AMPSession) => + AMPSession == null ? null : await AMPSession.TryGetMediaPropertiesAsync(); + } +} diff --git a/AppleMusicRPC/RPCManager.cs b/AppleMusicRPC/RPCManager.cs new file mode 100644 index 0000000..9d2425e --- /dev/null +++ b/AppleMusicRPC/RPCManager.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DiscordRPC; + +namespace AppleMusicRPC +{ + internal class RPCManager + { + + private static DiscordRpcClient rpcClient { get; set; } + + private static DiscordRpcClient GetClient() + { + if (rpcClient == null) + { + rpcClient = new DiscordRpcClient("773825528921849856"); + rpcClient.Initialize(); + } + + return rpcClient; + } + + private static Assets BuildAssetsFromPayload(Payload payload, TrackExtras extras) + { + return new Assets + { + LargeImageKey = extras.ArtworkUrl ?? "appicon", + LargeImageText = payload.album + }; + } + + private static Button[] BuildButtonsFromPayload(Payload payload, TrackExtras extras) + { + var Buttons = new List