Compare commits

..

9 Commits

Author SHA1 Message Date
43f0191752 Update readme.md 2024-10-25 21:47:54 +02:00
872b6d3138 Create README.md 2024-10-25 21:44:08 +02:00
f292124228 Update documentation 2024-10-25 21:38:47 +02:00
4cbee9a625 Fix bug with playback & add statuses (#8)
Co-authored-by: Myx <info@azaaxin.com>
2024-10-25 21:06:26 +02:00
b79e56d3a1 Add version to release file 2024-10-25 20:58:19 +02:00
fa19f8d938 Update dotnet.yml 2024-10-24 19:45:28 +02:00
ac869c43da Update dotnet.yml 2024-10-24 19:20:08 +02:00
e2fdd9a2d7 Update dotnet.yml 2024-10-24 19:10:54 +02:00
98761fc91d Update dotnet.yml 2024-10-24 19:10:12 +02:00
9 changed files with 347 additions and 44 deletions

View File

@@ -14,24 +14,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
fetch-depth: 0 # required for github-action-get-previous-tag fetch-depth: 0 # required for github-action-get-previous-tag
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore ./Bot/Lunaris2.csproj
- name: Build
run: dotnet build ./Bot/Lunaris2.csproj --no-restore -c Release -o ./out
- name: Publish
run: dotnet publish ./Bot/Lunaris2.csproj --configuration Release --output ./out
- name: Zip the build
run: 7z a -tzip ./out/Lunaris.zip ./out/*
- name: Get previous tag - name: Get previous tag
id: previoustag id: previoustag
uses: 'WyriHaximus/github-action-get-previous-tag@v1' uses: 'WyriHaximus/github-action-get-previous-tag@v1'
@@ -44,6 +27,23 @@ jobs:
with: with:
version: ${{ steps.previoustag.outputs.tag }} version: ${{ steps.previoustag.outputs.tag }}
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore ./Bot/Lunaris2.csproj
- name: Build
run: dotnet build ./Bot/Lunaris2.csproj --no-restore -c Release /p:AssemblyVersion=${{ steps.previoustag.outputs.tag }} -o ./out
- name: Publish
run: dotnet publish ./Bot/Lunaris2.csproj --configuration Release --output ./out
- name: Zip the build
run: 7z a -tzip ./out/Lunaris.zip ./out/*
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1
@@ -63,5 +63,5 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./out/Lunaris.zip asset_path: ./out/Lunaris.zip
asset_name: Lunaris.zip asset_name: Lunaris_${{steps.semver.outputs.patch}}.zip
asset_content_type: application/zip asset_content_type: application/zip

View File

@@ -31,21 +31,23 @@ public class MessageReceivedHandler : INotificationHandler<MessageReceivedNotifi
var servers = _client.Guilds.Select(guild => guild.Name); var servers = _client.Guilds.Select(guild => guild.Name);
var channels = _client.Guilds var channels = _client.Guilds
.SelectMany(guild => guild.VoiceChannels) .SelectMany(guild => guild.VoiceChannels)
.Where(channel => channel.Users.Any(user => user.IsBot)); .Where(channel => channel.ConnectedUsers.Any(guildUser => guildUser.Id == _client.CurrentUser.Id) &&
channel.Users.Count != 1);
var table = new StringBuilder(); var statsList = new StringBuilder();
var serverColumnWidth = 25; // Width for server column statsList.AppendLine("➡️ Servers");
var channelColumnWidth = 25; // Width for channel column
table.AppendLine($"{"Servers".PadRight(serverColumnWidth - 1)}|{"Channels".PadRight(channelColumnWidth - 1)}"); foreach (var server in servers)
table.AppendLine($"{new string('-', serverColumnWidth - 1)}|{new string('-', channelColumnWidth - 1)}"); statsList.AppendLine($"* {server}");
foreach (var (server, channel) in servers.Zip(channels))
{ statsList.AppendLine("➡️ Now playing channels: ");
table.AppendLine($"{server.PadRight(serverColumnWidth - 1)}|{channel.Name.PadRight(channelColumnWidth - 1)}");
} foreach (var channel in channels)
statsList.AppendLine($"* {channel.Name} in {channel.Guild.Name}");
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithTitle("Lunaris Statistics") .WithTitle("Lunaris Statistics")
.WithDescription(table.ToString()) .WithDescription(statsList.ToString())
.Build(); .Build();
await notification.Message.Channel.SendMessageAsync(embed: embed); await notification.Message.Channel.SendMessageAsync(embed: embed);

View File

@@ -105,7 +105,7 @@ public class PlayHandler : IRequestHandler<PlayCommand>
var trackLoadOptions = new TrackLoadOptions var trackLoadOptions = new TrackLoadOptions
{ {
SearchMode = TrackSearchMode.YouTube, SearchMode = TrackSearchMode.YouTubeMusic,
}; };
var trackCollection = await _audioService.Tracks.LoadTracksAsync(searchQuery, trackLoadOptions, cancellationToken: cancellationToken); var trackCollection = await _audioService.Tracks.LoadTracksAsync(searchQuery, trackLoadOptions, cancellationToken: cancellationToken);
@@ -145,7 +145,7 @@ public class PlayHandler : IRequestHandler<PlayCommand>
else else
{ {
// It's just a single track or a search result. // It's just a single track or a search result.
var track = trackCollection.Tracks.FirstOrDefault(); var track = trackCollection.Track;
if (track != null) if (track != null)
{ {

View File

@@ -8,6 +8,75 @@ flowchart TD
PlayTrack --> NowPlayingEmbed PlayTrack --> NowPlayingEmbed
``` ```
```mermaid
classDiagram
class PlayHandler {
-MusicEmbed _musicEmbed
-DiscordSocketClient _client
-IAudioService _audioService
-SocketSlashCommand _context
-const int MaxTrackDuration
-LavalinkTrack? _previousTrack
-static HashSet~ulong~ SubscribedGuilds
+PlayHandler(DiscordSocketClient client, MusicEmbed musicEmbed, IAudioService audioService)
+Task Handle(PlayCommand command, CancellationToken cancellationToken)
-void PlayMusic()
-Task OnTrackEnded(object sender, TrackEndedEventArgs eventargs)
-Task OnTrackStarted(object sender, TrackStartedEventArgs eventargs)
-void RegisterTrackStartedEventListerner(PlayCommand command)
-static Task ApplyFilters(CancellationToken cancellationToken, QueuedLavalinkPlayer player)
-static Task ConfigureSponsorBlock(CancellationToken cancellationToken, QueuedLavalinkPlayer player)
}
class PlayCommand {
+SocketSlashCommand Message
}
class TrackEndedEventArgs {
}
class TrackStartedEventArgs {
}
class QueuedLavalinkPlayer {
+LavalinkTrack? CurrentTrack
+Task PlayAsync(LavalinkTrack track, CancellationToken cancellationToken)
+Task Queue.AddRangeAsync(List~TrackQueueItem~ queueTracks, CancellationToken cancellationToken)
+Task Filters.SetFilter(NormalizationFilter normalizationFilter)
+Task Filters.CommitAsync(CancellationToken cancellationToken)
+Task UpdateSponsorBlockCategoriesAsync(ImmutableArray~SegmentCategory~ categories, CancellationToken cancellationToken)
}
class LavalinkTrack {
+string Identifier
}
class NormalizationFilter {
+NormalizationFilter(double gain, bool enabled)
}
class SegmentCategory {
+static SegmentCategory Intro
+static SegmentCategory Sponsor
+static SegmentCategory SelfPromotion
+static SegmentCategory Outro
+static SegmentCategory Filler
}
class TrackQueueItem {
+TrackQueueItem(LavalinkTrack track)
}
PlayHandler --> PlayCommand
PlayHandler --> TrackEndedEventArgs
PlayHandler --> TrackStartedEventArgs
PlayHandler --> QueuedLavalinkPlayer
PlayHandler --> LavalinkTrack
PlayHandler --> NormalizationFilter
PlayHandler --> SegmentCategory
PlayHandler --> TrackQueueItem
```
## Steps in the code ## Steps in the code
| Name | Description | | Name | Description |
@@ -32,4 +101,4 @@ There is also OnTrackEnd, when it get called an attempt is made to play the next
| `player` | `LavaPlayer` | An instance of the `LavaPlayer` class, representing a music player connected to a specific voice channel. Used to play, pause, skip, and queue tracks. | | `player` | `LavaPlayer` | An instance of the `LavaPlayer` class, representing a music player connected to a specific voice channel. Used to play, pause, skip, and queue tracks. |
| `guildMessageIds` | `Dictionary<ulong, List<ulong>>` | A dictionary that maps guild IDs to lists of message IDs. Used to keep track of messages sent by the bot in each guild, allowing the bot to delete its old messages when it sends new ones. | | `guildMessageIds` | `Dictionary<ulong, List<ulong>>` | A dictionary that maps guild IDs to lists of message IDs. Used to keep track of messages sent by the bot in each guild, allowing the bot to delete its old messages when it sends new ones. |
| `songName` | `string` | A string that represents the name or URL of a song to play. Used to search for and queue tracks. | | `songName` | `string` | A string that represents the name or URL of a song to play. Used to search for and queue tracks. |
| `searchResponse` | `SearchResponse` | An instance of the `SearchResponse` class, representing the result of a search for tracks. Used to get the tracks that were found and queue them in the player. | | `searchResponse` | `SearchResponse` | An instance of the `SearchResponse` class, representing the result of a search for tracks. Used to get the tracks that were found and queue them in the player. |

View File

@@ -0,0 +1,189 @@
### README.md
# Handlers
Handlers for the Lunaris2 bot, which is built using C#, Discord.Net, and Lavalink4NET. Below is a detailed description of each handler and their responsibilities.
## Handlers
### ClearQueueHandler
Handles the command to clear the music queue.
```csharp
public class ClearQueueHandler : IRequestHandler<ClearQueueCommand>
```
### DisconnectHandler
Handles the command to disconnect the bot from the voice channel.
```csharp
public class DisconnectHandler : IRequestHandler<DisconnectCommand>
```
### PauseHandler
Handles the command to pause the currently playing track.
```csharp
public class PauseHandler : IRequestHandler<PauseCommand>
```
### PlayHandler
Handles the command to play a track or playlist.
```csharp
public class PlayHandler : IRequestHandler<PlayCommand>
```
### ResumeHandler
Handles the command to resume the currently paused track.
```csharp
public class ResumeHandler : IRequestHandler<ResumeCommand>
```
### SkipHandler
Handles the command to skip the currently playing track.
```csharp
public class SkipHandler : IRequestHandler<SkipCommand>
```
### MessageReceivedHandler
Handles incoming messages and processes commands or statistics requests.
```csharp
public class MessageReceivedHandler : INotificationHandler<MessageReceivedNotification>
```
## Mermaid Diagrams
### Class Diagram
```mermaid
classDiagram
class ClearQueueHandler {
+Task Handle(ClearQueueCommand command, CancellationToken cancellationToken)
}
class DisconnectHandler {
+Task Handle(DisconnectCommand command, CancellationToken cancellationToken)
}
class PauseHandler {
+Task Handle(PauseCommand command, CancellationToken cancellationToken)
}
class PlayHandler {
+Task Handle(PlayCommand command, CancellationToken cancellationToken)
}
class ResumeHandler {
+Task Handle(ResumeCommand command, CancellationToken cancellationToken)
}
class SkipHandler {
+Task Handle(SkipCommand command, CancellationToken cancellationToken)
}
class MessageReceivedHandler {
+Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
}
class IAudioService
class DiscordSocketClient
class SocketSlashCommand
class CancellationToken
class Task
class IRequestHandler
class INotificationHandler
ClearQueueHandler ..|> IRequestHandler
DisconnectHandler ..|> IRequestHandler
PauseHandler ..|> IRequestHandler
PlayHandler ..|> IRequestHandler
ResumeHandler ..|> IRequestHandler
SkipHandler ..|> IRequestHandler
MessageReceivedHandler ..|> INotificationHandler
ClearQueueHandler --> IAudioService
DisconnectHandler --> IAudioService
PauseHandler --> IAudioService
PlayHandler --> IAudioService
ResumeHandler --> IAudioService
SkipHandler --> IAudioService
ClearQueueHandler --> DiscordSocketClient
DisconnectHandler --> DiscordSocketClient
PauseHandler --> DiscordSocketClient
PlayHandler --> DiscordSocketClient
ResumeHandler --> DiscordSocketClient
SkipHandler --> DiscordSocketClient
ClearQueueHandler --> SocketSlashCommand
DisconnectHandler --> SocketSlashCommand
PauseHandler --> SocketSlashCommand
PlayHandler --> SocketSlashCommand
ResumeHandler --> SocketSlashCommand
SkipHandler --> SocketSlashCommand
ClearQueueHandler --> CancellationToken
DisconnectHandler --> CancellationToken
PauseHandler --> CancellationToken
PlayHandler --> CancellationToken
ResumeHandler --> CancellationToken
SkipHandler --> CancellationToken
ClearQueueHandler --> Task
DisconnectHandler --> Task
PauseHandler --> Task
PlayHandler --> Task
ResumeHandler --> Task
SkipHandler --> Task
```
### Sequence Diagram for PlayHandler
```mermaid
sequenceDiagram
participant User
participant Bot
participant DiscordSocketClient
participant IAudioService
participant SocketSlashCommand
participant LavalinkPlayer
User->>Bot: /play [song]
Bot->>DiscordSocketClient: Get user voice channel
DiscordSocketClient-->>Bot: Voice channel info
Bot->>IAudioService: Get or create player
IAudioService-->>Bot: Player instance
Bot->>SocketSlashCommand: Get search query
SocketSlashCommand-->>Bot: Search query
Bot->>IAudioService: Load tracks
IAudioService-->>Bot: Track collection
Bot->>LavalinkPlayer: Play track
LavalinkPlayer-->>Bot: Track started
Bot->>User: Now playing embed
```
### Sequence Diagram for MessageReceivedHandler
```mermaid
sequenceDiagram
participant User
participant Bot
participant DiscordSocketClient
participant ISender
participant MessageReceivedNotification
User->>Bot: Send message
Bot->>MessageReceivedNotification: Create notification
Bot->>DiscordSocketClient: Check if bot is mentioned
DiscordSocketClient-->>Bot: Mention info
alt Bot is mentioned
Bot->>ISender: Send ChatCommand
end
Bot->>DiscordSocketClient: Check for statistics command
alt Statistics command found
Bot->>DiscordSocketClient: Get server and channel info
DiscordSocketClient-->>Bot: Server and channel info
Bot->>User: Send statistics embed
end
```
This README provides an overview of the handlers and their responsibilities, along with class and sequence diagrams to illustrate the interactions and relationships between the components.

View File

@@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UserSecretsId>ec2f340f-a44c-4869-ab79-a12ba9459d80</UserSecretsId> <UserSecretsId>ec2f340f-a44c-4869-ab79-a12ba9459d80</UserSecretsId>
<AssemblyVersion>0.0.1337</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<!-- Lavalink4net 4.0.25 seems to break the Message Module--> <!-- Lavalink4net 4.0.25 seems to break the Message Module-->

View File

@@ -2,11 +2,15 @@
```mermaid ```mermaid
flowchart TD flowchart TD
Program[Program] -->|Register| EventListener Program[Program] -->|Register| EventListener
Program --> Intervals[VoiceChannelMonitorService]
Intervals --> SetStatus[SetStatus, Updates status with amount of playing bots]
Intervals --> LeaveChannel[LeaveOnAlone, Leaves channel when alone for a time]
EventListener[DiscordEventListener] --> A[MessageReceivedHandler] EventListener[DiscordEventListener] --> A[MessageReceivedHandler]
EventListener[DiscordEventListener] --> A2[SlashCommandReceivedHandler] EventListener[DiscordEventListener] --> A2[SlashCommandReceivedHandler]
A --> |Message| f{If bot is mentioned} A --> |Message| f{If bot is mentioned}
A --> |Message '!LunarisStats'| p[Responds with Server and Channel Statistics.]
f --> |ChatCommand| v[ChatHandler] f --> |ChatCommand| v[ChatHandler]
A2[SlashCommandReceivedHandler] -->|Message| C{Send to correct command by A2[SlashCommandReceivedHandler] -->|Message| C{Send to correct command by
@@ -14,8 +18,11 @@ flowchart TD
C -->|JoinCommand| D[JoinHandler] C -->|JoinCommand| D[JoinHandler]
C -->|PlayCommand| E[PlayHandler] C -->|PlayCommand| E[PlayHandler]
C -->|HelloCommand| F[HelloHandler] C -->|PauseCommand| F[PauseHandler]
C -->|GoodbyeCommand| G[GoodbyeHandler] C -->|DisconnectCommand| H[DisconnectHandler]
C -->|ResumeCommand| J[ResumeHandler]
C -->|SkipCommand| K[SkipHandler]
C -->|ClearQueueCommand| L[ClearQueueHandler]
``` ```
Program registers an event listener ```DiscordEventListener``` which publish a message : Program registers an event listener ```DiscordEventListener``` which publish a message :
@@ -30,20 +37,33 @@ await Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken)
## Handler integrations ## Handler integrations
```mermaid ```mermaid
flowchart TD flowchart LR
D[JoinHandler] --> Disc[Discord Api] D[JoinHandler] --> Disc[Discord Api]
E[PlayHandler] --> Disc[Discord Api] E[PlayHandler] --> Disc[Discord Api]
F[HelloHandler] --> Disc[Discord Api] F[SkipHandler] --> Disc[Discord Api]
G[GoodbyeHandler] --> Disc[Discord Api] G[PauseHandler] --> Disc[Discord Api]
v[ChatHandler] --> Disc[Discord Api] v[ChatHandler] --> Disc[Discord Api]
ClearQueueHandler --> Disc
ClearQueuehandler --> Lava
DisconnectHandler --> Disc
Resumehandler --> Disc
v --> o[Ollama Server] v --> o[Ollama Server]
o --> v o --> v
E --> Lava[Lavalink] E --> Lava[Lavalink]
F --> Lava
G --> Lava
``` ```
|Name| Description | |Name| Description |
|--|--| |--|--|
| JoinHandler| Handles the logic for **just** joining a voice channel. | | JoinHandler| Handles the logic for **just** joining a voice channel. |
| PlayHandler| Handles the logic for joining and playing music in a voice channel. | | PlayHandler| Handles the logic for joining and playing music in a voice channel. |
| HelloHandler| Responds with Hello. (Dummy handler, will be removed)| | PauseHandler | Handles the logic for pausing currently playing track. |
| GoodbyeHandler| Responds with Goodbye. (Dummy handler, will be removed)| | DisconnectHandler | Handles the logic for disconnecting from voicechannels. |
| ClearQueueHandler | Handles the logic for clearing the queued songs, except the currently playing one. |
| SkipHandler | Handles the logic for skipping tracks that are queued. If 0 trackS is in queue, it stops the current one.|
| Resumehandler | Resumes paused tracks. |
| ChatHandler| Handles the logic for LLM chat with user. | | ChatHandler| Handles the logic for LLM chat with user. |

View File

@@ -1,3 +1,4 @@
using Discord;
using Discord.WebSocket; using Discord.WebSocket;
namespace Lunaris2.Service namespace Lunaris2.Service
@@ -25,6 +26,30 @@ namespace Lunaris2.Service
} }
private async Task CheckVoiceChannels() private async Task CheckVoiceChannels()
{
SetStatus();
await LeaveOnAlone();
}
private void SetStatus()
{
var channels = _client.Guilds
.SelectMany(guild => guild.VoiceChannels)
.Count(channel =>
channel.ConnectedUsers
.Any(guildUser => guildUser.Id == _client.CurrentUser.Id) &&
channel.Users.Count > 1
);
if (channels == 0)
_client.SetGameAsync(System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString(), type: ActivityType.CustomStatus);
else if(channels == 1)
_client.SetGameAsync("in 1 server", type: ActivityType.Playing);
else if(channels > 1)
_client.SetGameAsync($" in {channels} servers", type: ActivityType.Playing);
}
private async Task LeaveOnAlone()
{ {
foreach (var guild in _client.Guilds) foreach (var guild in _client.Guilds)
{ {

View File

@@ -49,12 +49,9 @@ plugins:
# Clients are queried in the order they are given (so the first client is queried first and so on...) # Clients are queried in the order they are given (so the first client is queried first and so on...)
clients: clients:
- MUSIC - MUSIC
- ANDROID_TESTSUITE
- WEB - WEB
- TVHTML5EMBEDDED - TVHTML5EMBEDDED
# name: # Name of the plugin - ANDROID_TESTSUITE
# some_key: some_value # Some key-value pair for the plugin
# another_key: another_value
lavalink: lavalink:
plugins: plugins:
- dependency: com.github.devoxin:lavadspx-plugin:0.0.5 # replace {VERSION} with the latest version from the "Releases" tab. - dependency: com.github.devoxin:lavadspx-plugin:0.0.5 # replace {VERSION} with the latest version from the "Releases" tab.