This commit is contained in:
José Henrique Ivanchechen 2022-12-10 15:49:16 -03:00
commit f501a71033
10 changed files with 492 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.vs*
bin/
obj/

88
Client.cs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,15 @@

namespace KasinoBot
{
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Initialize().Wait();
}
}
}

View File

@ -0,0 +1,10 @@
{
"profiles": {
"KasinoBot": {
"commandName": "Project",
"environmentVariables": {
"TOKEN": "ODg3Mzc0OTczNjA0MzU2MTA2.YUDOWA.tI--BKjpUHBHdiHZLjNNaZxniec"
}
}
}
}