initial
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| .vs* | ||||
| bin/ | ||||
| obj/ | ||||
							
								
								
									
										88
									
								
								Client.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Client.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										59
									
								
								CommandHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								CommandHandler.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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}"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								Commands.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Commands.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								KasinoBot.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								KasinoBot.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										25
									
								
								KasinoBot.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								KasinoBot.sln
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										164
									
								
								Music.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								Music.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										80
									
								
								PlayerController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								PlayerController.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
|  | ||||
| namespace KasinoBot | ||||
| { | ||||
|     class Program | ||||
|     { | ||||
|  | ||||
|         static void Main(string[] args) | ||||
|         { | ||||
|             Client client = new Client(); | ||||
|             client.Initialize().Wait(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| { | ||||
|   "profiles": { | ||||
|     "KasinoBot": { | ||||
|       "commandName": "Project", | ||||
|       "environmentVariables": { | ||||
|         "TOKEN": "ODg3Mzc0OTczNjA0MzU2MTA2.YUDOWA.tI--BKjpUHBHdiHZLjNNaZxniec" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user