2024-06-02 18:42:01 +03:00
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 ;
2024-06-02 18:45:16 +03:00
readonly Thread t ;
2024-06-02 18:42:01 +03:00
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." ) ;
}
2024-06-19 00:40:12 +03:00
IniData config_data = config . ReadFile ( "config.ini" ) ;
2024-06-02 18:42:01 +03:00
listenBrainz = new ListenBrainz ( ) ;
2024-06-19 00:40:12 +03:00
client = new DiscordRpcClient ( config_data [ "general" ] [ "discord_token" ] ) ;
2024-06-02 18:42:01 +03:00
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 ;
}
2024-06-19 00:40:12 +03:00
var username = config_data [ "general" ] [ "listenbrainz_username" ] ;
var ignoredPlayers = config_data [ "general" ] [ "ignored_sources" ] . Split ( "," , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
2024-06-02 18:42:01 +03:00
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 ) ;
2024-06-11 14:32:52 +03:00
albumArtUrl = "https://img.youtube.com/vi/" + youtubeId + "/mqdefault.jpg" ;
2024-06-02 18:42:01 +03:00
}
}
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}" } ) ;
2024-06-19 00:40:12 +03:00
string? newAlbumArt = null ;
if ( config_data [ "general" ] [ "prioritise_musicbrainz" ] = = "true" & & listenData . AdditionalInfo ? . ReleaseId ! = null )
2024-06-11 14:32:52 +03:00
{
2024-06-19 00:40:12 +03:00
newAlbumArt = "https://coverartarchive.org/release/" + listenData . AdditionalInfo ? . ReleaseId + "/front-250.jpg" ;
2024-06-11 14:32:52 +03:00
}
2024-06-19 00:40:12 +03:00
else if ( config_data [ "general" ] [ "prioritise_musicbrainz" ] = = "true" & & listenData . Release ! = null )
2024-06-02 18:42:01 +03:00
{
2024-06-19 00:40:12 +03:00
var release = FindMusicBrainzRelease ( listenData . Release ) ;
newAlbumArt = "https://coverartarchive.org/release/" + release ? . Item . Id + "/front-250.jpg" ;
2024-06-02 18:42:01 +03:00
}
2024-06-19 00:40:12 +03:00
if ( newAlbumArt = = null )
2024-06-02 18:42:01 +03:00
{
2024-06-19 00:40:12 +03:00
if ( ( listenData . AdditionalInfo ? . OriginUrl ? . ToString ( ) . Contains ( "youtube" ) = = true
| | listenData . AdditionalInfo ? . OriginUrl ? . ToString ( ) . Contains ( "youtu.be" ) = = true )
& & GetYoutubeId ( listenData . AdditionalInfo . OriginUrl ) ! = null )
2024-06-02 18:42:01 +03:00
{
2024-06-19 00:40:12 +03:00
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" ;
2024-06-02 18:42:01 +03:00
}
}
2024-06-19 00:40:12 +03:00
Console . WriteLine ( "Album art URL: " + ( newAlbumArt ? ? "(none)" ) ) ;
albumArtUrl = newAlbumArt ? ? "" ;
2024-06-02 18:42:01 +03:00
}
client . SetPresence ( new RichPresence ( )
{
Details = $"{listenData.Name}" ,
2024-06-19 00:40:12 +03:00
State = ( listenData . Release ! = null & & listenData . Release ! = listenData . Name ) ? $"{listenData.Release} ({listenData.Artist})" : listenData . Artist ,
2024-06-02 18:42:01 +03:00
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 ( ) ;
}
2024-06-19 00:40:12 +03:00
var interval = int . Parse ( config_data [ "general" ] [ "poll_interval" ] ) ;
2024-06-02 18:42:01 +03:00
while ( interval > 0 & & running )
{
Thread . Sleep ( 1000 ) ;
interval - - ;
}
}
}
2024-06-19 00:40:12 +03:00
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 ;
}
2024-06-02 18:42:01 +03:00
private string? GetYoutubeId ( Uri originUrl )
{
2024-06-11 14:32:52 +03:00
if ( originUrl . ToString ( ) . Contains ( "youtu.be" ) )
2024-06-02 18:42:01 +03:00
{
2024-06-11 14:32:52 +03:00
return originUrl . Segments . Last ( ) ;
}
else
{
var query = HttpUtility . ParseQueryString ( originUrl . Query ) ;
if ( query [ "v" ] ! = null )
{
return query [ "v" ] ;
}
2024-06-02 18:42:01 +03:00
}
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 ( ) ;
}
}