diff --git a/.gitignore b/.gitignore index 68b37b3..7e701e3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .vs bin appsettings.json +ollama +plugins diff --git a/Bot/Handler/MessageReceivedHandler.cs b/Bot/Handler/MessageReceivedHandler.cs index 65d36ee..9055573 100644 --- a/Bot/Handler/MessageReceivedHandler.cs +++ b/Bot/Handler/MessageReceivedHandler.cs @@ -1,4 +1,6 @@ +using System.Text; using System.Text.RegularExpressions; +using Discord; using Discord.WebSocket; using Lunaris2.Notification; using MediatR; @@ -19,13 +21,41 @@ public class MessageReceivedHandler : INotificationHandler guild.Name); + var channels = _client.Guilds + .SelectMany(guild => guild.VoiceChannels) + .Where(channel => channel.Users.Any(user => user.IsBot)); + + var table = new StringBuilder(); + var serverColumnWidth = 25; // Width for server column + var channelColumnWidth = 25; // Width for channel column + table.AppendLine($"{"Servers".PadRight(serverColumnWidth - 1)}|{"Channels".PadRight(channelColumnWidth - 1)}"); + table.AppendLine($"{new string('-', serverColumnWidth - 1)}|{new string('-', channelColumnWidth - 1)}"); + foreach (var (server, channel) in servers.Zip(channels)) + { + table.AppendLine($"{server.PadRight(serverColumnWidth - 1)}|{channel.Name.PadRight(channelColumnWidth - 1)}"); + } + + var embed = new EmbedBuilder() + .WithTitle("Lunaris Statistics") + .WithDescription(table.ToString()) + .Build(); + + await notification.Message.Channel.SendMessageAsync(embed: embed); + } } private async Task BotMentioned(MessageReceivedNotification notification, CancellationToken cancellationToken) { if (notification.Message.MentionedUsers.Any(user => user.Id == _client.CurrentUser.Id)) { - // The bot was mentioned const string pattern = "<.*?>"; const string replacement = ""; var regex = new Regex(pattern); diff --git a/Bot/Handler/MusicPlayer/ClearQueueCommand/ClearQueueHandler.cs b/Bot/Handler/MusicPlayer/ClearQueueCommand/ClearQueueHandler.cs new file mode 100644 index 0000000..05119cc --- /dev/null +++ b/Bot/Handler/MusicPlayer/ClearQueueCommand/ClearQueueHandler.cs @@ -0,0 +1,22 @@ +using Discord.WebSocket; +using Lavalink4NET; +using MediatR; + +namespace Lunaris2.Handler.MusicPlayer.ClearQueueCommand; + +public record ClearQueueCommand(SocketSlashCommand Message) : IRequest; + +public class DisconnectHandler(DiscordSocketClient client, IAudioService audioService) : IRequestHandler +{ + public async Task Handle(ClearQueueCommand command, CancellationToken cancellationToken) + { + var context = command.Message; + var player = await audioService.GetPlayerAsync(client, context, connectToVoiceChannel: true); + + if (player is null) + return; + + await player.Queue.ClearAsync(cancellationToken).ConfigureAwait(false); + await context.SendMessageAsync("Cleared queue. No songs are queued.", client).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/Bot/Handler/MusicPlayer/DisconnectCommand/DisconnectHandler.cs b/Bot/Handler/MusicPlayer/DisconnectCommand/DisconnectHandler.cs index 19459be..b45243d 100644 --- a/Bot/Handler/MusicPlayer/DisconnectCommand/DisconnectHandler.cs +++ b/Bot/Handler/MusicPlayer/DisconnectCommand/DisconnectHandler.cs @@ -17,6 +17,6 @@ public class DisconnectHandler(DiscordSocketClient client, IAudioService audioSe return; await player.DisconnectAsync(cancellationToken).ConfigureAwait(false); - await context.RespondAsync("Disconnected.").ConfigureAwait(false); + await context.SendMessageAsync("Disconnected.", client).ConfigureAwait(false); } } \ No newline at end of file diff --git a/Bot/Handler/MusicPlayer/MessageModule.cs b/Bot/Handler/MusicPlayer/MessageModule.cs index 5aa1e78..8bd0d59 100644 --- a/Bot/Handler/MusicPlayer/MessageModule.cs +++ b/Bot/Handler/MusicPlayer/MessageModule.cs @@ -1,4 +1,6 @@ +using System.Net; using Discord; +using Discord.Net; using Discord.WebSocket; namespace Lunaris2.Handler.MusicPlayer; @@ -65,20 +67,35 @@ public static class MessageModule if (GuildMessageIds.TryGetValue(guildId, out var value)) { - if (value.Count <= 0) + if (value.Count <= 0) return guildId; - - foreach (var messageId in value) + + // Create a copy of the list to avoid modifying it during iteration + var messagesToDelete = new List(value); + + foreach (var messageId in messagesToDelete) { - var messageToDelete = await context.Channel.GetMessageAsync(messageId); - if (messageToDelete != null) - await messageToDelete.DeleteAsync(); + try + { + var messageToDelete = await context.Channel.GetMessageAsync(messageId); + if (messageToDelete != null) + { + await messageToDelete.DeleteAsync(); + } + } + catch (HttpException ex) + { + if (ex.HttpCode != HttpStatusCode.NotFound) + throw; + } } + // Clear the list after we're done with the iteration value.Clear(); } else { + // If the guildId does not exist, add it to the dictionary GuildMessageIds.Add(guildId, new List()); } diff --git a/Bot/Handler/MusicPlayer/MusicEmbed.cs b/Bot/Handler/MusicPlayer/MusicEmbed.cs index 10d9570..17aec98 100644 --- a/Bot/Handler/MusicPlayer/MusicEmbed.cs +++ b/Bot/Handler/MusicPlayer/MusicEmbed.cs @@ -1,5 +1,6 @@ using Discord; using Discord.WebSocket; +using Lavalink4NET.Players.Queued; using Lavalink4NET.Tracks; namespace Lunaris2.Handler.MusicPlayer; @@ -11,30 +12,38 @@ public class MusicEmbed string title, string length, string artist, - string queuedBy) + string queuedBy, + string? nextSong = null) { + var getNextSong = nextSong is not null ? $"\nNext Song: {nextSong}" : string.Empty; + return new EmbedBuilder() .WithAuthor("Lunaris", "https://media.tenor.com/GqAwMt01UXgAAAAi/cd.gif") .WithTitle(title) - .WithDescription($"Length: {length}\nArtist: {artist}\nQueued by: {queuedBy}") + .WithDescription($"Length: {length}\nArtist: {artist}\nQueued by: {queuedBy}{getNextSong}") .WithColor(Color.Magenta) .WithThumbnailUrl(imageUrl) .Build(); } public async Task NowPlayingEmbed( - LavalinkTrack player, + LavalinkTrack track, SocketSlashCommand context, - DiscordSocketClient client) + DiscordSocketClient client, + ITrackQueue? queue = null) { - var artwork = player.ArtworkUri; + var duration = TimeSpan.Parse(track.Duration.ToString()); + + var artwork = track.ArtworkUri; + var nextSong = queue?.Count > 1 ? queue[1].Track?.Title : null; var embed = SendMusicEmbed( artwork.ToString(), - player.Title, - player.Duration.ToString(), - player.Author, - context.User.Username); - + track.Title, + duration.ToString(@"hh\:mm\:ss"), + track.Author, + context.User.Username, + nextSong); + await context.SendMessageAsync(embed, client); } } diff --git a/Bot/Handler/MusicPlayer/PlayCommand/NormalizationFilter.cs b/Bot/Handler/MusicPlayer/PlayCommand/NormalizationFilter.cs new file mode 100644 index 0000000..886cccd --- /dev/null +++ b/Bot/Handler/MusicPlayer/PlayCommand/NormalizationFilter.cs @@ -0,0 +1,30 @@ +using System.Text.Json; +using Lavalink4NET.Filters; +using Lavalink4NET.Protocol.Models.Filters; + +namespace Lunaris2.Handler.MusicPlayer.PlayCommand; + +public class NormalizationFilter : IFilterOptions +{ + private double MaxAmplitude { get; set; } + private bool Adaptive { get; set; } + + public NormalizationFilter(double maxAmplitude, bool adaptive) + { + MaxAmplitude = maxAmplitude; + Adaptive = adaptive; + } + + public bool IsDefault => MaxAmplitude == 1.0 && !Adaptive; + + public void Apply(ref PlayerFilterMapModel filterMap) + { + filterMap.AdditionalFilters ??= new Dictionary(); + var normalizationFilter = new + { + maxAmplitude = MaxAmplitude, + adaptive = Adaptive + }; + filterMap.AdditionalFilters["normalization"] = JsonSerializer.SerializeToElement(normalizationFilter); + } +} \ No newline at end of file diff --git a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs index 3f48676..fba2423 100644 --- a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs +++ b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs @@ -1,10 +1,14 @@ +using System.Collections.Immutable; using Discord.WebSocket; using Lunaris2.SlashCommand; using MediatR; using Lavalink4NET; using Lavalink4NET.Events.Players; +using Lavalink4NET.Integrations.SponsorBlock; +using Lavalink4NET.Integrations.SponsorBlock.Extensions; using Lavalink4NET.Players.Queued; using Lavalink4NET.Rest.Entities.Tracks; +using Lavalink4NET.Tracks; namespace Lunaris2.Handler.MusicPlayer.PlayCommand; @@ -17,6 +21,8 @@ public class PlayHandler : IRequestHandler private readonly IAudioService _audioService; private SocketSlashCommand _context; private const int MaxTrackDuration = 30; + private LavalinkTrack? _previousTrack; + private static readonly HashSet SubscribedGuilds = new(); public PlayHandler( DiscordSocketClient client, @@ -26,18 +32,37 @@ public class PlayHandler : IRequestHandler _client = client; _musicEmbed = musicEmbed; _audioService = audioService; - _audioService.TrackStarted += OnTrackStarted; + } + + private async Task OnTrackEnded(object sender, TrackEndedEventArgs eventargs) + { + // Reset the previous track when the track ends. + _previousTrack = null; } private async Task OnTrackStarted(object sender, TrackStartedEventArgs eventargs) { var player = sender as QueuedLavalinkPlayer; - var track = player?.CurrentTrack; - if (track != null) - await _musicEmbed.NowPlayingEmbed(track, _context, _client); + if (player?.CurrentTrack is null) + { + return; // No track is currently playing. + } + + var currentTrack = player.CurrentTrack; + + // Check if the current track is the same as the previous one + if (_previousTrack?.Identifier == currentTrack.Identifier) + { + // The same track is playing, so we don't need to create a new embed. + return; + } + + // Track has changed, update the previous track and send the embed + _previousTrack = currentTrack; + await _musicEmbed.NowPlayingEmbed(currentTrack, _context, _client); } - + public Task Handle(PlayCommand command, CancellationToken cancellationToken) { new Thread(PlayMusic).Start(); @@ -47,6 +72,8 @@ public class PlayHandler : IRequestHandler { try { + RegisterTrackStartedEventListerner(command); + await _audioService.StartAsync(cancellationToken); var context = command.Message; @@ -69,38 +96,66 @@ public class PlayHandler : IRequestHandler await context.SendMessageAsync("📻 Searching...", _client); var player = await _audioService.GetPlayerAsync(_client, context, connectToVoiceChannel: true); - + if (player is null) return; - - var trackLoadOptions = new TrackLoadOptions { SearchMode = TrackSearchMode.YouTube, }; - - var track = await _audioService.Tracks.LoadTrackAsync(searchQuery, trackLoadOptions, cancellationToken: cancellationToken); - - if (track is null) - { - await context.SendMessageAsync("😖 No results.", _client); - return; - } - if (player.CurrentTrack?.Duration.TotalMinutes > MaxTrackDuration) - { - await context.SendMessageAsync($"🔈 Sorry the track is longer than { MaxTrackDuration } minutes, to save resources this limit is active for now.", _client); - return; - } + await ApplyFilters(cancellationToken, player); + await ConfigureSponsorBlock(cancellationToken, player); - if (player.CurrentTrack is null) - { - await player.PlayAsync(track, cancellationToken: cancellationToken) - .ConfigureAwait(false); + var trackLoadOptions = new TrackLoadOptions + { + SearchMode = TrackSearchMode.YouTube, + }; - await _musicEmbed.NowPlayingEmbed(track, context, _client); + var trackCollection = await _audioService.Tracks.LoadTracksAsync(searchQuery, trackLoadOptions, cancellationToken: cancellationToken); + + // Check if the result is a playlist or just a single track from the search result + if (trackCollection.IsPlaylist) + { + // If it's a playlist, check if it's a free-text input. + if (!Uri.IsWellFormedUriString(searchQuery, UriKind.Absolute)) + { + // Free text was used (not a direct URL to a playlist), let's prevent queueing the whole playlist. + // Queue only the first track of the search result + // var firstTrack = trackCollection.Tracks.FirstOrDefault(); + if (trackCollection.Track != null) + { + await player.PlayAsync(trackCollection.Track, cancellationToken: cancellationToken).ConfigureAwait(false); + await _musicEmbed.NowPlayingEmbed(trackCollection.Track, _context, _client); + } + else + { + await context.SendMessageAsync("No tracks found.", _client); + } + } + else + { + // It's a playlist and a URL was used, so we can queue all tracks as usual + var queueTracks = trackCollection.Tracks + .Skip(1) + .Select(t => new TrackQueueItem(t)) + .ToList(); + + await player.PlayAsync(trackCollection.Tracks.First(), cancellationToken: cancellationToken).ConfigureAwait(false); + await player.Queue.AddRangeAsync(queueTracks, cancellationToken); + await context.SendMessageAsync($"Queued playlist {trackCollection.Playlist?.Name} with {queueTracks.Count} tracks.", _client); + } } else { - var queueTracks = new[] { new TrackQueueItem(track) }; - await player.Queue.AddRangeAsync(queueTracks, cancellationToken); - await context.SendMessageAsync($"🔈 Added to queue: {track.Title}", _client); + // It's just a single track or a search result. + var track = trackCollection.Tracks.FirstOrDefault(); + + if (track != null) + { + await player.PlayAsync(track, cancellationToken: cancellationToken).ConfigureAwait(false); + await _musicEmbed.NowPlayingEmbed(track, _context, _client); + } + else + { + await context.SendMessageAsync("No tracks found.", _client); + } } } catch (Exception error) @@ -109,4 +164,33 @@ public class PlayHandler : IRequestHandler } } } + + private void RegisterTrackStartedEventListerner(PlayCommand command) + { + if (SubscribedGuilds.Contains((ulong)command.Message.GuildId!)) + return; + + _audioService.TrackStarted += OnTrackStarted; + _audioService.TrackEnded += OnTrackEnded; + SubscribedGuilds.Add((ulong)command.Message.GuildId!); + } + + private static async Task ApplyFilters(CancellationToken cancellationToken, QueuedLavalinkPlayer player) + { + var normalizationFilter = new NormalizationFilter(0.5, true); + player.Filters.SetFilter(normalizationFilter); + await player.Filters.CommitAsync(cancellationToken); + } + + private static async Task ConfigureSponsorBlock(CancellationToken cancellationToken, QueuedLavalinkPlayer player) + { + var categories = ImmutableArray.Create( + SegmentCategory.Intro, + SegmentCategory.Sponsor, + SegmentCategory.SelfPromotion, + SegmentCategory.Outro, + SegmentCategory.Filler); + + await player.UpdateSponsorBlockCategoriesAsync(categories, cancellationToken: cancellationToken); + } } \ No newline at end of file diff --git a/Bot/Handler/SlashCommandReceivedHandler.cs b/Bot/Handler/SlashCommandReceivedHandler.cs index d77ebd6..79dd137 100644 --- a/Bot/Handler/SlashCommandReceivedHandler.cs +++ b/Bot/Handler/SlashCommandReceivedHandler.cs @@ -1,3 +1,4 @@ +using Lunaris2.Handler.MusicPlayer.ClearQueueCommand; using Lunaris2.Handler.MusicPlayer.DisconnectCommand; using Lunaris2.Handler.MusicPlayer.PauseCommand; using Lunaris2.Handler.MusicPlayer.PlayCommand; @@ -32,6 +33,9 @@ public class SlashCommandReceivedHandler(ISender mediator) : INotificationHandle case Command.Skip.Name: await mediator.Send(new SkipCommand(notification.Message), cancellationToken); break; + case Command.Clear.Name: + await mediator.Send(new ClearQueueCommand(notification.Message), cancellationToken); + break; } } } \ No newline at end of file diff --git a/Bot/Lunaris2.csproj b/Bot/Lunaris2.csproj index a3e34c6..d094e83 100644 --- a/Bot/Lunaris2.csproj +++ b/Bot/Lunaris2.csproj @@ -8,26 +8,31 @@ ec2f340f-a44c-4869-ab79-a12ba9459d80 + - - - - - - - - - + + + + + + + + + + + - - - - + + + + - + + + Always diff --git a/Bot/Program.cs b/Bot/Program.cs index 79d1f77..9204bf5 100644 --- a/Bot/Program.cs +++ b/Bot/Program.cs @@ -4,6 +4,7 @@ using Discord.Interactions; using Discord.WebSocket; using Lunaris2.Handler.ChatCommand; using Lavalink4NET.Extensions; +using Lavalink4NET.Integrations.SponsorBlock.Extensions; using Lunaris2.Handler.MusicPlayer; using Lunaris2.Notification; using Lunaris2.Service; @@ -11,7 +12,6 @@ using Lunaris2.SlashCommand; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Victoria.Node; namespace Lunaris2; @@ -23,7 +23,10 @@ public class Program { Console.WriteLine(eventArgs.ExceptionObject); }; - CreateHostBuilder(args).Build().Run(); + var app = CreateHostBuilder(args).Build(); + + app.UseSponsorBlock(); + app.Run(); } private static IHostBuilder CreateHostBuilder(string[] args) => @@ -53,7 +56,6 @@ public class Program options.Passphrase = configuration["LavaLinkPassword"] ?? "youshallnotpass"; options.Label = "Node"; }) - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(client) diff --git a/Bot/Service/VoiceChannelMonitorService.cs b/Bot/Service/VoiceChannelMonitorService.cs index 9bacaee..78e997b 100644 --- a/Bot/Service/VoiceChannelMonitorService.cs +++ b/Bot/Service/VoiceChannelMonitorService.cs @@ -1,59 +1,71 @@ using Discord.WebSocket; -namespace Lunaris2.Service; - -public class VoiceChannelMonitorService +namespace Lunaris2.Service { - private readonly DiscordSocketClient _client; - private readonly Dictionary _timers = new(); - - public VoiceChannelMonitorService(DiscordSocketClient client) + public class VoiceChannelMonitorService { - _client = client; - } + private readonly DiscordSocketClient _client; + private readonly Dictionary _timers = new(); - public void StartMonitoring() - { - Task.Run(async () => + public VoiceChannelMonitorService(DiscordSocketClient client) { - while (true) - { - await CheckVoiceChannels(); - await Task.Delay(TimeSpan.FromMinutes(1)); - } - }); - } + _client = client; + } - private async Task CheckVoiceChannels() - { - foreach (var guild in _client.Guilds) + public void StartMonitoring() { - var voiceChannel = guild.VoiceChannels.FirstOrDefault(vc => vc.ConnectedUsers.Count == 1); - if (voiceChannel != null) + Task.Run(async () => { - if (!_timers.ContainsKey(voiceChannel.Id)) + while (true) { - _timers[voiceChannel.Id] = new Timer(async _ => await LeaveChannel(voiceChannel), null, TimeSpan.FromMinutes(3), Timeout.InfiniteTimeSpan); + await CheckVoiceChannels(); + await Task.Delay(TimeSpan.FromMinutes(1)); // Monitor every minute + } + }); + } + + private async Task CheckVoiceChannels() + { + foreach (var guild in _client.Guilds) + { + // Find voice channels where only the bot is left + var voiceChannel = guild.VoiceChannels.FirstOrDefault(vc => + vc.ConnectedUsers.Count == 1 && + vc.Users.Any(u => u.Id == _client.CurrentUser.Id)); + + if (voiceChannel != null) + { + // If timer not set for this channel, start one + if (!_timers.ContainsKey(voiceChannel.Id)) + { + Console.WriteLine($"Bot is alone in channel {voiceChannel.Name}, starting timer..."); + _timers[voiceChannel.Id] = new Timer(async _ => await LeaveChannel(voiceChannel), null, + TimeSpan.FromMinutes(3), Timeout.InfiniteTimeSpan); // Set delay before leaving + } + } + else + { + // Clean up timer if channel is no longer active + var timersToDispose = _timers.Where(t => guild.VoiceChannels.All(vc => vc.Id != t.Key)).ToList(); + foreach (var timer in timersToDispose) + { + await timer.Value.DisposeAsync(); + _timers.Remove(timer.Key); + Console.WriteLine($"Disposed timer for inactive voice channel ID: {timer.Key}"); + } } } - else + } + + private async Task LeaveChannel(SocketVoiceChannel voiceChannel) + { + if (voiceChannel.ConnectedUsers.Count == 1 && voiceChannel.Users.Any(u => u.Id == _client.CurrentUser.Id)) { - if (voiceChannel == null || !_timers.ContainsKey(voiceChannel.Id)) - continue; - + Console.WriteLine($"Leaving channel {voiceChannel.Name} due to inactivity..."); + await voiceChannel.DisconnectAsync(); await _timers[voiceChannel.Id].DisposeAsync(); - _timers.Remove(voiceChannel.Id); + _timers.Remove(voiceChannel.Id); // Clean up after leaving } } } - - private async Task LeaveChannel(SocketVoiceChannel voiceChannel) - { - if (voiceChannel.ConnectedUsers.Count == 1 && voiceChannel.Users.Any(u => u.Id == _client.CurrentUser.Id)) - { - await voiceChannel.DisconnectAsync(); - await _timers[voiceChannel.Id].DisposeAsync(); - _timers.Remove(voiceChannel.Id); - } - } -} \ No newline at end of file +} diff --git a/Bot/SlashCommand/Command.cs b/Bot/SlashCommand/Command.cs index 979385f..99d9f04 100644 --- a/Bot/SlashCommand/Command.cs +++ b/Bot/SlashCommand/Command.cs @@ -15,6 +15,12 @@ public static class Command public const string Description = "Disconnect from the voice channel!"; } + public static class Clear + { + public const string Name = "clear"; + public const string Description = "Clear the music queue!"; + } + public static class Skip { public const string Name = "skip"; diff --git a/Bot/SlashCommand/SlashCommandRegistration.cs b/Bot/SlashCommand/SlashCommandRegistration.cs index 5dfa15a..2c20b02 100644 --- a/Bot/SlashCommand/SlashCommandRegistration.cs +++ b/Bot/SlashCommand/SlashCommandRegistration.cs @@ -13,6 +13,7 @@ public static class SlashCommandRegistration RegisterCommand(client, Command.Skip.Name, Command.Skip.Description); RegisterCommand(client, Command.Play.Name, Command.Play.Description, Command.Play.Options); RegisterCommand(client, Command.Resume.Name, Command.Resume.Description); + RegisterCommand(client, Command.Clear.Name, Command.Clear.Description); } private static void RegisterCommand( diff --git a/Bot/generate-trusted-session.sh b/Bot/generate-trusted-session.sh new file mode 100644 index 0000000..69f441b --- /dev/null +++ b/Bot/generate-trusted-session.sh @@ -0,0 +1,2 @@ +docker run quay.io/invidious/youtube-trusted-session-generator +read -p "Copy the codes and press enter to close the terminal." \ No newline at end of file diff --git a/application.yml b/application.yml index ac658fa..66e0603 100644 --- a/application.yml +++ b/application.yml @@ -2,7 +2,45 @@ server: # REST and WS server port: 2333 address: 0.0.0.0 plugins: + lavasrc: + providers: # Custom providers for track loading. This is the default + # - "dzisrc:%ISRC%" # Deezer ISRC provider + # - "dzsearch:%QUERY%" # Deezer search provider + - "ytsearch:\"%ISRC%\"" # Will be ignored if track does not have an ISRC. See https://en.wikipedia.org/wiki/International_Standard_Recording_Code + - "ytsearch:%QUERY%" # Will be used if track has no ISRC or no track could be found for the ISRC + # you can add multiple other fallback sources here + sources: + spotify: true # Enable Spotify source + applemusic: false # Enable Apple Music source + deezer: false # Enable Deezer source + yandexmusic: false # Enable Yandex Music source + flowerytts: false # Enable Flowery TTS source + youtube: false # Enable YouTube search source (https://github.com/topi314/LavaSearch) + vkmusic: false # Enable Vk Music source + spotify: + clientId: "ID" + clientSecret: "SECRET" + # spDc: "your sp dc cookie" # the sp dc cookie used for accessing the spotify lyrics api + countryCode: "US" # the country code you want to use for filtering the artists top tracks. See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + playlistLoadLimit: 6 # The number of pages at 100 tracks each + albumLoadLimit: 6 # The number of pages at 50 tracks each + resolveArtistsInSearch: true # Whether to resolve artists in track search results (can be slow) + localFiles: false # Enable local files support with Spotify playlists. Please note `uri` & `isrc` will be `null` & `identifier` will be `"local"` youtube: + oauth: + # setting "enabled: true" is the bare minimum to get OAuth working. + enabled: true + + # if you have a refresh token, you may set it below (make sure to uncomment the line to apply it). + # setting a valid refresh token will skip the OAuth flow entirely. See above note on how to retrieve + # your refreshToken. + # Set this if you don't want the OAuth flow to be triggered, if you intend to supply a refresh token later. + # Initialization is skipped automatically if a valid refresh token is supplied. Leave this commented if you're + # completing the OAuth flow for the first time/do not have a refresh token. + # skipInitialization: true + # pot: // run generate-trusted-session.sh + # token: "" + # visitorData: "" enabled: true # Whether this source can be used. allowSearch: true # Whether "ytsearch:" and "ytmsearch:" can be used. allowDirectVideoIds: true # Whether just video IDs can match. If false, only complete URLs will be loaded. @@ -19,11 +57,17 @@ plugins: # another_key: another_value lavalink: plugins: -# - dependency: "group:artifact:version" -# repository: "repository" - - dependency: "dev.lavalink.youtube:youtube-plugin:1.5.2" + - dependency: com.github.devoxin:lavadspx-plugin:0.0.5 # replace {VERSION} with the latest version from the "Releases" tab. + repository: https://jitpack.io + - dependency: "dev.lavalink.youtube:youtube-plugin:1.8.3" snapshot: false # Set to true if you want to use a snapshot version. - + - dependency: "com.github.topi314.lavasearch:lavasearch-plugin:1.0.0" + repository: "https://maven.lavalink.dev/releases" # this is optional for lavalink v4.0.0-beta.5 or greater + snapshot: false # set to true if you want to use snapshot builds (see below) + - dependency: "com.github.topi314.sponsorblock:sponsorblock-plugin:3.0.1" + - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.2.0" + repository: "https://maven.lavalink.dev/releases" # this is optional for lavalink v4.0.0-beta.5 or greater + snapshot: false # set to true if you want to use snapshot builds (see below) server: password: "youshallnotpass" sources: @@ -56,20 +100,6 @@ lavalink: youtubeSearchEnabled: true soundcloudSearchEnabled: true gc-warnings: true - #ratelimit: - #ipBlocks: ["1.0.0.0/8", "..."] # list of ip blocks - #excludedIps: ["...", "..."] # ips which should be explicit excluded from usage by lavalink - #strategy: "RotateOnBan" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch - #searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing - #retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times - #youtubeConfig: # Required for avoiding all age restrictions by YouTube, some restricted videos still can be played without. - #email: "" # Email of Google account - #password: "" # Password of Google account - #httpConfig: # Useful for blocking bad-actors from ip-grabbing your music node and attacking it, this way only the http proxy will be attacked - #proxyHost: "localhost" # Hostname of the proxy, (ip or domain) - #proxyPort: 3128 # Proxy port, 3128 is the default for squidProxy - #proxyUser: "" # Optional user for basic authentication fields, leave blank if you don't use basic auth - #proxyPassword: "" # Password for basic authentication metrics: prometheus: @@ -79,9 +109,6 @@ metrics: sentry: dsn: "" environment: "" -# tags: -# some_key: some_value -# another_key: another_value logging: file: @@ -99,8 +126,7 @@ logging: includePayload: true maxPayloadLength: 10000 - logback: rollingpolicy: max-file-size: 1GB - max-history: 30 + max-history: 30 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 91e0290..0db7e97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: lavalink: # pin the image version to Lavalink v4 - image: ghcr.io/lavalink-devs/lavalink:4.0.7 + image: ghcr.io/lavalink-devs/lavalink:4.0.8 container_name: lavalink restart: unless-stopped environment: diff --git a/plugins/lavasrc-plugin-3.2.10.jar b/plugins/lavasrc-plugin-3.2.10.jar deleted file mode 100644 index 5c8a7d7..0000000 Binary files a/plugins/lavasrc-plugin-3.2.10.jar and /dev/null differ diff --git a/start-services.sh b/start-services.sh index 956fe58..e6fb3f1 100644 --- a/start-services.sh +++ b/start-services.sh @@ -1,3 +1 @@ docker compose up -d - -read -p "Press enter to continue"