first commit

This commit is contained in:
2025-06-07 02:03:54 +03:00
commit 75762504bd
21 changed files with 1005 additions and 0 deletions

138
.editorconfig Normal file
View File

@@ -0,0 +1,138 @@
root = true
# top-most EditorConfig file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
# 4 space indentation
indent_style = space
indent_size = 4
# disable redundant style warnings
# Microsoft .NET properties
csharp_indent_braces = false
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_open_brace = all
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_code_quality_unused_parameters = non_public
dotnet_naming_rule.event_rule.severity = warning
dotnet_naming_rule.event_rule.style = on_upper_camel_case_style
dotnet_naming_rule.event_rule.symbols = event_symbols
dotnet_naming_rule.private_constants_rule.severity = warning
dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
dotnet_naming_rule.private_instance_fields_rule.severity = warning
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
dotnet_naming_rule.private_static_fields_rule.severity = warning
dotnet_naming_rule.private_static_fields_rule.style = upper_camel_case_style
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
dotnet_naming_rule.private_static_readonly_rule.severity = warning
dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_style.on_upper_camel_case_style.capitalization = pascal_case
dotnet_naming_style.on_upper_camel_case_style.required_prefix = On
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.event_symbols.applicable_accessibilities = *
dotnet_naming_symbols.event_symbols.applicable_kinds = event
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_style_parentheses_in_arithmetic_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
dotnet_style_object_initializer = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_empty_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_before_open_square_brackets = false
csharp_space_before_comma = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_comma = true
csharp_space_after_cast = false
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = none
csharp_space_between_square_brackets = false
# ReSharper properties
resharper_align_linq_query = true
resharper_align_multiline_argument = true
resharper_align_multiline_calls_chain = true
resharper_align_multiline_expression = true
resharper_align_multiline_extends_list = true
resharper_align_multiline_for_stmt = true
resharper_align_multline_type_parameter_constrains = true
resharper_align_multline_type_parameter_list = true
resharper_apply_on_completion = true
resharper_auto_property_can_be_made_get_only_global_highlighting = none
resharper_auto_property_can_be_made_get_only_local_highlighting = none
resharper_autodetect_indent_settings = true
resharper_braces_for_ifelse = required_for_multiline
resharper_can_use_global_alias = false
resharper_csharp_align_multiline_parameter = true
resharper_csharp_align_multiple_declaration = true
resharper_csharp_empty_block_style = together_same_line
resharper_csharp_int_align_comments = true
resharper_csharp_new_line_before_while = true
resharper_csharp_wrap_after_declaration_lpar = true
resharper_enforce_line_ending_style = true
resharper_member_can_be_private_global_highlighting = none
resharper_member_can_be_private_local_highlighting = none
resharper_new_line_before_finally = false
resharper_place_accessorholder_attribute_on_same_line = false
resharper_place_field_attribute_on_same_line = false
resharper_show_autodetect_configure_formatting_tip = false
resharper_use_indent_from_vs = false
# ReSharper inspection severities
resharper_arrange_missing_parentheses_highlighting = hint
resharper_arrange_redundant_parentheses_highlighting = hint
resharper_arrange_this_qualifier_highlighting = hint
resharper_arrange_type_member_modifiers_highlighting = hint
resharper_arrange_type_modifiers_highlighting = hint
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = none
resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = none
resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = none
resharper_invert_if_highlighting = none
resharper_loop_can_be_converted_to_query_highlighting = none
resharper_method_has_async_overload_highlighting = none
resharper_private_field_can_be_converted_to_local_variable_highlighting = none
resharper_redundant_base_qualifier_highlighting = none
resharper_suggest_var_or_type_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_highlighting = hint
resharper_unused_auto_property_accessor_global_highlighting = none
csharp_style_deconstructed_variable_declaration=true:silent
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 4
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent

40
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Build
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
Build:
runs-on: ubuntu-latest
env:
DALAMUD_HOME: /tmp/dalamud
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: Download Dalamud Latest
run: |
wget https://goatcorp.github.io/dalamud-distrib/latest.zip -O ${{ env.DALAMUD_HOME }}.zip
unzip ${{ env.DALAMUD_HOME }}.zip -d ${{ env.DALAMUD_HOME }}
- name: Restore Project
run: dotnet restore
- name: Build Project
run: dotnet build --configuration Release SpotifyHonorific/SpotifyHonorific.csproj
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: SpotifyHonorific
path: |
SpotifyHonorific/bin/Release/*
!SpotifyHonorific/bin/Release/SpotifyHonorific/*

52
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Build and Release
on:
push:
tags:
- "*.*.*.*"
jobs:
Build:
runs-on: ubuntu-latest
env:
DALAMUD_HOME: /tmp/dalamud
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: Download Dalamud Latest
run: |
wget https://goatcorp.github.io/dalamud-distrib/latest.zip -O ${{ env.DALAMUD_HOME }}.zip
unzip ${{ env.DALAMUD_HOME }}.zip -d ${{ env.DALAMUD_HOME }}
- name: Restore Project
run: dotnet restore
- name: Build Project
run: dotnet build --configuration Release SpotifyHonorific/SpotifyHonorific.csproj -p:AssemblyVersion=${{ github.ref_name }}
- name: Create Release
uses: actions/create-release@v1
id: create_release
with:
draft: false
prerelease: false
release_name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: SpotifyHonorific/bin/Release/SpotifyHonorific/latest.zip
asset_name: SpotifyHonorific.zip
asset_content_type: application/zip
env:
GITHUB_TOKEN: ${{ github.token }}

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.vs/
obj/
bin/
*.user

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# SpotifyHonorific
Update honorific title based on discord activity informations.
## Installation
Installable using my custom repository (instructions here: https://github.com/anya-hichu/DalamudPluginRepo) or from compiled archives.
## Commands
- `/spotifyhonorific config`
- `/spotifyhonorific enable`
- `/spotifyhonorific disable`

25
SpotifyHonorific.sln Normal file
View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35919.96 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpotifyHonorific", "SpotifyHonorific\SpotifyHonorific.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace SpotifyHonorific.Activities;
[Serializable]
public class ActivityConfig
{
public static readonly int DEFAULT_VERSION = 1;
private static readonly List<ActivityConfig> DEFAULTS = [
new() {
Name = $"Spotify (V{DEFAULT_VERSION})",
Priority = 1,
TitleTemplate = """
{{- if (Context.SecsElapsed % 30) < 10 -}}
Listening to Spotify
{{- else if (Context.SecsElapsed % 30) < 20 -}}
{{ Activity.SongName | string.truncate 30 }}
{{- else -}}
{{ Activity.Artist | string.truncate 30 }}
{{- end -}}
"""
}
];
public string Name { get; set; } = string.Empty;
public bool Enabled { get; set; } = true;
public int Priority { get; set; } = 0;
public string TypeName { get; set; } = string.Empty;
public string FilterTemplate { get; set; } = string.Empty;
public string TitleTemplate { get; set; } = string.Empty;
public bool IsPrefix { get; set; } = false;
public Vector3? Color { get; set; }
public Vector3? Glow { get; set; }
public ActivityConfig Clone()
{
return (ActivityConfig)MemberwiseClone();
}
public static List<ActivityConfig> GetDefaults()
{
return DEFAULTS.Select(c => c.Clone()).ToList();
}
}

View File

@@ -0,0 +1,28 @@
using Dalamud.Configuration;
using SpotifyHonorific.Activities;
using System;
using System.Collections.Generic;
namespace SpotifyHonorific;
[Serializable]
public class Config : IPluginConfiguration
{
public int Version { get; set; } = 0;
public bool Enabled { get; set; } = true;
public string Token { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public List<ActivityConfig> ActivityConfigs { get; set; } = [];
public Config() { }
public Config(List<ActivityConfig> activityConfigs)
{
ActivityConfigs = activityConfigs;
}
public void Save()
{
Plugin.PluginInterface.SavePluginConfig(this);
}
}

View File

@@ -0,0 +1,82 @@
using Dalamud.Game.Command;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using SpotifyHonorific.Windows;
using SpotifyHonorific.Activities;
using SpotifyHonorific.Updaters;
namespace SpotifyHonorific;
public sealed class Plugin : IDalamudPlugin
{
[PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] internal static ICommandManager CommandManager { get; private set; } = null!;
[PluginService] internal static IChatGui ChatGui { get; private set; } = null!;
[PluginService] internal static IPluginLog PluginLog { get; private set; } = null!;
[PluginService] internal static IFramework Framework { get; private set; } = null!;
private const string CommandName = "/spotifyhonorific";
private const string CommandHelpMessage = $"Available subcommands for {CommandName} are config, enable and disable";
public Config Config { get; init; }
public readonly WindowSystem WindowSystem = new("SpotifyHonorific");
private ConfigWindow ConfigWindow { get; init; }
private Updater Updater { get; init; }
public Plugin()
{
Config = PluginInterface.GetPluginConfig() as Config ?? new Config(ActivityConfig.GetDefaults());
Updater = new(Config, Framework, PluginInterface, PluginLog);
ConfigWindow = new ConfigWindow(Config, new(), Updater);
WindowSystem.AddWindow(ConfigWindow);
CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
{
HelpMessage = CommandHelpMessage
});
PluginInterface.UiBuilder.Draw += DrawUI;
PluginInterface.UiBuilder.OpenConfigUi += ToggleConfigUI;
PluginInterface.UiBuilder.OpenMainUi += ToggleConfigUI;
}
public void Dispose()
{
WindowSystem.RemoveAllWindows();
CommandManager.RemoveHandler(CommandName);
Updater.Dispose();
}
private void OnCommand(string command, string args)
{
var subcommand = args.Split(" ", 2)[0];
if (subcommand == "config")
{
ToggleConfigUI();
}
else if (subcommand == "enable")
{
Config.Enabled = true;
Config.Save();
Updater.Start();
}
else if (subcommand == "disable")
{
Config.Enabled = false;
Config.Save();
Updater.Stop();
}
else
{
ChatGui.Print(CommandHelpMessage);
}
}
private void DrawUI() => WindowSystem.Draw();
public void ToggleConfigUI() => ConfigWindow.Toggle();
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Dalamud.NET.Sdk/12.0.2">
<PropertyGroup>
<Description>Update honorific title based on spotify informations</Description>
<IsPackable>false</IsPackable>
<TargetFramework>net9.0-windows7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Scriban" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
{
"Author": "Lozy Rhel",
"Name": "SpotifyHonorific",
"Punchline": "Spotify activity as honorific",
"Description": "Update honorific title based on Spotify activity informations",
"ApplicableVersion": "any",
"Tags": [
"spotify",
"activity",
"honorific"
]
}

View File

@@ -0,0 +1,285 @@
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<int, string, object> SetCharacterTitleSubscriber { get; init; }
private ICallGateSubscriber<int, object> 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<int, string, object>("Honorific.SetCharacterTitle");
ClearCharacterTitleSubscriber = PluginInterface.GetIpcSubscriber<int, object>("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<string, object>() {
{"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
}

View File

@@ -0,0 +1,6 @@
namespace SpotifyHonorific.Updaters;
public class UpdaterContext
{
public double SecsElapsed { get; set; } = 0;
}

View File

@@ -0,0 +1,98 @@
using Dalamud.Interface.Utility;
using ImGuiNET;
using System.Numerics;
namespace SpotifyHonorific.Utils;
public class ImGuiHelper
{
// Source: https://github.com/Caraxi/Honorific/blob/1.4.1.0/ConfigWindow.cs#L826
private Vector3 editingColour = Vector3.One;
public bool DrawColorPicker(string label, ref Vector3? color, Vector2 checkboxSize)
{
var modified = false;
bool comboOpen;
ImGui.SetNextItemWidth(checkboxSize.X * 2);
if (color == null)
{
ImGui.PushStyleColor(ImGuiCol.FrameBg, 0xFFFFFFFF);
ImGui.PushStyleColor(ImGuiCol.FrameBgActive, 0xFFFFFFFF);
ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, 0xFFFFFFFF);
var p = ImGui.GetCursorScreenPos();
var dl = ImGui.GetWindowDrawList();
comboOpen = ImGui.BeginCombo(label, " ", ImGuiComboFlags.HeightLargest);
dl.AddLine(p, p + new Vector2(checkboxSize.X), 0xFF0000FF, 3f * ImGuiHelpers.GlobalScale);
ImGui.PopStyleColor(3);
}
else
{
ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(color.Value, 1));
ImGui.PushStyleColor(ImGuiCol.FrameBgActive, new Vector4(color.Value, 1));
ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, new Vector4(color.Value, 1));
comboOpen = ImGui.BeginCombo(label, " ", ImGuiComboFlags.HeightLargest);
ImGui.PopStyleColor(3);
}
if (comboOpen)
{
if (ImGui.IsWindowAppearing())
{
editingColour = color ?? Vector3.One;
}
if (ImGui.ColorButton($"##ColorPickClear", Vector4.One, ImGuiColorEditFlags.NoTooltip))
{
color = null;
modified = true;
ImGui.CloseCurrentPopup();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Clear selected colour");
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
}
var dl = ImGui.GetWindowDrawList();
dl.AddLine(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), 0xFF0000FF, 3f * ImGuiHelpers.GlobalScale);
if (color != null)
{
ImGui.SameLine();
if (ImGui.ColorButton($"##ColorPick_old", new Vector4(color.Value, 1), ImGuiColorEditFlags.NoTooltip))
{
ImGui.CloseCurrentPopup();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Revert to previous selection");
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
}
}
ImGui.SameLine();
if (ImGui.ColorButton("Confirm", new Vector4(editingColour, 1), ImGuiColorEditFlags.NoTooltip, new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetItemRectSize().Y)))
{
color = editingColour;
modified = true;
ImGui.CloseCurrentPopup();
}
var size = ImGui.GetItemRectSize();
if (ImGui.IsItemHovered())
{
dl.AddRectFilled(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), 0x33333333);
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
}
var textSize = ImGui.CalcTextSize("Confirm");
dl.AddText(ImGui.GetItemRectMin() + size / 2 - textSize / 2, ImGui.ColorConvertFloat4ToU32(new Vector4(editingColour, 1)) ^ 0x00FFFFFF, "Confirm");
ImGui.ColorPicker3($"##ColorPick", ref editingColour, ImGuiColorEditFlags.NoSidePreview | ImGuiColorEditFlags.NoSmallPreview);
ImGui.EndCombo();
}
return modified;
}
}

View File

@@ -0,0 +1,136 @@
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using SpotifyHonorific.Updaters;
using SpotifyHonorific.Utils;
using ImGuiNET;
using System.Numerics;
namespace SpotifyHonorific.Windows;
public class ConfigWindow : Window
{
private Config Config { get; init; }
private ImGuiHelper ImGuiHelper { get; init; }
private Updater Updater { get; init; }
public ConfigWindow(Config config, ImGuiHelper imGuiHelper, Updater updater) : base("Spotify Honorific Config (Modified By Lozy Rhel)##configWindow")
{
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(760, 420),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
};
Config = config;
ImGuiHelper = imGuiHelper;
Updater = updater;
}
public override void Draw()
{
var enabled = Config.Enabled;
if (ImGui.Checkbox("Enabled##enabled", ref enabled))
{
Config.Enabled = enabled;
Config.Save();
Updater.Enable(enabled);
}
if (ImGui.BeginTabBar("activityConfigsTabBar"))
{
foreach (var activityConfig in Config.ActivityConfigs)
{
var activityConfigId = $"activityConfigs{activityConfig.GetHashCode()}";
var name = activityConfig.Name;
if (ImGui.BeginTabItem($"{(name.IsNullOrWhitespace() ? "(Blank)" : name)}###{activityConfigId}TabItem"))
{
ImGui.Indent(10);
var activityConfigEnabled = activityConfig.Enabled;
if (ImGui.Checkbox($"Enabled###{activityConfigId}enabled", ref activityConfigEnabled))
{
activityConfig.Enabled = activityConfigEnabled;
Config.Save();
}
if (ImGui.InputText($"Name###{activityConfigId}Name", ref name, ushort.MaxValue))
{
activityConfig.Name = name;
Config.Save();
}
var priority = activityConfig.Priority;
if (ImGui.InputInt($"Priority###{activityConfigId}Priority", ref priority, 1))
{
activityConfig.Priority = priority;
Config.Save();
}
var typeName = activityConfig.TypeName;
var filterTemplate = activityConfig.FilterTemplate;
var filterTemplateInput = ImGui.InputTextMultiline($"Filter Template (scriban)###{activityConfigId}FilterTemplate", ref filterTemplate, ushort.MaxValue, new(ImGui.GetWindowWidth() - 170, 50));
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Expects parsable boolean as output if provided\nSyntax reference available on https://github.com/scriban/scriban");
}
if (filterTemplateInput)
{
activityConfig.FilterTemplate = filterTemplate;
Config.Save();
}
var titleTemplate = activityConfig.TitleTemplate;
var titleTemplateInput = ImGui.InputTextMultiline($"Title Template (scriban)###{activityConfigId}TitleTemplate", ref titleTemplate, ushort.MaxValue, new(ImGui.GetWindowWidth() - 170, ImGui.GetWindowHeight() - ImGui.GetCursorPosY() - 40));
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Expects single line as output\nSyntax reference available on https://github.com/scriban/scriban");
}
if (titleTemplateInput)
{
activityConfig.TitleTemplate = titleTemplate;
Config.Save();
}
var isPrefix = activityConfig.IsPrefix;
if (ImGui.Checkbox($"Prefix###{activityConfigId}Prefix", ref isPrefix))
{
activityConfig.IsPrefix = isPrefix;
Config.Save();
}
ImGui.SameLine();
ImGui.Spacing();
ImGui.SameLine();
var checkboxSize = new Vector2(ImGui.GetTextLineHeightWithSpacing(), ImGui.GetTextLineHeightWithSpacing());
var color = activityConfig.Color;
if (ImGuiHelper.DrawColorPicker($"Color###{activityConfigId}Color", ref color, checkboxSize))
{
activityConfig.Color = color;
Config.Save();
}
ImGui.SameLine();
ImGui.Spacing();
ImGui.SameLine();
var glow = activityConfig.Glow;
if (ImGuiHelper.DrawColorPicker($"Glow###{activityConfigId}Glow", ref glow, checkboxSize))
{
activityConfig.Glow = glow;
Config.Save();
}
ImGui.Unindent();
ImGui.EndTabItem();
}
}
ImGui.EndTabBar();
}
}
}

View File

@@ -0,0 +1,25 @@
{
"version": 1,
"dependencies": {
"net9.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[12.0.0, )",
"resolved": "12.0.0",
"contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.2.25, )",
"resolved": "1.2.25",
"contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg=="
},
"Scriban": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "MZOtxtcehrCGiVwHpdcZQSe04Zy4IJfltVZdmlr1nFvSvEXnu50SWa7fonC0bqfMyTnNhQcY9BmEt882P129qw=="
}
}
}
}

BIN
images/image1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

BIN
images/image2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

BIN
images/image3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
images/image4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
images/image5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB