listenbrainz-discord-rpc/Program.cs
Thord Johansson 90eca9e76a Enhance config handling and album art logic
- Refactored variable names for clarity, changing `data` to `config_data` to better indicate its purpose related to configuration settings.
- Significantly enhanced the album art determination logic to prioritize MusicBrainz data when configured, with a fallback to YouTube thumbnails if necessary.
- Introduced `FindMusicBrainzRelease` method for searching MusicBrainz releases based on the release name, aiming for exact alphanumeric matches or the highest scoring match.
- Added `EqualsAlphanumeric` utility method for accurate alphanumeric string comparisons, supporting the MusicBrainz release search functionality.
2024-06-18 23:40:12 +02:00

302 lines
13 KiB
C#

using DiscordRPC.Logging;
using DiscordRPC;
using MetaBrainz.ListenBrainz;
using MetaBrainz.MusicBrainz;
using IniParser;
using IniParser.Model;
using Topshelf;
using System.Web;
using MetaBrainz.MusicBrainz.Interfaces.Entities;
using MetaBrainz.MusicBrainz.Interfaces.Searches;
public class LbzRpcService
{
DiscordRpcClient? client = null;
ListenBrainz? listenBrainz = null;
readonly Thread t;
bool running = false;
public LbzRpcService()
{
t = new Thread(MainThread);
}
public void Start()
{
running = true;
t.Start();
}
public void Stop()
{
running = false;
client?.Dispose();
listenBrainz?.Dispose();
}
public void MainThread()
{
FileIniDataParser config = new FileIniDataParser();
if (!File.Exists("config.ini"))
{
throw new ApplicationException("Configuration file not found. Please create a config.ini file with your tokens and user information.");
}
IniData config_data = config.ReadFile("config.ini");
listenBrainz = new ListenBrainz();
client = new DiscordRpcClient(config_data["general"]["discord_token"]);
void InitializeDiscordRpc()
{
client.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
client.SkipIdenticalPresence = true;
client.OnReady += (sender, e) =>
{
Console.WriteLine("Received Ready from user {0}", e.User.Username);
};
client.Initialize();
}
bool connected = false;
client.OnConnectionEstablished += (sender, e) =>
{
Console.WriteLine("Connected to Discord");
connected = true;
};
InitializeDiscordRpc();
string? lastRecording = null;
List<Button> buttons = new List<Button>();
string albumArtUrl = "";
while (running)
{
if (!connected)
{
// Wait for Discord client to connect
Thread.Sleep(2000);
continue;
}
var username = config_data["general"]["listenbrainz_username"];
var ignoredPlayers = config_data["general"]["ignored_sources"].Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var listen = listenBrainz.GetPlayingNow(username);
if (listen != null)
{
//check ignored players
if (listen.Track?.Info?.AdditionalInfo?.MediaPlayer != null && ignoredPlayers.Any(player => player.ToLower() == listen.Track.Info.AdditionalInfo.MediaPlayer.ToLower()))
{
Console.WriteLine("Ignoring player {0}", listen.Track.Info.AdditionalInfo.MediaPlayer);
client.ClearPresence();
}
else
{
var listenData = listen.Track?.Info;
if (listenData != null)
{
if (listenData.AdditionalInfo.OriginUrl != null && (listenData.AdditionalInfo.OriginUrl?.Host == "music.youtube.com" || listenData.AdditionalInfo.OriginUrl?.Host == "www.youtube.com") && lastRecording != GetYoutubeId(listenData.AdditionalInfo.OriginUrl))
{
lastRecording = GetYoutubeId(listenData.AdditionalInfo.OriginUrl);
Console.WriteLine("Searching for release {0} on MusicBrainz", listenData.Release);
Query q = new("LBZ-DRPC", new Version(1, 0, 0));
ISearchResult<IRelease>? release = null;
if (listenData.Release != null || listenData.Artist != null)
{
release = q.FindReleases(listenData.Release ?? listenData.Artist, 1).Results?.FirstOrDefault();
if (release?.Item.CoverArtArchive == null || !release.Item.CoverArtArchive.Front)
{
if (release?.Item.Aliases != null)
foreach (IAlias alias in release.Item.Aliases)
{
if (alias.Primary == true)
{
var r = q.FindReleases(alias.Name, 1).Results?.FirstOrDefault();
if (release != null && release.Item.CoverArtArchive?.Front == true)
{
release = r;
}
}
};
}
}
if (release != null)
{
albumArtUrl = "https://coverartarchive.org/release/" + release.Item.Id + "/front-250.jpg";
//albumArtUrl = "https://coverartarchive.org/release/" + release?.Item.Id + "/front-250.jpg";
}
else
{
var youtubeId = GetYoutubeId(listenData.AdditionalInfo.OriginUrl);
albumArtUrl = "https://img.youtube.com/vi/" + youtubeId + "/mqdefault.jpg";
}
}
else if (lastRecording != listenData.AdditionalInfo?.RecordingId.ToString()) // MusicBrainz ID available
{
Console.WriteLine("Listening to {0} by {1}", listenData.Name, listenData.Artist);
lastRecording = listenData.AdditionalInfo?.RecordingId.ToString();
buttons = new List<Button>();
if (listenData.AdditionalInfo?.RecordingId != null)
{
buttons.Add(new Button() { Label = "Listen on MusicBrainz", Url = $"https://listenbrainz.org/player/?recording_mbids={listenData.AdditionalInfo?.RecordingId}" });
}
buttons.Add(new Button() { Label = $"{username} on ListenBrainz", Url = $"https://listenbrainz.org/user/{username}" });
string? newAlbumArt = null;
if (config_data["general"]["prioritise_musicbrainz"] == "true" && listenData.AdditionalInfo?.ReleaseId != null)
{
newAlbumArt = "https://coverartarchive.org/release/" + listenData.AdditionalInfo?.ReleaseId + "/front-250.jpg";
}
else if (config_data["general"]["prioritise_musicbrainz"] == "true" && listenData.Release != null)
{
var release = FindMusicBrainzRelease(listenData.Release);
newAlbumArt = "https://coverartarchive.org/release/" + release?.Item.Id + "/front-250.jpg";
}
if (newAlbumArt == null)
{
if ((listenData.AdditionalInfo?.OriginUrl?.ToString().Contains("youtube") == true
|| listenData.AdditionalInfo?.OriginUrl?.ToString().Contains("youtu.be") == true)
&& GetYoutubeId(listenData.AdditionalInfo.OriginUrl) != null)
{
newAlbumArt = "https://img.youtube.com/vi/" + GetYoutubeId(listenData.AdditionalInfo.OriginUrl) + "/0.jpg";
}
else if (listenData.AdditionalInfo?.ReleaseId != null)
{
newAlbumArt = "https://coverartarchive.org/release/" + listenData.AdditionalInfo?.ReleaseId + "/front-250.jpg";
}
else if (listenData.Release != null)
{
var release = FindMusicBrainzRelease(listenData.Release);
newAlbumArt = "https://coverartarchive.org/release/" + release?.Item.Id + "/front-250.jpg";
}
}
Console.WriteLine("Album art URL: " + (newAlbumArt ?? "(none)"));
albumArtUrl = newAlbumArt ?? "";
}
client.SetPresence(new RichPresence()
{
Details = $"{listenData.Name}",
State = (listenData.Release != null && listenData.Release != listenData.Name) ? $"{listenData.Release} ({listenData.Artist})" : listenData.Artist,
Buttons = buttons.ToArray(),
Assets = new Assets()
{
SmallImageKey = "play",
LargeImageKey = albumArtUrl,
LargeImageText = $"Listening to {listenData.Name} by {listenData.Artist}{(listenData.AdditionalInfo?.MediaPlayer != null ? " (via " + listenData.AdditionalInfo.MediaPlayer + ")" : "")}"
}
});
}
else
{
Console.WriteLine("Not listening to anything");
client.ClearPresence();
}
}
}
else
{
Console.WriteLine("Not listening to anything");
client.ClearPresence();
}
var interval = int.Parse(config_data["general"]["poll_interval"]);
while (interval > 0 && running)
{
Thread.Sleep(1000);
interval--;
}
}
}
private ISearchResult<IRelease>? FindMusicBrainzRelease(string release)
{
Console.WriteLine("Searching for release {0} on MusicBrainz", release);
Query q = new("LBZ-DRPC", new Version(1, 0, 0));
var results = q.FindReleases(release, 5).Results;
if (results != null && results.Any())
{
// Find the result with the exact release title
var exactMatch = results.FirstOrDefault(r => EqualsAlphanumeric(r.Item.Title, release));
if (exactMatch != null)
{
Console.WriteLine("Found match (alphanumeric): {0}", release);
return exactMatch;
}
// If exact match not found, return the result with the highest score
var highestScore = results.OrderByDescending(r => r.Score).FirstOrDefault();
Console.WriteLine("Found match (highest scoring): {0}", release);
return highestScore;
}
return null;
}
private bool EqualsAlphanumeric(string? a, string b)
{
return a?.Where(b => char.IsLetterOrDigit(b) || char.IsWhiteSpace(b)).SequenceEqual(b.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c))) == true;
}
private string? GetYoutubeId(Uri originUrl)
{
if (originUrl.ToString().Contains("youtu.be"))
{
return originUrl.Segments.Last();
}
else
{
var query = HttpUtility.ParseQueryString(originUrl.Query);
if (query["v"] != null)
{
return query["v"];
}
}
return null;
}
}
internal static class ConfigureService
{
internal static void Configure()
{
HostFactory.Run(configure =>
{
configure.Service<LbzRpcService>(service =>
{
service.ConstructUsing(s => new LbzRpcService());
service.WhenStarted(s => s.Start());
service.WhenStopped(s => s.Stop());
});
//Setup Account that window service use to run.
configure.RunAsLocalSystem();
configure.SetServiceName("ListenBrainzDiscordRpc");
configure.SetDisplayName("ListenBrainz Discord Rich Presence");
configure.SetDescription("Updates your Discord Rich Presence with your currently playing track on ListenBrainz.");
});
}
}
public class Program
{
public static void Main(string[] args)
{
ConfigureService.Configure();
}
}