Skip to content

Commit

Permalink
Generate images for playing, queueing, and viewing tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
Azorant committed May 18, 2024
1 parent ed717a7 commit 5a05599
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 57 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,5 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

.idea/
.idea/
Rhea/tmp.png
14 changes: 14 additions & 0 deletions Rhea/Models/TrackMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Rhea.Models;

public class TrackMetadata
{

public string? ArtworkUri { get; set; }
public required string Title { get; set; }
public required string Artist { get; set; }
public required TimeSpan Duration { get; set; }
public required bool Livestream { get; set; }
public TimeSpan? CurrentPosition { get; set; }
public int? QueuePosition { get; set; }
public TimeSpan? TimeToPlay { get; set; }
}
10 changes: 5 additions & 5 deletions Rhea/Modules/BaseModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public class BaseModule(IAudioService lavalink) : InteractionModuleBase<SocketIn
return result.Player;
}

protected string FormatTime(TimeSpan time)
public static string FormatTime(TimeSpan time)
=> time.ToString(@"hh\:mm\:ss").TrimStart('0', ':');

protected bool IsPrivileged(SocketGuildUser Member)
=> Member.GetPermissions(Member.VoiceChannel).MoveMembers ||
Member.Roles.FirstOrDefault(role => role.Name.ToLower() == "dj") != null ||
!Member.VoiceChannel.ConnectedUsers.Any(user => !user.IsBot && user.Id != Member.Id);
protected static bool IsPrivileged(SocketGuildUser member)
=> member.GetPermissions(member.VoiceChannel).MoveMembers ||
member.Roles.FirstOrDefault(role => role.Name.ToLower() == "dj") != null ||
!member.VoiceChannel.ConnectedUsers.Any(user => !user.IsBot && user.Id != member.Id);
}
7 changes: 4 additions & 3 deletions Rhea/Modules/ControlsModule.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using Discord.Interactions;
using Discord;
using Discord.Interactions;
using Lavalink4NET;
using Lavalink4NET.Players;
using Lavalink4NET.Players.Queued;
using Lavalink4NET.Players.Vote;
using Rhea.Services;

namespace Rhea.Modules;

[CommandContextType(InteractionContextType.Guild)]
public class ControlsModule(IAudioService lavalink) : BaseModule(lavalink)
{
[SlashCommand("resume", "Resume playing")]
Expand Down Expand Up @@ -216,7 +217,7 @@ public async Task SeekCommand([Summary(description: "The timestamp to seek to")]
// TODO: Investigate why queue loop isn't working as intended
[SlashCommand("loop", "Set whether or not the queue should loop")]
public async Task LoopCommand(
[Summary(description: "Loop mode"), Choice("Disable", (int)TrackRepeatMode.None), Choice("Track", (int)TrackRepeatMode.Track)]
[Summary(description: "Loop mode"), Choice("Disable", (int)TrackRepeatMode.None), Choice("Track", (int)TrackRepeatMode.Track), Choice("Queue", (int)TrackRepeatMode.Queue)]
int mode)
{
var member = Context.Guild.GetUser(Context.User.Id);
Expand Down
95 changes: 48 additions & 47 deletions Rhea/Modules/MediaModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
using Lavalink4NET.Rest.Entities.Tracks;
using Rhea.Models;
using Rhea.Services;
using Serilog;

namespace Rhea.Modules;

[CommandContextType(InteractionContextType.Guild)]
public class MediaModule(IAudioService lavalink, SimulatorRadio simulatorRadio) : BaseModule(lavalink)
{
[SlashCommand("play", "Play some music")]
Expand Down Expand Up @@ -69,44 +69,53 @@ public async Task Play([Summary(description: "A search term or url")] string sea
if (player.State is PlayerState.Playing or PlayerState.Paused)
{
var embed = new EmbedBuilder()
.WithAuthor("Queued Track")
.WithTitle(result.Track.Title)
.WithTitle("Queued Track")
.WithUrl(result.Track.Uri!.AbsoluteUri)
.AddField("Channel", result.Track.Author, true)
.AddField("Duration", result.Track.IsLiveStream
? "Live stream"
: FormatTime(result.Track.Duration), true)
.AddField("Time until playing",
FormatTime(new TimeSpan(player.Queue.Sum(t => t.Track!.Duration.Ticks) + player.CurrentTrack!.Duration.Ticks - player.Position!.Value.Position.Ticks)),
true)
.AddField("Queue position", player.Queue.Count + 1)
.WithImageUrl("attachment://cover.png")
.WithColor(Color.Blue)
.WithFooter(DiscordClientHost.DisplayName(Context.User), Context.User.GetAvatarUrl());

if (result.Track.ArtworkUri != null) embed.WithThumbnailUrl(result.Track.ArtworkUri.AbsoluteUri);
var image = await ImageGenerator.Generate(new TrackMetadata()
{
Artist = result.Track.Author,
ArtworkUri = result.Track.ArtworkUri?.AbsoluteUri,
Title = result.Track.Title,
Duration = result.Track.Duration,
Livestream = result.Track.IsLiveStream,
QueuePosition = player.Queue.Count + 1,
TimeToPlay = new TimeSpan(player.Queue.Sum(t => t.Track!.Duration.Ticks) + player.CurrentTrack!.Duration.Ticks - player.Position!.Value.Position.Ticks)
});

await player.Queue.AddAsync(result);

await ModifyOriginalResponseAsync(m => m.Embed = embed.Build());
await ModifyOriginalResponseAsync(m =>
{
m.Embed = embed.Build();
m.Attachments = new List<FileAttachment>() { image };
});
}
else
{
var embed = new EmbedBuilder()
.WithAuthor("Now Playing")
.WithTitle(result.Track.Title)
.WithTitle("Now Playing")
.WithUrl(result.Track.Uri!.AbsoluteUri)
.AddField("Channel", result.Track.Author, true)
.AddField("Duration", result.Track.IsLiveStream
? "Live stream"
: FormatTime(result.Track.Duration), true)
.WithImageUrl("attachment://cover.png")
.WithColor(Color.Green)
.WithFooter(DiscordClientHost.DisplayName(Context.User), Context.User.GetAvatarUrl());

if (result.Track.ArtworkUri != null) embed.WithThumbnailUrl(result.Track.ArtworkUri.AbsoluteUri);
var image = await ImageGenerator.Generate(new TrackMetadata()
{
Artist = result.Track.Author,
ArtworkUri = result.Track.ArtworkUri?.AbsoluteUri,
Title = result.Track.Title,
Duration = result.Track.Duration,
Livestream = result.Track.IsLiveStream
});

await player.PlayAsync(result);

await ModifyOriginalResponseAsync(properties => properties.Embed = embed.Build());
await ModifyOriginalResponseAsync(m =>
{
m.Embed = embed.Build();
m.Attachments = new List<FileAttachment>() { image };
});
}
}

Expand Down Expand Up @@ -193,30 +202,22 @@ public async Task NowPlayingCommand()
}
else
{
var bar = "";

if (player.CurrentTrack!.IsLiveStream)
{
bar = "Live stream";
}
else
{
var progress = (int)Math.Floor((decimal)player.Position!.Value.Position.Ticks / player.CurrentTrack!.Duration.Ticks * 100 / 4);
if (progress - 1 > 0) bar += new string('▬', progress - 1);
bar += "🔘";
bar += new string('▬', 25 - progress);
bar += $"\n\n{FormatTime(player.Position!.Value.Position)} / {FormatTime(player.CurrentTrack.Duration)}";
}

var embed = new EmbedBuilder()
.WithTitle("Currently playing")
.WithDescription(
$"[{player.CurrentTrack.Title}]({player.CurrentTrack.Uri})\n\n{bar}")
.WithColor(Color.Blue);

if (player.CurrentTrack.ArtworkUri != null) embed.WithThumbnailUrl(player.CurrentTrack.ArtworkUri.AbsoluteUri);

await RespondAsync(embed: embed.Build());
.WithTitle("Currently Playing")
.WithUrl(player.CurrentTrack!.Uri!.AbsoluteUri)
.WithImageUrl("attachment://cover.png")
.WithColor(Color.Blue)
.WithFooter(DiscordClientHost.DisplayName(Context.User), Context.User.GetAvatarUrl());
var image = await ImageGenerator.Generate(new TrackMetadata()
{
Artist = player.CurrentTrack.Author,
ArtworkUri = player.CurrentTrack.ArtworkUri?.AbsoluteUri,
Title = player.CurrentTrack.Title,
Duration = player.CurrentTrack.Duration,
Livestream = player.CurrentTrack.IsLiveStream,
CurrentPosition = player.Position!.Value.Position
});
await RespondWithFileAsync(attachment: image, embed: embed.Build());
}
}

Expand Down
5 changes: 4 additions & 1 deletion Rhea/Modules/MiscModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Discord;
using Discord.Interactions;
using Lavalink4NET;
using Rhea.Models;
using Rhea.Services;

namespace Rhea.Modules;
Expand All @@ -20,7 +21,9 @@ public async Task AboutCommand()
.AddField("Library", $"Discord.Net {library.Version!.ToString()}", true)
.AddField("Players", lavalink.Players.Players.Count().ToString("N0"), true)
.AddField("Developer", $"{DiscordClientHost.DisplayName(korrdyn)}", true)
.AddField("Links", $"[GitHub](https://github.com/Korrdyn/Rhea)\n[Support](https://discord.gg/{Environment.GetEnvironmentVariable("DISCORD_INVITE")})\n[Patreon](https://patreon.com/Korrdyn)", true)
.AddField("Links",
$"[GitHub](https://github.com/Korrdyn/Rhea)\n[Support](https://discord.gg/{Environment.GetEnvironmentVariable("DISCORD_INVITE")})\n[Patreon](https://patreon.com/Korrdyn)",
true)
.WithColor(Color.Blue)
.WithCurrentTimestamp()
.WithFooter(DiscordClientHost.DisplayName(Context.User), Context.User.GetAvatarUrl())
Expand Down
Binary file added Rhea/Resources/placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions Rhea/Rhea.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.3" />
<PackageReference Include="Websocket.Client" Version="5.1.1" />
</ItemGroup>

<ItemGroup>
<None Update="Resources\placeholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading

0 comments on commit 5a05599

Please sign in to comment.