Merged projects with plugins and modules
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using DiscordBotCore;
|
||||
|
||||
namespace CppWrapper.LibraryManagement
|
||||
{
|
||||
public sealed class ExternLibrary
|
||||
{
|
||||
public string LibraryPath { get; init; }
|
||||
public IntPtr LibraryHandle { get; private set; }
|
||||
|
||||
public ExternLibrary(string libraryPath)
|
||||
{
|
||||
LibraryPath = libraryPath;
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void InitializeLibrary()
|
||||
{
|
||||
if(LibraryHandle != IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Loading library {LibraryPath}");
|
||||
|
||||
|
||||
if(!NativeLibrary.TryLoad(LibraryPath, out IntPtr hModule))
|
||||
{
|
||||
throw new DllNotFoundException($"Unable to load library {LibraryPath}");
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Library {LibraryPath} loaded successfully [{hModule}]");
|
||||
|
||||
LibraryHandle = hModule;
|
||||
}
|
||||
|
||||
public void FreeLibrary()
|
||||
{
|
||||
if(LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.Free(LibraryHandle);
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Library {LibraryPath} freed successfully");
|
||||
}
|
||||
|
||||
private IntPtr GetFunctionPointer(string functionName)
|
||||
{
|
||||
if(LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Library is not loaded");
|
||||
}
|
||||
|
||||
if(!NativeLibrary.TryGetExport(LibraryHandle, functionName, out IntPtr functionPointer))
|
||||
{
|
||||
throw new EntryPointNotFoundException($"Unable to find function {functionName}");
|
||||
}
|
||||
|
||||
return functionPointer;
|
||||
}
|
||||
|
||||
public T GetDelegateForFunctionPointer<T>(string methodName) where T : Delegate
|
||||
{
|
||||
IntPtr functionPointer = GetFunctionPointer(methodName);
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Function pointer for {methodName} obtained successfully [address: {functionPointer}]");
|
||||
|
||||
T result = (T)Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T));
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Delegate for {methodName} created successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IntPtr GetFunctionPointerForDelegate<T>(T functionDelegate) where T : Delegate
|
||||
{
|
||||
IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(functionDelegate);
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Function pointer for delegate {functionDelegate.Method.Name} obtained successfully [address: {functionPointer}]");
|
||||
|
||||
return functionPointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the extern setter function to point its function to this C# function instead.
|
||||
/// This function takes the name of the extern setter function and the C# function to be executed.
|
||||
/// <para><b>How it works:</b></para>
|
||||
/// Find the external setter method by its name. It should take one parameter, which is the pointer to the function to be executed.
|
||||
/// Take the delegate function that should be executed and get its function pointer.
|
||||
/// Call the external setter with the new function memory address. This should replace the old C++ function with the new C# function.
|
||||
/// </summary>
|
||||
/// <param name="setterExternFunctionName">The setter function name</param>
|
||||
/// <param name="executableFunction">The function that the C++ setter will make its internal function to point to</param>
|
||||
/// <typeparam name="ExecuteDelegate">A delegate that reflects the executable function structure</typeparam>
|
||||
/// <typeparam name="SetDelegate">The Setter delegate </typeparam>
|
||||
/// <returns>A response if it exists as an object</returns>
|
||||
public object? SetExternFunctionSetterPointerToCustomDelegate<SetDelegate, ExecuteDelegate>(string setterExternFunctionName, ExecuteDelegate executableFunction) where ExecuteDelegate : Delegate where SetDelegate : Delegate
|
||||
{
|
||||
SetDelegate setterDelegate = GetDelegateForFunctionPointer<SetDelegate>(setterExternFunctionName);
|
||||
IntPtr executableFunctionPtr = GetFunctionPointerForDelegate(executableFunction);
|
||||
|
||||
var result = setterDelegate.DynamicInvoke(executableFunctionPtr);
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Function {setterExternFunctionName} bound to local action successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CppWrapper.Objects
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct ApplicationStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 19)]
|
||||
public string ServerId;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
|
||||
public string Prefix;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 71)]
|
||||
public string Token;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct ComplexObject
|
||||
{
|
||||
public int Integer;
|
||||
public double DoubleValue;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string strValue;
|
||||
|
||||
public ComplexObject(int integer, double doubleValue, string strValue)
|
||||
{
|
||||
Integer = integer;
|
||||
DoubleValue = doubleValue;
|
||||
this.strValue = strValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using DiscordBotCore;
|
||||
|
||||
namespace CppWrapper.Objects
|
||||
{
|
||||
public static class ObjectConvertor
|
||||
{
|
||||
public static ApplicationStruct ToApplicationStruct(this Application application)
|
||||
{
|
||||
return new ApplicationStruct
|
||||
{
|
||||
Token = application.ApplicationEnvironmentVariables["token"],
|
||||
Prefix = application.ApplicationEnvironmentVariables["prefix"],
|
||||
ServerId = application.ServerID
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using CppWrapper.Objects;
|
||||
|
||||
namespace DiscordBotUI
|
||||
{
|
||||
public abstract class Delegates
|
||||
{
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ProcessApplicationData(ref ApplicationStruct appData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ProcessComplexObject(ref ComplexObject complexObject);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void CsharpFunctionDelegate();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void SetCsharpFunctionPointerDelegate(IntPtr funcPtr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
<ProjectReference Include="..\CppWrapper\CppWrapper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,52 @@
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
using CppWrapper.Objects;
|
||||
using CppWrapper.LibraryManagement;
|
||||
using DiscordBotCore;
|
||||
using CppWrapper;
|
||||
|
||||
namespace DiscordBotUI;
|
||||
|
||||
public class Entry : ICommandAction
|
||||
{
|
||||
public string ActionName => "cppui";
|
||||
|
||||
public string? Description => "A C++ linker to the C++ UI for the bot";
|
||||
|
||||
public string? Usage => "cppui";
|
||||
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnStartupAndCall;
|
||||
|
||||
public async Task Execute(string[]? args)
|
||||
{
|
||||
try{
|
||||
|
||||
string appUiComponent = "./Data/Test/libtestlib.dll";
|
||||
|
||||
ExternLibrary externalLibrary = new ExternLibrary(appUiComponent);
|
||||
externalLibrary.InitializeLibrary();
|
||||
|
||||
externalLibrary.SetExternFunctionSetterPointerToCustomDelegate<Delegates.SetCsharpFunctionPointerDelegate, Delegates.CsharpFunctionDelegate>("setCSharpFunctionPointer", () =>
|
||||
{
|
||||
Console.WriteLine("Hello from C#. This code is called from the C# function");
|
||||
});
|
||||
|
||||
Delegates.ProcessComplexObject processObj = externalLibrary.GetDelegateForFunctionPointer<Delegates.ProcessComplexObject>("ProcessComplexObject");
|
||||
|
||||
ComplexObject complexObject = new ComplexObject(10, 10.5, "Hello from C#");
|
||||
processObj(ref complexObject);
|
||||
|
||||
Console.WriteLine($"Integer: {complexObject.Integer}");
|
||||
Console.WriteLine($"Double: {complexObject.DoubleValue}");
|
||||
Console.WriteLine($"String: {complexObject.strValue}");
|
||||
|
||||
externalLibrary.FreeLibrary();
|
||||
} catch (Exception dllException) {
|
||||
Application.CurrentApplication.Logger.LogException(dllException, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Discord;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelCommand: DBCommand
|
||||
{
|
||||
public string Command => "level";
|
||||
|
||||
public List<string> Aliases => ["lvl", "rank"];
|
||||
|
||||
public string Description => "Display tour current level";
|
||||
|
||||
public string Usage => "level";
|
||||
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
if(Variables.Database is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Database is not initialized", this, LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
object[]? user = await Variables.Database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE UserID=@userId",
|
||||
new KeyValuePair<string, object>("userId", args.Context.Message.Author.Id));
|
||||
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("You are now unranked !");
|
||||
return;
|
||||
}
|
||||
|
||||
var level = (int)user[1];
|
||||
var exp = (int)user[2];
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
var r = new Random();
|
||||
builder.WithColor(r.Next(256), r.Next(256), r.Next(256));
|
||||
builder.AddField("Current Level", level, true)
|
||||
.AddField("Current EXP", exp, true)
|
||||
.AddField("Required Exp", (level * 8 + 24).ToString(), true);
|
||||
builder.WithTimestamp(DateTimeOffset.Now);
|
||||
builder.WithAuthor(args.Context.Message.Author);
|
||||
await args.Context.Channel.SendMessageAsync(embed: builder.Build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Database;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using static LevelingSystem.Variables;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelEvent : DBEvent
|
||||
{
|
||||
public string Name => "Leveling System Event Handler";
|
||||
public string Description => "The Leveling System Event Handler";
|
||||
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public async void Start(DiscordSocketClient client)
|
||||
{
|
||||
|
||||
Directory.CreateDirectory(DataFolder);
|
||||
await Task.Delay(200);
|
||||
Database = new SqlDatabase(DataFolder + "Users.db");
|
||||
await Database.Open();
|
||||
|
||||
|
||||
if (!File.Exists(DataFolder + "Settings.txt"))
|
||||
{
|
||||
GlobalSettings = new Settings
|
||||
{
|
||||
SecondsToWaitBetweenMessages = 5,
|
||||
MaxExp = 7,
|
||||
MinExp = 1
|
||||
};
|
||||
await DiscordBotCore.Others.JsonManager.SaveToJsonFile(DataFolder + "Settings.txt", GlobalSettings);
|
||||
}
|
||||
else
|
||||
GlobalSettings = await DiscordBotCore.Others.JsonManager.ConvertFromJson<Settings>(DataFolder + "Settings.txt");
|
||||
|
||||
if (!await Database.TableExistsAsync("Levels"))
|
||||
await Database.CreateTableAsync("Levels", "UserID VARCHAR(128)", "Level INT", "EXP INT");
|
||||
|
||||
if (!await Database.TableExistsAsync("Users"))
|
||||
await Database.CreateTableAsync("Users", "UserID VARCHAR(128)", "UserMention VARCHAR(128)", "Username VARCHAR(128)", "Discriminator VARCHAR(128)");
|
||||
|
||||
|
||||
|
||||
client.MessageReceived += ClientOnMessageReceived;
|
||||
}
|
||||
|
||||
private async Task ClientOnMessageReceived(SocketMessage arg)
|
||||
{
|
||||
if (arg.Author.IsBot || arg.IsTTS || arg.Content.StartsWith(Application.CurrentApplication.ApplicationEnvironmentVariables["prefix"]))
|
||||
return;
|
||||
|
||||
if (WaitingList.ContainsKey(arg.Author.Id) && WaitingList[arg.Author.Id] > DateTime.Now.AddSeconds(-GlobalSettings.SecondsToWaitBetweenMessages))
|
||||
return;
|
||||
|
||||
var userID = arg.Author.Id.ToString();
|
||||
|
||||
object[] userData = await Database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE userID='{userID}'");
|
||||
if (userData is null)
|
||||
{
|
||||
await Database.ExecuteAsync($"INSERT INTO Levels (UserID, Level, EXP) VALUES ('{userID}', 1, 0)");
|
||||
await Database.ExecuteAsync($"INSERT INTO Users (UserID, UserMention) VALUES ('{userID}', '{arg.Author.Mention}')");
|
||||
return;
|
||||
}
|
||||
|
||||
var level = (int)userData[1];
|
||||
var exp = (int)userData[2];
|
||||
|
||||
|
||||
var random = new Random().Next(GlobalSettings.MinExp, GlobalSettings.MaxExp);
|
||||
if (exp + random >= level * 8 + 24)
|
||||
{
|
||||
await Database.ExecuteAsync($"UPDATE Levels SET Level={level + 1}, EXP={random - (level * 8 + 24 - exp)} WHERE UserID='{userID}'");
|
||||
await arg.Channel.SendMessageAsync($"{arg.Author.Mention} has leveled up to level {level + 1}!");
|
||||
}
|
||||
else await Database.ExecuteAsync($"UPDATE Levels SET EXP={exp + random} WHERE UserID='{userID}'");
|
||||
|
||||
WaitingList.Add(arg.Author.Id, DateTime.Now);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,20 @@
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
public class ReloadAction: ICommandAction
|
||||
{
|
||||
public string ActionName => "LevelingSystemReload";
|
||||
public string? Description => "Reloads the Leveling System config file";
|
||||
public string? Usage => "LevelingSystemReload";
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public async Task Execute(string[]? args)
|
||||
{
|
||||
Variables.GlobalSettings = await JsonManager.ConvertFromJson<Settings>(Variables.DataFolder + "Settings.txt");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Database;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
public int SecondsToWaitBetweenMessages { get; set; }
|
||||
public int MinExp { get; set; }
|
||||
public int MaxExp { get; set; }
|
||||
}
|
||||
|
||||
internal static class Variables
|
||||
{
|
||||
internal static readonly string DataFolder = Application.GetResourceFullPath("LevelingSystem/");
|
||||
internal static SqlDatabase? Database;
|
||||
internal static readonly Dictionary<ulong, DateTime> WaitingList = new();
|
||||
internal static Settings GlobalSettings = new();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Discord;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Online;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class AddMelody: DBCommand
|
||||
{
|
||||
public string Command => "add_melody";
|
||||
|
||||
public List<string>? Aliases => new()
|
||||
{
|
||||
"madd"
|
||||
};
|
||||
|
||||
public string Description => "Add a custom melody to the database";
|
||||
public string Usage => "add_melody [title],[description?],[aliases],[byteSize]";
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
var arguments = string.Join(" ", args.Arguments);
|
||||
string[] split = arguments.Split(',');
|
||||
|
||||
if (split.Length < 4)
|
||||
{
|
||||
var message = "";
|
||||
message += "Invalid arguments given. Please use the following format:\n";
|
||||
message += "add_melody [title],[description?],[aliases],[byteSize]\n";
|
||||
message += "title: The title of the melody\n";
|
||||
message += "description: The description of the melody\n";
|
||||
message += "aliases: The aliases of the melody. Use | to separate them\n";
|
||||
message += "byteSize: The byte size of the melody. Default is 1024. ( & will use default)\n";
|
||||
|
||||
await args.Context.Channel.SendMessageAsync(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Context.Message.Attachments.Count == 0)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("You must upload a valid .mp3 audio or .mp4 video file !!");
|
||||
return;
|
||||
}
|
||||
|
||||
var file = args.Context.Message.Attachments.FirstOrDefault();
|
||||
if (!(file.Filename.EndsWith(".mp3") || file.Filename.EndsWith(".mp4")))
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid file format !!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var title = split[0];
|
||||
var description = split[1];
|
||||
string[]? aliases = split[2]?.Split('|') ?? null;
|
||||
var byteSize = split[3];
|
||||
int bsize;
|
||||
if (!int.TryParse(byteSize, out bsize))
|
||||
bsize = 1024;
|
||||
|
||||
|
||||
var msg = await args.Context.Channel.SendMessageAsync("Saving melody ...");
|
||||
Console.WriteLine("Saving melody");
|
||||
|
||||
IProgress<float> downloadProgress = new Progress<float>();
|
||||
|
||||
|
||||
var location = Application.GetResourceFullPath($"Music/Melodies/{title}.mp3");
|
||||
Directory.CreateDirectory(Application.GetResourceFullPath("Music/Melodies"));
|
||||
await ServerCom.DownloadFileAsync(file.Url, location, downloadProgress);
|
||||
|
||||
Console.WriteLine($"Done. Saved at {location}");
|
||||
|
||||
await msg.ModifyAsync(a => a.Content = "Done");
|
||||
|
||||
|
||||
var info =
|
||||
MusicInfoExtensions.CreateMusicInfo(title, location, description ?? "Unknown", aliases.ToList(), bsize);
|
||||
|
||||
Variables._MusicDatabase?.Add(title, info);
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Title = "A new music was successfully added !";
|
||||
builder.AddField("Title", info.Title);
|
||||
builder.AddField("Description", info.Description);
|
||||
builder.AddField("Aliases", string.Join(" | ", aliases));
|
||||
await args.Context.Channel.SendMessageAsync(embed: builder.Build());
|
||||
|
||||
await Variables._MusicDatabase.SaveToFile();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class AddMelodyYoutube: DBCommand
|
||||
{
|
||||
public string Command => "add_melody_youtube";
|
||||
|
||||
public List<string>? Aliases => new()
|
||||
{
|
||||
"madd-yt"
|
||||
};
|
||||
|
||||
public string Description => "Add melody to the database from a youtube link";
|
||||
public string Usage => "add_melody_youtube [URL] <alias1|alias2|...>";
|
||||
public bool requireAdmin => true;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
if (args.Arguments is null)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid arguments given. Please use the following format:\nadd_melody_youtube [URL]");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var URL = args.Arguments[0];
|
||||
|
||||
if (!URL.StartsWith("https://www.youtube.com/watch?v=") && !URL.StartsWith("https://youtu.be/"))
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid URL given. Please use the following format:\nadd_melody_youtube [URL]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Arguments.Length <= 1)
|
||||
{
|
||||
await args.Channel.SendMessageAsync("Please specify at least one alias for the melody !");
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = await args.Context.Channel.SendMessageAsync("Saving melody ...");
|
||||
|
||||
var title = await YoutubeDLP.DownloadMelody(URL);
|
||||
|
||||
if (title == null)
|
||||
{
|
||||
await msg.ModifyAsync(x => x.Content = "Failed to download melody !");
|
||||
return;
|
||||
}
|
||||
|
||||
var joinedAliases = string.Join(" ", args.Arguments.Skip(1));
|
||||
List<string> aliases = joinedAliases.Split('|').ToList();
|
||||
|
||||
|
||||
if (Variables._MusicDatabase.ContainsMelodyWithNameOrAlias(title))
|
||||
Variables._MusicDatabase.Remove(title);
|
||||
|
||||
Variables._MusicDatabase.Add(title, new MusicInfo()
|
||||
{
|
||||
Aliases = aliases,
|
||||
ByteSize = 1024,
|
||||
Description = "Melody added from youtube link",
|
||||
Location = Application.GetResourceFullPath($"Music/Melodies/{title}.mp3"),
|
||||
Title = title
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
await Variables._MusicDatabase.SaveToFile();
|
||||
await msg.ModifyAsync(x => x.Content = "Melody saved !");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Discord;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class SearchMelody: DBCommand
|
||||
{
|
||||
|
||||
public string Command => "search_melody";
|
||||
public List<string>? Aliases => null;
|
||||
public string Description => "Search for a melody in the database";
|
||||
public string Usage => "search_melody [melody name OR one of its aliases]";
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
var title = string.Join(" ", args.Arguments);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
args.Context.Channel.SendMessageAsync("You need to specify a melody name");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MusicInfo>? info = Variables._MusicDatabase.GetMusicInfoList(title);
|
||||
if (info == null || info.Count == 0)
|
||||
{
|
||||
args.Context.Channel.SendMessageAsync("No melody with that name or alias was found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.Count > 1)
|
||||
args.Context.Channel.SendMessageAsync(embed: info.ToEmbed(Color.DarkOrange));
|
||||
else
|
||||
args.Context.Channel.SendMessageAsync(embed: info[0].ToEmbed(Color.DarkOrange));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.Events;
|
||||
|
||||
public class OnLoad: DBEvent
|
||||
{
|
||||
private static readonly string _DefaultMusicPath = "Music/";
|
||||
private static readonly string _DefaultSaveLocation = "Music/Melodies/";
|
||||
private static readonly string _DefaultMusicDB = "Music/music_db.json";
|
||||
public string Name => "Music Commands";
|
||||
public string Description => "The default music commands event loader";
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public async void Start(DiscordSocketClient client)
|
||||
{
|
||||
var path1 = Application.GetResourceFullPath(_DefaultMusicPath);
|
||||
var path2 = Application.GetResourceFullPath(_DefaultSaveLocation);
|
||||
var path3 = Application.GetResourceFullPath(_DefaultMusicDB);
|
||||
Directory.CreateDirectory(path1);
|
||||
Directory.CreateDirectory(path2);
|
||||
Variables._MusicDatabase = new MusicDatabase(path3);
|
||||
await Variables._MusicDatabase.LoadFromFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Events;
|
||||
|
||||
public class OnVoiceRemoved: DBEvent
|
||||
{
|
||||
|
||||
public string Name => "Event: OnVoiceRemoved";
|
||||
public string Description => "Called when bot leaves a voice channel";
|
||||
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public void Start(DiscordSocketClient client)
|
||||
{
|
||||
client.UserVoiceStateUpdated += async (user, oldState, newState) =>
|
||||
{
|
||||
if (user.Id == client.CurrentUser.Id && newState.VoiceChannel == null)
|
||||
{
|
||||
Variables._MusicPlayer?.MusicQueue.Clear();
|
||||
Variables._MusicPlayer?.Skip();
|
||||
Variables._MusicPlayer?.Stop();
|
||||
await Variables.audioClient!.StopAsync();
|
||||
Variables.audioClient = null;
|
||||
Variables._MusicPlayer = null;
|
||||
|
||||
Application.CurrentApplication.Logger.Log("Bot left voice channel.", this, LogType.Info);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicDatabase: SettingsDictionary<string, MusicInfo>
|
||||
{
|
||||
public MusicDatabase(string file): base(file)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the database contains a melody with the specified name or alias
|
||||
/// </summary>
|
||||
/// <param name="melodyName">The name (alias) of the melody</param>
|
||||
/// <returns></returns>
|
||||
public bool ContainsMelodyWithNameOrAlias(string melodyName)
|
||||
{
|
||||
return ContainsKey(melodyName) || Values.Any(elem => elem.Aliases.Contains(melodyName, StringComparer.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the music info of a melody with the specified name or alias. Returns the first match or null if no match was found.
|
||||
/// </summary>
|
||||
/// <param name="searchQuery">The music name or one of its aliases.</param>
|
||||
/// <returns></returns>
|
||||
public MusicInfo? GetMusicInfo(string searchQuery)
|
||||
{
|
||||
return FirstOrDefault(kvp => kvp.Key.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
kvp.Value.Aliases.Any(alias => alias.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase))
|
||||
).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of music info that match the search query. Returns null if an error occurred, or empty list if no match was found.
|
||||
/// </summary>
|
||||
/// <param name="searchQuery">The search query</param>
|
||||
/// <returns>null if an error occured, otherwise a list with songs that match the search query. If no song match the list is empty</returns>
|
||||
public List<MusicInfo>? GetMusicInfoList(string searchQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.Where(kvp =>
|
||||
kvp.Key.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
kvp.Value.Aliases.Any(alias => alias.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase))
|
||||
)
|
||||
.Select(item => item.Value).ToList();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new entry to the database based on the music info. It uses the title as the key.
|
||||
/// </summary>
|
||||
/// <param name="musicInfo">The music to add to</param>
|
||||
public void AddNewEntryBasedOnMusicInfo(MusicInfo musicInfo)
|
||||
{
|
||||
Add(musicInfo.Title, musicInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicInfo
|
||||
{
|
||||
public string Title { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string Location { get; init; }
|
||||
public List<string>? Aliases { get; init; }
|
||||
public int? ByteSize { get; init; } = 1024;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Discord;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public static class MusicInfoExtensions
|
||||
{
|
||||
public static void AddAlias(this MusicInfo musicInfo, string alias)
|
||||
{
|
||||
musicInfo.Aliases.Add(alias);
|
||||
}
|
||||
|
||||
public static void RemoveAlias(this MusicInfo musicInfo, string alias)
|
||||
{
|
||||
musicInfo.Aliases.Remove(alias);
|
||||
}
|
||||
|
||||
public static MusicInfo CreateMusicInfo(string title, string fileLocation, string? Description = "Unknown", List<string>? aliases = null, int? byteSize = 1024)
|
||||
{
|
||||
return new MusicInfo()
|
||||
{
|
||||
Title = title,
|
||||
Aliases = aliases,
|
||||
Description = Description,
|
||||
Location = fileLocation,
|
||||
ByteSize = byteSize
|
||||
};
|
||||
}
|
||||
|
||||
public static Embed ToEmbed(this MusicInfo musicInfo, Color? embedColor = null)
|
||||
{
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Color = embedColor ?? Color.Default;
|
||||
builder.WithTitle(musicInfo.Title);
|
||||
builder.WithDescription(musicInfo.Description);
|
||||
if (musicInfo.Aliases != null)
|
||||
builder.AddField("Aliases", string.Join(", ", musicInfo.Aliases));
|
||||
else
|
||||
builder.AddField("Aliases", "None");
|
||||
builder.AddField("Location", musicInfo.Location);
|
||||
builder.AddField("ByteSize", musicInfo.ByteSize);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static Embed ToEmbed(this List<MusicInfo> musicInfo, Color? embedColor = null)
|
||||
{
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Color = embedColor ?? Color.Default;
|
||||
builder.WithTitle("Search results");
|
||||
builder.WithDescription("Found " + musicInfo.Count + " results");
|
||||
builder.AddField("Results", string.Join("\n", musicInfo.Select(item => item.Title)));
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System.Diagnostics;
|
||||
using Discord.Audio;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicPlayer
|
||||
{
|
||||
private static int defaultByteSize = 1024;
|
||||
|
||||
public Queue<MusicInfo> MusicQueue { get; private set; }
|
||||
|
||||
public bool isPaused { get; private set; }
|
||||
public bool isPlaying { get; private set; }
|
||||
|
||||
private bool isQueueRunning;
|
||||
public int ByteSize { get; private set; }
|
||||
|
||||
public MusicInfo? CurrentlyPlaying { get; private set; }
|
||||
|
||||
public MusicPlayer()
|
||||
{
|
||||
MusicQueue = new Queue<MusicInfo>();
|
||||
}
|
||||
|
||||
public async Task PlayQueue()
|
||||
{
|
||||
if (isQueueRunning)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Another queue is running !", typeof(MusicPlayer), LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables.audioClient is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Audio Client is null", typeof(MusicPlayer), LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
isQueueRunning = true;
|
||||
|
||||
string? ffmpegPath = await Application.CurrentApplication.PluginManager.GetDependencyLocation("FFMPEG");
|
||||
if(ffmpegPath is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("FFMPEG is missing. Please install it and try again.", typeof(MusicPlayer), LogType.Error);
|
||||
isQueueRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ffmpegPath = ffmpegPath.Replace("\\", "/");
|
||||
ffmpegPath = Path.GetFullPath(ffmpegPath);
|
||||
|
||||
Console.WriteLine("FFMPEG Path: " + ffmpegPath);
|
||||
|
||||
while (MusicQueue.TryDequeue(out var dequeuedMusic))
|
||||
{
|
||||
CurrentlyPlaying = dequeuedMusic;
|
||||
await using var dsAudioStream = Variables.audioClient.CreatePCMStream(AudioApplication.Mixed);
|
||||
using var ffmpeg = CreateStream(ffmpegPath, CurrentlyPlaying.Location);
|
||||
if (ffmpeg is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Failed to start ffmpeg process. FFMPEG is missing or the {CurrentlyPlaying.Location} has an invalid format.", typeof(MusicPlayer), LogType.Error);
|
||||
continue;
|
||||
}
|
||||
await using var ffmpegOut = ffmpeg.StandardOutput.BaseStream;
|
||||
await PlayCurrentTrack(dsAudioStream, ffmpegOut, CurrentlyPlaying.ByteSize ?? defaultByteSize);
|
||||
}
|
||||
isQueueRunning = false;
|
||||
CurrentlyPlaying = null;
|
||||
}
|
||||
|
||||
public void Loop(int numberOfTimes)
|
||||
{
|
||||
if (CurrentlyPlaying is null) return;
|
||||
|
||||
Queue<MusicInfo> tempQueue = new();
|
||||
for (var i = 0; i < numberOfTimes; i++)
|
||||
{
|
||||
tempQueue.Enqueue(CurrentlyPlaying);
|
||||
}
|
||||
|
||||
foreach (var musicInfo in MusicQueue)
|
||||
{
|
||||
tempQueue.Enqueue(musicInfo);
|
||||
}
|
||||
|
||||
MusicQueue = tempQueue;
|
||||
}
|
||||
|
||||
private async Task PlayCurrentTrack(Stream discordVoiceChannelStream, Stream fileStreamFfmpeg, int byteSize)
|
||||
{
|
||||
if (isPlaying) return;
|
||||
ByteSize = byteSize;
|
||||
|
||||
isPlaying = true;
|
||||
isPaused = false;
|
||||
|
||||
while (isPlaying)
|
||||
{
|
||||
if (isPaused) continue;
|
||||
|
||||
var bits = new byte[byteSize];
|
||||
var read = await fileStreamFfmpeg.ReadAsync(bits, 0, ByteSize);
|
||||
if (read == 0) break;
|
||||
|
||||
try
|
||||
{
|
||||
await discordVoiceChannelStream.WriteAsync(bits, 0, read);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.LogException(ex, this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
await discordVoiceChannelStream.FlushAsync();
|
||||
await fileStreamFfmpeg.FlushAsync();
|
||||
|
||||
isPlaying = false;
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
public void Unpause()
|
||||
{
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public bool Enqueue(string musicName)
|
||||
{
|
||||
var minfo = Variables._MusicDatabase.GetMusicInfo(musicName);
|
||||
if (minfo is null) return false;
|
||||
|
||||
MusicQueue.Enqueue(minfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Skip()
|
||||
{
|
||||
isPlaying = false;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
// set volume
|
||||
}
|
||||
|
||||
private static Process? CreateStream(string? fileName, string path)
|
||||
{
|
||||
return Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
MusicQueue.Clear();
|
||||
isPlaying = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,54 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Loop: DBSlashCommand
|
||||
{
|
||||
|
||||
public string Name => "loop";
|
||||
public string Description => "Loop the current song for a certain amount of times. If no times are specified, it will loop once";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
|
||||
public List<SlashCommandOptionBuilder> Options => new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ApplicationCommandOptionType.Integer,
|
||||
Name = "times",
|
||||
Description = "How many times to loop the song",
|
||||
IsRequired = false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
context.RespondAsync("There is nothing playing right now");
|
||||
return;
|
||||
}
|
||||
|
||||
var times = context.Data.Options.FirstOrDefault()?.Value.ToString() ?? "1";
|
||||
|
||||
if (!int.TryParse(times, out var timesToLoop))
|
||||
{
|
||||
context.RespondAsync("Invalid number");
|
||||
return;
|
||||
}
|
||||
|
||||
if (timesToLoop < 1)
|
||||
{
|
||||
context.RespondAsync("You need to specify a number greater than 0");
|
||||
return;
|
||||
}
|
||||
|
||||
Variables._MusicPlayer.Loop(timesToLoop);
|
||||
|
||||
context.RespondAsync($"Looping {Variables._MusicPlayer.CurrentlyPlaying.Title} {timesToLoop} times. Check the queue to see the progress");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Play: DBSlashCommand
|
||||
{
|
||||
public string Name => "play";
|
||||
public string Description => "Play music command";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
|
||||
public List<SlashCommandOptionBuilder> Options => new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
IsRequired = true,
|
||||
Description = "The music name to be played",
|
||||
Name = "music-name",
|
||||
Type = ApplicationCommandOptionType.String
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
var melodyName = context.Data.Options.First().Value as string;
|
||||
|
||||
if (melodyName is null)
|
||||
{
|
||||
await context.RespondAsync("Failed to retrieve melody with name " + melodyName);
|
||||
return;
|
||||
}
|
||||
|
||||
var melody = Variables._MusicDatabase.GetMusicInfo(melodyName);
|
||||
if (melody is null)
|
||||
{
|
||||
await context.RespondAsync("The searched melody does not exists in the database. Sorry :(");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = context.User as IGuildUser;
|
||||
if (user is null)
|
||||
{
|
||||
await context.RespondAsync("Failed to get user data from channel ! Check error log at " + DateTime.Now.ToLongTimeString());
|
||||
Application.CurrentApplication.Logger.Log("User is null while trying to convert from context.User to IGuildUser.", typeof(Play), LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var voiceChannel = user.VoiceChannel;
|
||||
if (voiceChannel is null)
|
||||
{
|
||||
await context.RespondAsync("Unknown voice channel. Maybe I do not have permission to join it ?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables.audioClient is null)
|
||||
{
|
||||
Variables.audioClient = await voiceChannel.ConnectAsync(true); // self deaf
|
||||
}
|
||||
|
||||
Variables._MusicPlayer ??= new MusicPlayer();
|
||||
|
||||
if (!Variables._MusicPlayer.Enqueue(melodyName))
|
||||
{
|
||||
await context.RespondAsync("Failed to enqueue your request. Something went wrong !");
|
||||
return;
|
||||
}
|
||||
await context.RespondAsync("Enqueued your request");
|
||||
|
||||
await Variables._MusicPlayer.PlayQueue(); //start queue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Queue: DBSlashCommand
|
||||
{
|
||||
public string Name => "queue";
|
||||
public string Description => "Queue a melody to play";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
public List<SlashCommandOptionBuilder> Options => null;
|
||||
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer is null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables._MusicPlayer.MusicQueue.Count == 0 && Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing");
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new EmbedBuilder()
|
||||
{
|
||||
Title = "Music Queue",
|
||||
Description = "Here is the current music queue",
|
||||
Color = Color.Blue
|
||||
};
|
||||
|
||||
if (Variables._MusicPlayer.CurrentlyPlaying != null)
|
||||
builder.AddField("Current music", Variables._MusicPlayer.CurrentlyPlaying.Title);
|
||||
|
||||
var i = 1;
|
||||
foreach (var melody in Variables._MusicPlayer.MusicQueue)
|
||||
{
|
||||
builder.AddField($"#{i}", melody.Title);
|
||||
i++;
|
||||
}
|
||||
|
||||
await context.RespondAsync(embed: builder.Build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Skip: DBSlashCommand
|
||||
{
|
||||
public string Name => "skip";
|
||||
public string Description => "Skip the current melody";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
public List<SlashCommandOptionBuilder> Options => null;
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer is null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables._MusicPlayer.MusicQueue.Count == 0 && Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing");
|
||||
return;
|
||||
}
|
||||
|
||||
var melodyTitle = Variables._MusicPlayer.CurrentlyPlaying.Title;
|
||||
|
||||
await context.RespondAsync($"Skipping {melodyTitle} ...");
|
||||
Variables._MusicPlayer.Skip();
|
||||
await context.ModifyOriginalResponseAsync(x => x.Content = $"Skipped {melodyTitle}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Discord.Audio;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class Variables
|
||||
{
|
||||
public static MusicDatabase? _MusicDatabase;
|
||||
public static MusicPlayer? _MusicPlayer;
|
||||
|
||||
public static IAudioClient? audioClient;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class YoutubeDLP
|
||||
{
|
||||
public static async Task<string?> DownloadMelody(string url)
|
||||
{
|
||||
Console.WriteLine("Downloading melody: " + url);
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = await DiscordBotCore.Application.CurrentApplication.PluginManager.GetDependencyLocation("yt-dlp");
|
||||
process.StartInfo.Arguments = $"-x --force-overwrites -o \"{DiscordBotCore.Application.GetResourceFullPath("/Music/Melodies")}/%(title)s.%(ext)s\" --audio-format mp3 {url}";
|
||||
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
var title = "";
|
||||
process.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
{
|
||||
if (args.Data.StartsWith("[ExtractAudio] Destination: "))
|
||||
{
|
||||
title = args.Data.Replace("[ExtractAudio] Destination: ", "").Replace(".mp3", "");
|
||||
title = title.Replace("\\", "/");
|
||||
title = title.Split('/').Last().Replace(".mp3", "").TrimEnd();
|
||||
|
||||
Console.WriteLine("Output title: " + title);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
Console.WriteLine("Waiting for process to exit ...");
|
||||
process.BeginOutputReadLine();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return title ?? null;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user