diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 4f8ea81..95e7a78 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -75,3 +75,24 @@ jobs: - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} + + deploy: + name: Update running container + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Recreate container + uses: appleboy/ssh-action@v0.1.7 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_KEY }} + port: ${{ secrets.DEPLOY_PORT }} + script: | + cd docker/kasbot + docker-compose rm + docker-compose pull + docker-compose up -d + \ No newline at end of file diff --git a/Modules/PublicModule.cs b/Modules/PublicModule.cs index 252e46c..5e02901 100644 --- a/Modules/PublicModule.cs +++ b/Modules/PublicModule.cs @@ -50,7 +50,16 @@ namespace TextCommandFramework.Modules var user = Context.User; if (user.IsBot) return; - await PlayerService.Stop(Context); + await PlayerService.Stop(Context.Guild.Id); + } + + [Command("leave", RunMode = RunMode.Async)] + public async Task LeaveAsync() + { + var user = Context.User; + if (user.IsBot) return; + + await PlayerService.Stop(Context.Guild.Id); } [Command("guild_only")] diff --git a/Program.cs b/Program.cs index 86a6606..edd7d88 100644 --- a/Program.cs +++ b/Program.cs @@ -28,6 +28,8 @@ namespace TextCommandFramework var client = services.GetRequiredService(); client.Log += LogAsync; + client.LoggedIn += () => Client_LoggedIn(client); + client.Ready += () => Client_Ready(client); services.GetRequiredService().Log += LogAsync; await client.LoginAsync(TokenType.Bot, token); @@ -39,6 +41,34 @@ namespace TextCommandFramework } } + private async Task Client_Ready(DiscordSocketClient client) + { + var announceLoginGuild = ulong.Parse(Environment.GetEnvironmentVariable("ANNOUNCE_LOGIN_GUILD") ?? "0"); + var announceLoginChannel = ulong.Parse(Environment.GetEnvironmentVariable("ANNOUNCE_LOGIN_CHANNEL") ?? "0"); + + if (announceLoginGuild == 0 || announceLoginChannel == 0) + { + return; + } + + var channel = client.GetGuild(announceLoginGuild).GetTextChannel(announceLoginChannel); + + if (channel == null) + { + Console.WriteLine("Announce channel not found."); + return; + } + + await channel.SendMessageAsync("@everyone LIVE!"); + } + + private Task Client_LoggedIn(DiscordSocketClient client) + { + Console.WriteLine("Successfully logged in!"); + + return Task.CompletedTask; + } + private Task LogAsync(LogMessage log) { Console.WriteLine(log.ToString()); diff --git a/Services/PlayerService.cs b/Services/PlayerService.cs index 38909ca..a5ba045 100644 --- a/Services/PlayerService.cs +++ b/Services/PlayerService.cs @@ -10,77 +10,84 @@ namespace Kasbot.Services { public class PlayerService { - public Dictionary Clients { get; set; } + public Dictionary Clients { get; set; } public PlayerService() { - Clients = new Dictionary(); + Clients = new Dictionary(); } - private async Task DownloadAudioFromYoutube(string youtubeUrl) + private async Task DownloadAudioFromYoutube(string youtubeUrl) { + var memoryStream = new MemoryStream(); var youtube = new YoutubeClient(); + var videoId = await youtube.Search.GetVideosAsync(youtubeUrl).FirstOrDefaultAsync(); var streamInfoSet = await youtube.Videos.Streams.GetManifestAsync(videoId.Id); var streamInfo = streamInfoSet.GetAudioOnlyStreams().GetWithHighestBitrate(); var streamVideo = await youtube.Videos.Streams.GetAsync(streamInfo); - var fileName = $"{videoId.Id}.mp3"; + streamVideo.Position = 0; + + streamVideo.CopyTo(memoryStream); + memoryStream.Position = 0; - using (var fileStream = new FileStream(fileName, FileMode.Create)) - { - await streamVideo.CopyToAsync(fileStream); - } - return fileName; + return memoryStream; } public async Task Play(SocketCommandContext Context, string arguments) { IVoiceChannel channel = (Context.User as IVoiceState).VoiceChannel; - string filename; + var mp3Stream = await DownloadAudioFromYoutube(arguments); + var ffmpeg = CreateStream(); - try - { - filename = await DownloadAudioFromYoutube(arguments); - } - catch (Exception ex) - { - throw new Exception("Error while downloading video from YouTube!"); - } - var audioClient = await channel.ConnectAsync(); - - using (var ffmpeg = CreateStream(filename)) - using (var output = ffmpeg.StandardOutput.BaseStream) - using (var discord = audioClient.CreatePCMStream(AudioApplication.Mixed)) + audioClient.ClientDisconnected += AudioClient_ClientDisconnected; + var media = new Media() { - try + AudioClient = audioClient, + Message = Context.Message, + Name = "" + }; + Clients.Add(Context.Guild.Id, media); + + Task stdin = new Task(() => + { + using (var input = mp3Stream) { - var media = new Media() - { - AudioClient = audioClient, - AudioOutStream = discord, - FileName = filename, - Message = Context.Message, - Name = "" - }; - Clients.Add(Context.Guild, media); - await output.CopyToAsync(discord); + input.CopyTo(ffmpeg.StandardInput.BaseStream); } - finally + }); + + Task stdout = new Task(() => + { + using (var output = ffmpeg.StandardOutput.BaseStream) + using (var discord = audioClient.CreatePCMStream(AudioApplication.Music)) { - await discord.FlushAsync(); - File.Delete(filename); + output.CopyTo(discord); + discord.Flush(); } - } + }); + + stdin.Start(); + stdout.Start(); + + Task.WaitAll(stdin, stdout); + + ffmpeg.WaitForExit(); } - private Process CreateStream(string filename) + private async Task AudioClient_ClientDisconnected(ulong arg) + { + await Stop(arg); + } + + private Process CreateStream() { var process = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", - Arguments = $"-hide_banner -loglevel panic -i {filename} -ac 2 -f s16le -ar 48000 pipe:1", + Arguments = $"-hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1", UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true @@ -88,36 +95,32 @@ namespace Kasbot.Services if (process == null || process.HasExited) { - throw new Exception("Sorry, ffmpeg killed himself! Bah."); + throw new Exception("Sorry, ffmpeg killed himself in a tragic accident!"); } return process; } - public async Task Stop(SocketCommandContext Context) + public async Task Stop(ulong guildId) { - if (!Clients.ContainsKey(Context.Guild)) + if (!Clients.ContainsKey(guildId)) return; - var media = Clients[Context.Guild]; - Clients.Remove(Context.Guild); + var media = Clients[guildId]; - File.Delete(media.FileName); - await Context.Message.DeleteAsync(); await media.Message.DeleteAsync(); - await media.AudioOutStream.DisposeAsync(); - await media.AudioOutStream.ClearAsync(new CancellationToken()); await media.AudioClient.StopAsync(); - } + Clients.Remove(guildId); + } } public class Media { public string Name { get; set; } + public int Length { get; set; } public string FileName { get; set; } public IAudioClient AudioClient { get; set; } - public AudioOutStream AudioOutStream { get; set; } public SocketUserMessage Message { get; set; } } }