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