initial
This commit is contained in:
commit
f501a71033
|
@ -0,0 +1,3 @@
|
||||||
|
.vs*
|
||||||
|
bin/
|
||||||
|
obj/
|
|
@ -0,0 +1,88 @@
|
||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
private static DiscordSocketClient? client;
|
||||||
|
private CommandService? commandService;
|
||||||
|
private ServiceProvider? services;
|
||||||
|
|
||||||
|
private async Task MessageReceived (SocketMessage rawMessage)
|
||||||
|
{
|
||||||
|
if (commandService == null || services == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rawMessage is not SocketUserMessage message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (message.Source != MessageSource.User)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var argPos = 0;
|
||||||
|
if (!message.HasCharPrefix('!', ref argPos))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var context = new SocketCommandContext(client, message);
|
||||||
|
await commandService.ExecuteAsync(context, argPos, services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Initializing...");
|
||||||
|
|
||||||
|
using (services = GetServices())
|
||||||
|
{
|
||||||
|
// Initialize Discord Socket Client and Command Service
|
||||||
|
client = services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
commandService = services.GetRequiredService<CommandService>();
|
||||||
|
|
||||||
|
// add log handlers
|
||||||
|
client.Log += LogAsync;
|
||||||
|
client.MessageReceived += MessageReceived;
|
||||||
|
client.Ready += ReadyAsync;
|
||||||
|
|
||||||
|
services.GetRequiredService<CommandService>().Log += LogAsync;
|
||||||
|
|
||||||
|
// Start Discord Socket Client
|
||||||
|
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("TOKEN"));
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
|
// Initialize command handler
|
||||||
|
await services.GetRequiredService<CommandHandler>().InitializeAsync();
|
||||||
|
|
||||||
|
// Hang there!
|
||||||
|
await Task.Delay(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceProvider GetServices()
|
||||||
|
{
|
||||||
|
return new ServiceCollection()
|
||||||
|
.AddSingleton<DiscordSocketClient>()
|
||||||
|
.AddSingleton<CommandService>()
|
||||||
|
.AddSingleton<CommandHandler>()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ReadyAsync()
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{client?.CurrentUser} is connected!");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LogAsync(LogMessage arg)
|
||||||
|
{
|
||||||
|
Console.WriteLine(arg.ToString());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
public class CommandHandler
|
||||||
|
{
|
||||||
|
private readonly CommandService _commands;
|
||||||
|
private readonly DiscordSocketClient _discord;
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
|
||||||
|
public CommandHandler(IServiceProvider services)
|
||||||
|
{
|
||||||
|
_commands = services.GetRequiredService<CommandService>();
|
||||||
|
_discord = services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
_services = services;
|
||||||
|
|
||||||
|
_commands.CommandExecuted += CommandExecutedAsync;
|
||||||
|
_discord.MessageReceived += MessageReceivedAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MessageReceivedAsync(SocketMessage rawMessage)
|
||||||
|
{
|
||||||
|
if (!(rawMessage is SocketUserMessage message))
|
||||||
|
return;
|
||||||
|
if (message.Source != MessageSource.User)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var argPos = 0;
|
||||||
|
if (!message.HasCharPrefix('~', ref argPos))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var context = new SocketCommandContext(_discord, message);
|
||||||
|
await _commands.ExecuteAsync(context, argPos, _services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
||||||
|
{
|
||||||
|
if (!command.IsSpecified)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Console.WriteLine($"[{context.User.Username} | {context.Guild.Name}] Command error: {result}");
|
||||||
|
await context.Channel.SendMessageAsync($"Command error: {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
using Discord.Commands;
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
public class Commands : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
private PlayerController playerController = PlayerController.Instance;
|
||||||
|
|
||||||
|
[Command("ping")]
|
||||||
|
[Alias("pong", "hello")]
|
||||||
|
public Task PingAsync()
|
||||||
|
=> ReplyAsync("pong!");
|
||||||
|
|
||||||
|
[Command("play", RunMode = RunMode.Async)]
|
||||||
|
public async Task PlayAsync([Remainder] string musicString)
|
||||||
|
{
|
||||||
|
await playerController.Play(musicString, Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("stop", RunMode = RunMode.Async)]
|
||||||
|
public async Task StopAsync()
|
||||||
|
{
|
||||||
|
await playerController.Stop(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("skip", RunMode = RunMode.Async)]
|
||||||
|
public async Task SkipAsync()
|
||||||
|
{
|
||||||
|
await playerController.Skip(Context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Discord.Net" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.32014.148
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KasinoBot", "KasinoBot.csproj", "{A13F32A9-2011-467D-BEFA-92FD5329B422}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A13F32A9-2011-467D-BEFA-92FD5329B422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A13F32A9-2011-467D-BEFA-92FD5329B422}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A13F32A9-2011-467D-BEFA-92FD5329B422}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A13F32A9-2011-467D-BEFA-92FD5329B422}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {1F467E8A-0721-4DE4-B46C-08F58DD91926}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,164 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Audio;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
public class Music
|
||||||
|
{
|
||||||
|
private IAudioClient AudioClient { get; set; }
|
||||||
|
private ISocketMessageChannel TextChannel { get; set; }
|
||||||
|
private Queue<MusicInfo> Queue { get; }
|
||||||
|
private Stream? CurrentMusicStream { get; set; }
|
||||||
|
private Process? CurrentFFmpeg { get; set; }
|
||||||
|
private AudioOutStream? CurrentOuAudio { get; set; }
|
||||||
|
|
||||||
|
public Music(SocketGuild guild, ISocketMessageChannel channel, IAudioClient audioClient)
|
||||||
|
{
|
||||||
|
Queue = new Queue<MusicInfo>();
|
||||||
|
TextChannel = channel;
|
||||||
|
AudioClient = audioClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Play(string query, SocketUser user, SocketUserMessage message)
|
||||||
|
{
|
||||||
|
if (message.Channel != null)
|
||||||
|
TextChannel = message.Channel;
|
||||||
|
|
||||||
|
MusicInfo musicInfo = new MusicInfo(query, user);
|
||||||
|
//await message.AddReactionAsync(Emoji.Parse("👌"));
|
||||||
|
await TextChannel.SendMessageAsync($"Enqueued **\"{musicInfo.Title}\"** *({musicInfo.Duration})*");
|
||||||
|
|
||||||
|
Queue.Enqueue(musicInfo);
|
||||||
|
if (Queue.Count == 1)
|
||||||
|
await PlayNextMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop()
|
||||||
|
{
|
||||||
|
Queue.Clear();
|
||||||
|
if (CurrentMusicStream != null)
|
||||||
|
CurrentMusicStream.Close();
|
||||||
|
await Task.Delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Skip()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Skipping");
|
||||||
|
if (CurrentMusicStream != null)
|
||||||
|
CurrentMusicStream.Close();
|
||||||
|
await Task.Delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PlayNextMusic()
|
||||||
|
{
|
||||||
|
if (Queue.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var musicInfo = Queue.Peek();
|
||||||
|
|
||||||
|
using (var input = AudioClient.CreatePCMStream(AudioApplication.Music))
|
||||||
|
using (CurrentFFmpeg = CreateAudioStream(musicInfo.MediaURL))
|
||||||
|
using (CurrentMusicStream = CurrentFFmpeg.StandardOutput.BaseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CurrentMusicStream.CopyToAsync(input);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error CopyToAsync: " + ex.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Queue.Dequeue();
|
||||||
|
await input.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await TextChannel.SendMessageAsync($"Error: {e.Message}");
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await PlayNextMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process CreateAudioStream(string path)
|
||||||
|
{
|
||||||
|
var p = Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "ffmpeg",
|
||||||
|
Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (p == null)
|
||||||
|
throw new Exception("Could not start ffmpeg. Please make sure ffmpeg is available.");
|
||||||
|
else if (p.HasExited)
|
||||||
|
throw new Exception("ffmpeg exited with code " + p.ExitCode);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MusicInfo
|
||||||
|
{
|
||||||
|
public string Query { get; }
|
||||||
|
public SocketUser User { get; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Duration { get; set; }
|
||||||
|
public string MediaURL { get; set; }
|
||||||
|
public string YoutubeURL { get; set; }
|
||||||
|
public MusicInfo(string query, SocketUser user)
|
||||||
|
{
|
||||||
|
Query = query;
|
||||||
|
User = user;
|
||||||
|
|
||||||
|
var p = Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "youtube-dl",
|
||||||
|
Arguments = $"-f bestaudio --dump-json --default-search \"ytsearch\" -g \"{this.Query}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (p == null)
|
||||||
|
throw new Exception("Could not start youtube-dl. Please make sure youtube-dl is available.");
|
||||||
|
else if (p.HasExited)
|
||||||
|
throw new Exception("youtube-dl exited with code " + p.ExitCode);
|
||||||
|
|
||||||
|
var output = p.StandardOutput.ReadToEnd();
|
||||||
|
p.WaitForExit();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JToken json = JObject.Parse(output.Split('\n')[1]);
|
||||||
|
if (json == null)
|
||||||
|
throw new Exception("Could not parse youtube-dl output.");
|
||||||
|
|
||||||
|
int duration = int.Parse(json["duration"].ToString());
|
||||||
|
this.MediaURL = output.Split('\n')[0];
|
||||||
|
this.Title = json["title"].ToString();
|
||||||
|
this.YoutubeURL = json["webpage_url"].ToString();
|
||||||
|
this.Duration = $"{Math.Floor((float)duration / 60)}m {duration % 60}s";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("Error parsing information from Youtube-DL: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
using Discord.Audio;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
public class PlayerController
|
||||||
|
{
|
||||||
|
private static PlayerController instance;
|
||||||
|
public static PlayerController Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
instance = new PlayerController();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<SocketGuild, Music> guildMusic = new Dictionary<SocketGuild, Music>();
|
||||||
|
private static Dictionary<SocketGuild, IAudioClient> clientAudios = new Dictionary<SocketGuild, IAudioClient>();
|
||||||
|
|
||||||
|
private PlayerController() { }
|
||||||
|
|
||||||
|
public async Task Play(string musicInfo, SocketCommandContext context)
|
||||||
|
{
|
||||||
|
var guild = context.Guild;
|
||||||
|
var user = context.User;
|
||||||
|
var message = context.Message;
|
||||||
|
if (guildMusic.ContainsKey(guild))
|
||||||
|
{
|
||||||
|
await guildMusic[guild].Play(musicInfo, user, message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Music musicPlayer;
|
||||||
|
IAudioClient? audioClient;
|
||||||
|
if (!clientAudios.TryGetValue(guild, out audioClient))
|
||||||
|
{
|
||||||
|
SocketVoiceChannel? voiceChannel =
|
||||||
|
guild.Channels.Select(x => x as SocketVoiceChannel).Where(x => x?.Users.Contains(user) ?? false).FirstOrDefault();
|
||||||
|
|
||||||
|
if (voiceChannel == null)
|
||||||
|
{
|
||||||
|
await context.Channel.SendMessageAsync("You need to be in a voice channel to play music.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioClient = await voiceChannel.ConnectAsync();
|
||||||
|
clientAudios.Add(guild, audioClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
musicPlayer = new Music(guild, context.Channel, audioClient);
|
||||||
|
|
||||||
|
guildMusic.Add(guild, musicPlayer);
|
||||||
|
|
||||||
|
await guildMusic[guild].Play(musicInfo, user, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop(SocketCommandContext context)
|
||||||
|
{
|
||||||
|
if (guildMusic.ContainsKey(context.Guild))
|
||||||
|
await guildMusic[context.Guild].Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Skip(SocketCommandContext context)
|
||||||
|
{
|
||||||
|
if (guildMusic.ContainsKey(context.Guild))
|
||||||
|
await guildMusic[context.Guild].Skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
namespace KasinoBot
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Client client = new Client();
|
||||||
|
client.Initialize().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"KasinoBot": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"TOKEN": "ODg3Mzc0OTczNjA0MzU2MTA2.YUDOWA.tI--BKjpUHBHdiHZLjNNaZxniec"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue