Initial commit
This commit is contained in:
parent
2998d33d33
commit
8216e6c85a
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
appsettings.*.json
|
||||||
|
.env*
|
||||||
|
|
||||||
|
.vs
|
||||||
|
.vscode
|
||||||
|
bin
|
||||||
|
obj
|
17
Kasbot.csproj
Normal file
17
Kasbot.csproj
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<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.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
|
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||||
|
<PackageReference Include="YoutubeExplode" Version="6.2.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
25
Kasbot.sln
Normal file
25
Kasbot.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33213.308
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kasbot", "Kasbot.csproj", "{70A0CD18-5914-4104-A1A1-C531B96FCC20}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{70A0CD18-5914-4104-A1A1-C531B96FCC20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{70A0CD18-5914-4104-A1A1-C531B96FCC20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{70A0CD18-5914-4104-A1A1-C531B96FCC20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{70A0CD18-5914-4104-A1A1-C531B96FCC20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {65C82F99-E6D5-41DC-AA40-7B12A66657BF}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
64
Modules/PublicModule.cs
Normal file
64
Modules/PublicModule.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Audio;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Kasbot.Services;
|
||||||
|
using NAudio.Wave;
|
||||||
|
using TextCommandFramework.Services;
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
namespace TextCommandFramework.Modules
|
||||||
|
{
|
||||||
|
public class PublicModule : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
public PictureService PictureService { get; set; }
|
||||||
|
public PlayerService PlayerService { get; set; }
|
||||||
|
|
||||||
|
[Command("ping")]
|
||||||
|
[Alias("pong", "hello")]
|
||||||
|
public Task PingAsync()
|
||||||
|
=> ReplyAsync("pong!");
|
||||||
|
|
||||||
|
[Command("cat")]
|
||||||
|
public async Task CatAsync()
|
||||||
|
{
|
||||||
|
var stream = await PictureService.GetCatPictureAsync();
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
await Context.Channel.SendFileAsync(stream, "cat.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("echo")]
|
||||||
|
public Task EchoAsync([Remainder] string text)
|
||||||
|
=> ReplyAsync('\u200B' + text);
|
||||||
|
|
||||||
|
[Command("play", RunMode = RunMode.Async)]
|
||||||
|
public async Task PlayAsync([Remainder] string text)
|
||||||
|
{
|
||||||
|
var user = Context.User;
|
||||||
|
if (user.IsBot) return;
|
||||||
|
|
||||||
|
string youtubeUrl = text;
|
||||||
|
IVoiceChannel channel = (Context.User as IVoiceState).VoiceChannel;
|
||||||
|
if (channel is null)
|
||||||
|
{
|
||||||
|
await Context.Channel.SendMessageAsync("You need to be in a voice channel to use this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await PlayerService.Play(Context, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("stop", RunMode = RunMode.Async)]
|
||||||
|
public async Task StopAsync()
|
||||||
|
{
|
||||||
|
var user = Context.User;
|
||||||
|
if (user.IsBot) return;
|
||||||
|
|
||||||
|
await PlayerService.Stop(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("guild_only")]
|
||||||
|
[RequireContext(ContextType.Guild, ErrorMessage = "Sorry, this command must be ran from within a server, not a DM!")]
|
||||||
|
public Task GuildOnlyCommand()
|
||||||
|
=> ReplyAsync("Nothing to see here!");
|
||||||
|
}
|
||||||
|
}
|
66
Program.cs
Normal file
66
Program.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Kasbot.Services;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using TextCommandFramework.Services;
|
||||||
|
|
||||||
|
namespace TextCommandFramework
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
|
using (var services = ConfigureServices())
|
||||||
|
{
|
||||||
|
var token = Environment.GetEnvironmentVariable("TOKEN");
|
||||||
|
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Discord Bot Token was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
|
||||||
|
client.Log += LogAsync;
|
||||||
|
services.GetRequiredService<CommandService>().Log += LogAsync;
|
||||||
|
|
||||||
|
await client.LoginAsync(TokenType.Bot, token);
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
|
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
||||||
|
|
||||||
|
await Task.Delay(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LogAsync(LogMessage log)
|
||||||
|
{
|
||||||
|
Console.WriteLine(log.ToString());
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceProvider ConfigureServices()
|
||||||
|
{
|
||||||
|
return new ServiceCollection()
|
||||||
|
.AddSingleton(new DiscordSocketConfig
|
||||||
|
{
|
||||||
|
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
|
||||||
|
})
|
||||||
|
.AddSingleton<DiscordSocketClient>()
|
||||||
|
.AddSingleton<CommandService>()
|
||||||
|
.AddSingleton<PlayerService>()
|
||||||
|
.AddSingleton<CommandHandlingService>()
|
||||||
|
.AddSingleton<HttpClient>()
|
||||||
|
.AddSingleton<PictureService>()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
Services/CommandHandlingService.cs
Normal file
60
Services/CommandHandlingService.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TextCommandFramework.Services
|
||||||
|
{
|
||||||
|
public class CommandHandlingService
|
||||||
|
{
|
||||||
|
private readonly CommandService _commands;
|
||||||
|
private readonly DiscordSocketClient _discord;
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
|
||||||
|
public CommandHandlingService(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;
|
||||||
|
var prefix = "!";
|
||||||
|
|
||||||
|
//Check if the message sent has the specified prefix
|
||||||
|
if (!message.HasStringPrefix(prefix, 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;
|
||||||
|
|
||||||
|
await context.Channel.SendMessageAsync($"error: {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Services/PictureService.cs
Normal file
20
Services/PictureService.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TextCommandFramework.Services
|
||||||
|
{
|
||||||
|
public class PictureService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
|
||||||
|
public PictureService(HttpClient http)
|
||||||
|
=> _http = http;
|
||||||
|
|
||||||
|
public async Task<Stream> GetCatPictureAsync()
|
||||||
|
{
|
||||||
|
var resp = await _http.GetAsync("https://cataas.com/cat");
|
||||||
|
return await resp.Content.ReadAsStreamAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
Services/PlayerService.cs
Normal file
87
Services/PlayerService.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Audio;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using NAudio.Wave;
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
namespace Kasbot.Services
|
||||||
|
{
|
||||||
|
public class PlayerService
|
||||||
|
{
|
||||||
|
public Dictionary<SocketGuild, Media> Clients { get; set; }
|
||||||
|
|
||||||
|
public PlayerService()
|
||||||
|
{
|
||||||
|
Clients = new Dictionary<SocketGuild, Media>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Stream> DownloadAudioFromYoutube(string youtubeUrl)
|
||||||
|
{
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
var videoId = await youtube.Search.GetVideosAsync(youtubeUrl).FirstOrDefaultAsync();
|
||||||
|
var streamInfoSet = await youtube.Videos.Streams.GetManifestAsync(videoId.Id);
|
||||||
|
var highestAudioStreamInfo = streamInfoSet.GetAudioStreams().OrderByDescending(s => s.Bitrate).FirstOrDefault();
|
||||||
|
var streamVideo = await youtube.Videos.Streams.GetAsync(highestAudioStreamInfo);
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
await streamVideo.CopyToAsync(memoryStream);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
return memoryStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Play(SocketCommandContext Context, string arguments)
|
||||||
|
{
|
||||||
|
IVoiceChannel channel = (Context.User as IVoiceState).VoiceChannel;
|
||||||
|
|
||||||
|
var audioStream = await DownloadAudioFromYoutube(arguments);
|
||||||
|
if (audioStream is null)
|
||||||
|
{
|
||||||
|
await Context.Channel.SendMessageAsync("Failed to download audio from YouTube.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var audioClient = await channel.ConnectAsync();
|
||||||
|
using (var mp3Reader = new StreamMediaFoundationReader(audioStream))
|
||||||
|
{
|
||||||
|
var audioOut = audioClient.CreatePCMStream(AudioApplication.Music);
|
||||||
|
await audioClient.SetSpeakingAsync(true);
|
||||||
|
|
||||||
|
var media = new Media
|
||||||
|
{
|
||||||
|
AudioClient = audioClient,
|
||||||
|
Message = Context.Message,
|
||||||
|
Name = "",
|
||||||
|
AudioOutStream = audioOut,
|
||||||
|
};
|
||||||
|
Clients.Add(Context.Guild, media);
|
||||||
|
|
||||||
|
await mp3Reader.CopyToAsync(audioOut);
|
||||||
|
await audioClient.SetSpeakingAsync(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop(SocketCommandContext Context)
|
||||||
|
{
|
||||||
|
if (!Clients.ContainsKey(Context.Guild))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var media = Clients[Context.Guild];
|
||||||
|
Clients.Remove(Context.Guild);
|
||||||
|
|
||||||
|
await Context.Message.DeleteAsync();
|
||||||
|
await media.Message.DeleteAsync();
|
||||||
|
await media.AudioOutStream.DisposeAsync();
|
||||||
|
await media.AudioOutStream.ClearAsync(new CancellationToken());
|
||||||
|
await media.AudioClient.StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Media
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public IAudioClient AudioClient { get; set; }
|
||||||
|
public AudioOutStream AudioOutStream { get; set; }
|
||||||
|
public SocketUserMessage Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user