using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Services; using Dalamud.Utility; using Newtonsoft.Json; using Scriban; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Timers; namespace SpotifyHonorific.Updaters; public class Updater : IDisposable { private Config Config { get; init; } private IFramework Framework { get; init; } private IDalamudPluginInterface PluginInterface { get; init; } private IPluginLog PluginLog { get; init; } private ICallGateSubscriber SetCharacterTitleSubscriber { get; init; } private ICallGateSubscriber ClearCharacterTitleSubscriber { get; init; } private Action? UpdateTitle { get; set; } private string? UpdatedTitleJson { get; set; } private UpdaterContext UpdaterContext { get; init; } = new(); private Timer MediaCheckTimer { get; init; } private MediaActivity? CurrentMediaActivity { get; set; } public Updater(Config config, IFramework framwork, IDalamudPluginInterface pluginInterface, IPluginLog pluginLog) { Config = config; Framework = framwork; PluginInterface = pluginInterface; PluginLog = pluginLog; SetCharacterTitleSubscriber = PluginInterface.GetIpcSubscriber("Honorific.SetCharacterTitle"); ClearCharacterTitleSubscriber = PluginInterface.GetIpcSubscriber("Honorific.ClearCharacterTitle"); MediaCheckTimer = new Timer(2000); MediaCheckTimer.Elapsed += CheckMediaActivity; MediaCheckTimer.AutoReset = true; if (Config.Enabled) { Start(); } Framework.Update += OnFrameworkUpdate; } public void Dispose() { Framework.Update -= OnFrameworkUpdate; MediaCheckTimer?.Stop(); MediaCheckTimer?.Dispose(); Framework.RunOnFrameworkThread(() => { ClearCharacterTitleSubscriber.InvokeAction(0); }); } public Task Enable(bool value) { return value ? Start() : Stop(); } public Task Restart() { return Stop().ContinueWith(t => Start()); } public Task Start() { if (Config.Enabled) { MediaCheckTimer.Start(); PluginLog.Info("Media activity monitoring started"); } return Task.CompletedTask; } public Task Stop() { MediaCheckTimer.Stop(); Framework.RunOnFrameworkThread(() => { ClearCharacterTitleSubscriber.InvokeAction(0); }); PluginLog.Info("Media activity monitoring stopped"); return Task.CompletedTask; } public string State() { return MediaCheckTimer.Enabled ? "Running" : "Stopped"; } private void CheckMediaActivity(object sender, ElapsedEventArgs e) { try { var mediaActivity = GetCurrentMediaActivity(); if (!MediaActivitiesEqual(CurrentMediaActivity, mediaActivity)) { CurrentMediaActivity = mediaActivity; MediaActivityUpdated(mediaActivity); } } catch (Exception ex) { PluginLog.Error($"Error checking media activity: {ex.Message}"); } } private MediaActivity? GetCurrentMediaActivity() { var spotifyTrack = GetSpotifyTrack(); if (!string.IsNullOrEmpty(spotifyTrack)) { string artist = ""; string songName = spotifyTrack; var parts = spotifyTrack.Split(new[] { " - " }, 2, StringSplitOptions.None); if (parts.Length == 2) { artist = parts[0]; songName = parts[1]; } return new MediaActivity { Name = "Spotify (V1)", SongName = songName, Artist = artist, Type = ActivityType.Listening }; } return null; } private string? GetSpotifyTrack() { try { var spotifyProcesses = Process.GetProcessesByName("Spotify"); foreach (var process in spotifyProcesses) { if (!string.IsNullOrEmpty(process.MainWindowTitle) && process.MainWindowTitle != "Spotify" && process.MainWindowTitle != "Spotify Premium" && process.MainWindowTitle != "Spotify Free") { return process.MainWindowTitle; } } } catch (Exception ex) { PluginLog.Debug($"Error getting Spotify track: {ex.Message}"); } return null; } private bool MediaActivitiesEqual(MediaActivity? a, MediaActivity? b) { if (a == null && b == null) return true; if (a == null || b == null) return false; return a.Name == b.Name && a.SongName == b.SongName && a.Type == b.Type; } private void MediaActivityUpdated(MediaActivity? mediaActivity) { PluginLog.Verbose($"MediaActivityUpdated: {JsonConvert.SerializeObject(mediaActivity, Formatting.Indented)}"); if (mediaActivity != null) { foreach (var activityConfig in Config.ActivityConfigs.Where(c => c.Enabled).OrderByDescending(c => c.Priority)) { var matchesType = (mediaActivity.Type == ActivityType.Listening); PluginLog.Verbose($"Checking activity config '{activityConfig.Name}' for match: {matchesType}"); if (matchesType) { var matchFilter = true; if (!activityConfig.FilterTemplate.IsNullOrWhitespace()) { var filterTemplate = Template.Parse(activityConfig.FilterTemplate); var filter = filterTemplate.Render(new { Activity = mediaActivity, Context = UpdaterContext }, member => member.Name); if (bool.TryParse(filter, out var parsedFilter)) { matchFilter = parsedFilter; } else { PluginLog.Error($"Unable to parse filter '{filter}' as boolean, skipping result"); } } if (matchFilter) { UpdaterContext.SecsElapsed = 0; UpdateTitle = () => { if (Config.Enabled && activityConfig.Enabled) { var titleTemplate = Template.Parse(activityConfig.TitleTemplate); var title = titleTemplate.Render(new { Activity = mediaActivity, Context = UpdaterContext }, member => member.Name); var data = new Dictionary() { {"Title", title}, {"IsPrefix", activityConfig.IsPrefix}, {"Color", activityConfig.Color!}, {"Glow", activityConfig.Glow!} }; var serializedData = JsonConvert.SerializeObject(data, Formatting.Indented); if (serializedData != UpdatedTitleJson) { PluginLog.Verbose($"Call Honorific SetCharacterTitle IPC with:\n{serializedData}"); SetCharacterTitleSubscriber.InvokeAction(0, serializedData); UpdatedTitleJson = serializedData; } } else { ClearTitle(); } }; return; } } } } if (UpdateTitle != null || UpdatedTitleJson != null) { ClearTitle(); } } private void OnFrameworkUpdate(IFramework framework) { if (Config.Enabled && UpdateTitle != null) { UpdateTitle.Invoke(); UpdaterContext.SecsElapsed += framework.UpdateDelta.TotalSeconds; } } private void ClearTitle() { PluginLog.Verbose("Call Honorific ClearCharacterTitle IPC"); Framework.RunOnFrameworkThread(() => { ClearCharacterTitleSubscriber.InvokeAction(0); }); UpdaterContext.SecsElapsed = 0; UpdateTitle = null; UpdatedTitleJson = null; } } public class MediaActivity { public string Name { get; set; } = ""; public string SongName { get; set; } = ""; public string Artist { get; set; } = ""; public ActivityType Type { get; set; } } public enum ActivityType { Playing, Streaming, Listening, Watching, Custom, Competing }