commit b6abc9f5265fd97b4c8e9a90e0b7f5db7016776b Author: sim1222 Date: Tue Jul 25 01:16:16 2023 +0900 first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.DiscordAudioBot/.idea/workspace.xml b/.idea/.idea.DiscordAudioBot/.idea/workspace.xml new file mode 100644 index 0000000..073a55b --- /dev/null +++ b/.idea/.idea.DiscordAudioBot/.idea/workspace.xml @@ -0,0 +1,130 @@ + + + + DiscordAudioBot/DiscordAudioBot.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1689489047458 + + + + + + + + \ No newline at end of file diff --git a/Elementary.sln b/Elementary.sln new file mode 100644 index 0000000..d9251ea --- /dev/null +++ b/Elementary.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33815.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elementary", "Elementary\Elementary.csproj", "{E853B381-09A7-46CF-88CC-62C44B5ACA89}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Debug|x64.ActiveCfg = Debug|x64 + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Debug|x64.Build.0 = Debug|x64 + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Release|Any CPU.Build.0 = Release|Any CPU + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Release|x64.ActiveCfg = Release|x64 + {E853B381-09A7-46CF-88CC-62C44B5ACA89}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C3A79BA4-F57B-4B08-A752-906D0AF3800F} + EndGlobalSection +EndGlobal diff --git a/Elementary/Audio/AudioConverter.cs b/Elementary/Audio/AudioConverter.cs new file mode 100644 index 0000000..99cb0e3 --- /dev/null +++ b/Elementary/Audio/AudioConverter.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using NAudio; +using NAudio.Wave; +// using ManagedBass; +// using ManagedBass.Mix; + +namespace Elementary.Audio; + +public class AudioConverter +{ + public readonly WaveFormat WaveFormat = new(48000, 16, 2); + + public WaveFormatConversionStream CreateStreamFromFilePath(string path, float volume = 1.0f) + => new(WaveFormat, new VolumeAdjustedWaveStream(new MediaFoundationReader(path), volume)); + + /// + /// Return PCM stream from any codec of audio stream + /// + /// + /// + public WaveFormatConversionStream CreateStreamFromStream(Stream stream) + { + return new(WaveFormat, new StreamMediaFoundationReader(stream)); + } + + /// + /// Return PCM stream from any codec of audio stream + /// + /// + /// + /// + public WaveFormatConversionStream CreateStreamFromStream(Stream stream, float volume = 1.0f) + { + return new(WaveFormat, new VolumeAdjustedWaveStream(new StreamMediaFoundationReader(stream), volume)); + } +} \ No newline at end of file diff --git a/Elementary/Audio/AudioManager.cs b/Elementary/Audio/AudioManager.cs new file mode 100644 index 0000000..14722c8 --- /dev/null +++ b/Elementary/Audio/AudioManager.cs @@ -0,0 +1,93 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text.RegularExpressions; +using Discord; +using Discord.Audio; +using Discord.WebSocket; +using ManagedBass; +using Microsoft.Extensions.DependencyInjection; +using NAudio.Wave; + +namespace Elementary.Audio; + +public class AudioManager +{ + private DiscordSocketClient _client; + private IServiceProvider _services; + private IAudioClient _audioClient; + private AudioOutStream _audioStream; + + private SozaiAPI _sozaiAPI; + private VoicevoxAPI _voicevoxAPI; + + private AudioConverter _audioConverter; + + private PlaybackQueue _playbackQueue; + // private AudioMixer _audioMixer; + + public bool isConnected; + + public AudioManager(IServiceProvider services, DiscordSocketClient client, SozaiAPI sozaiApi, + VoicevoxAPI voicevoxApi) + { + _services = services; + _client = client; + _sozaiAPI = sozaiApi; + _voicevoxAPI = voicevoxApi; + _audioConverter = new(); + } + + public async Task JoinChannel(IVoiceChannel channel) + { + _audioClient = await channel.ConnectAsync(true); + + _audioStream = _audioClient.CreatePCMStream(AudioApplication.Music, 128 * 1024); + + _playbackQueue = _services.GetRequiredService(); + + // _audioMixer = new AudioMixer(_audioStream); + + isConnected = true; + } + + public async Task LeaveChannel() + { + await _audioClient.StopAsync(); + } + + public async Task PlayAudio(string path) + { + await using var wave = _audioConverter.CreateStreamFromFilePath(path, 0.15f); + + // _audioMixer.AddStream(wave); + await wave.CopyToAsync(_audioStream); + } + + public async Task PlayText(string text) + { + if (text.Contains("```")) text = "コードブロック"; + if (text.StartsWith("!") || text.StartsWith(".")) return; + text = Regex.Replace(text, @"https?://[\w/:%#\$&\?\(\)~\.=\+\-]+", "URL"); + + float volume = 0.12f; + + Stream? stream = await _sozaiAPI.GetAudioStream(text); + if (stream == null) + { + stream = await _voicevoxAPI.Speak(text); + volume = 0.8f; + if (stream == null) return; + } + + await using var wave = _audioConverter.CreateStreamFromStream(stream, volume); + // _audioMixer.AddStream(wave); + await wave.CopyToAsync(_audioStream); + } + + public async Task StopAudio() + { + // _audioMixer.Stop(); + _playbackQueue.Flush(); + await _audioStream.FlushAsync(); + } +} \ No newline at end of file diff --git a/Elementary/Audio/AudioMixer.cs b/Elementary/Audio/AudioMixer.cs new file mode 100644 index 0000000..f17180f --- /dev/null +++ b/Elementary/Audio/AudioMixer.cs @@ -0,0 +1,80 @@ +using ManagedBass; +using ManagedBass.Mix; + +namespace Elementary.Audio; + +public class AudioMixer +{ + private readonly int _mixerStream; + private MemoryStream?[] _inputStreams; + private Stream _outStream; + private object _lock = new(); + + public AudioMixer(Stream outStream) + { + _outStream = outStream; + Bass.Init(-1, 44100, DeviceInitFlags.NoSpeakerAssignment, IntPtr.Zero); + _mixerStream = BassMix.CreateMixerStream(44100, 2, BassFlags.Float | BassFlags.MixerNonStop); + _inputStreams = new MemoryStream[10]; + + Bass.ChannelPlay(_mixerStream); + } + + /// + /// Add a stream to the mixer. + /// wait for the stream to finish playing + /// + /// + /// + public void AddStream(Stream stream, float volume = 1.0f) + { + Console.WriteLine("Adding stream to mixer"); + byte[] pcmBytes; + using (MemoryStream ms = new()) + { + stream.CopyTo(ms); + pcmBytes = ms.ToArray(); + } + + lock (_lock) + { + for (var i = 0; i < _inputStreams.Length; i++) + { + if (_inputStreams[i] == null) + { + _inputStreams[i] = new MemoryStream(pcmBytes); + break; + } + } + } + + var channel = Bass.CreateStream(pcmBytes, 0, pcmBytes.Length, BassFlags.Float); + Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, volume); + BassMix.MixerAddChannel(_mixerStream, channel, BassFlags.Default); + + int length = Bass.ChannelGetData(_mixerStream, new byte[4096], 4096); + byte[] buffer = new byte[length]; + length = Bass.ChannelGetData(_mixerStream, buffer, length); + _outStream.Write(buffer, 0, length); + } + + public void Stop() + { + Bass.ChannelStop(_mixerStream); + } + + public void Dispose() + { + lock (_lock) + { + foreach (var stream in _inputStreams) + { + stream?.Dispose(); + } + } + + _outStream.Flush(); + Bass.StreamFree(_mixerStream); + Bass.Free(); + } +} \ No newline at end of file diff --git a/Elementary/Audio/IAudioConverter.cs b/Elementary/Audio/IAudioConverter.cs new file mode 100644 index 0000000..d28e210 --- /dev/null +++ b/Elementary/Audio/IAudioConverter.cs @@ -0,0 +1,10 @@ +using System.Diagnostics; + +namespace Elementary.Audio; + +public interface IAudioConverter +{ + public Process CreateStreamFromFilePath(string path); + + public Process CreateStreamFromStream(Stream stream); +} \ No newline at end of file diff --git a/Elementary/Audio/MessageHandler.cs b/Elementary/Audio/MessageHandler.cs new file mode 100644 index 0000000..365fdff --- /dev/null +++ b/Elementary/Audio/MessageHandler.cs @@ -0,0 +1,25 @@ +using Discord.Audio; +using Discord.WebSocket; + +namespace Elementary.Audio; + +public class MessageHandler +{ + // private IAudioClient _audioClient; + private PlaybackQueue _playbackQueue; + + public MessageHandler(PlaybackQueue playbackQueue) + { + _playbackQueue = playbackQueue; + } + + + public async Task HandleMessage(SocketMessage message) + { + await _playbackQueue.Enqueue(new PlaybackJob() + { + Type = JobType.Text, + Text = message.Content + }); + } +} \ No newline at end of file diff --git a/Elementary/Audio/PlaybackQueue.cs b/Elementary/Audio/PlaybackQueue.cs new file mode 100644 index 0000000..cf1fc24 --- /dev/null +++ b/Elementary/Audio/PlaybackQueue.cs @@ -0,0 +1,94 @@ +using System.Collections.Concurrent; +using Discord.Audio; +using NAudio.Wave; + +namespace Elementary.Audio; + +public enum JobType +{ + Audio, + Text, + // Sozai +} + +public class PlaybackJob +{ + public JobType Type; + public string Text; + public float Volume = 1.0f; +} + +public class PlaybackQueue +{ + // any type of job ConcurrentQueue + + private ConcurrentQueue _queue; + + private AudioManager _audioManager; + + private object _lock; + + private bool _isPlaying; + + public PlaybackQueue(AudioManager audioManager) + { + _queue = new(); + _isPlaying = false; + _lock = new(); + _audioManager = audioManager; + } + + /// + /// Enqueue audio stream and play it + /// + /// + public async Task Enqueue(PlaybackJob job) + { + _queue.Enqueue(job); + Console.WriteLine("Enqueued"); + if (!_isPlaying) + { + lock (_lock) + { + if (!_isPlaying) + { + Console.WriteLine("Start Playing due to empty queue"); + PlayNext(); + } + } + } + + await Task.CompletedTask; + } + + public void Flush() + { + _queue.Clear(); + Console.WriteLine("Queue Flushed"); + } + + private async void PlayNext() + { + if (_queue.TryDequeue(out var currentStream)) + { + Console.WriteLine("Start Playing"); + + _isPlaying = true; + await (currentStream.Type switch + { + JobType.Audio => _audioManager.PlayAudio(currentStream.Text), + JobType.Text => _audioManager.PlayText(currentStream.Text), + _ => throw new ArgumentOutOfRangeException() + }); + Console.WriteLine("Finished Playing"); + await Task.Delay(200); + _isPlaying = false; + PlayNext(); + } + else if (_queue.IsEmpty) + { + Console.WriteLine("Queue is empty"); + _isPlaying = false; + } + } +} \ No newline at end of file diff --git a/Elementary/Audio/SozaiAPI.cs b/Elementary/Audio/SozaiAPI.cs new file mode 100644 index 0000000..9978f8a --- /dev/null +++ b/Elementary/Audio/SozaiAPI.cs @@ -0,0 +1,52 @@ +using System.Text.Json; + +namespace Elementary.Audio; + +public class SozaiAPI +{ + private Uri AssetsInfoUrl = new("https://synchthia-sounds.storage.googleapis.com/index.json"); + private Asset[] Assets; + + private class Asset + { + /// + /// SHA1 hash of the file + /// + public string hash { get; set; } + + public string id { get; set; } + public string[] names { get; set; } + public string[] namespaces { get; set; } + public string path { get; set; } + public string url { get; set; } + } + + private HttpClient _client; + + public SozaiAPI() + { + _client = new HttpClient(); + } + + public async Task Setup() + { + var response = await _client.GetAsync(AssetsInfoUrl); + var stream = await response.Content.ReadAsStreamAsync(); + Assets = await JsonSerializer.DeserializeAsync(stream); + + Console.WriteLine($"Loaded {Assets.Length} assets"); + } + + public async Task GetAudioStream(string name) + { + var asset = Assets.FirstOrDefault(asset => asset.names.Contains(name)); + if (asset == null) return null; + + Console.WriteLine($"Requested {asset.names[0]}"); + + var response = await _client.GetAsync(asset.url); + Console.WriteLine($"Got response {response.StatusCode}"); + var stream = await response.Content.ReadAsStreamAsync(); + return stream; + } +} \ No newline at end of file diff --git a/Elementary/Audio/VoicevoxAPI.cs b/Elementary/Audio/VoicevoxAPI.cs new file mode 100644 index 0000000..4fb32ad --- /dev/null +++ b/Elementary/Audio/VoicevoxAPI.cs @@ -0,0 +1,58 @@ +using System.Net.Http.Json; +using System.Text; + +namespace Elementary.Audio; + +public class VoicevoxAPI +{ + private UriBuilder _APIRootUrl; + private HttpClient _client; + + public async Task Setup(string url) + { + Uri _url = new(url); + + _APIRootUrl = new UriBuilder($"{_url.Scheme}://{_url.Host}:{_url.Port}"); + _client = new HttpClient(); + } + + /// + /// Returns a stream of the 24000Hz wave audio generated from the text + /// + /// + /// + /// + public async Task Speak(string text, string speaker = "47") + { + Console.WriteLine($"Requested TTS {text}"); + + var query = await GetAudioQuery(text, speaker); + if (query == null) return null; + var stream = await GetAudioStream(query, speaker); + if (stream == null) return null; + return stream; + } + + internal async Task GetAudioQuery(string text, string speaker = "1") + { + UriBuilder builder = new(_APIRootUrl.Uri); + builder.Path = "/audio_query"; + builder.Query = $"text={text}&speaker={speaker}"; + + var response = await _client.PostAsync(builder.Uri, new StringContent(string.Empty)); + var stream = await response.Content.ReadAsStreamAsync(); + return await new StreamReader(stream).ReadToEndAsync(); + } + + internal async Task GetAudioStream(string query, string speaker = "1") + { + UriBuilder builder = new(_APIRootUrl.Uri); + builder.Path = "/synthesis"; + builder.Query = $"speaker={speaker}"; + + var response = + await _client.PostAsync(builder.Uri, new StringContent(query, Encoding.UTF8, "application/json")); + var stream = await response.Content.ReadAsStreamAsync(); + return stream; + } +} \ No newline at end of file diff --git a/Elementary/Audio/VolumeAdjustedWaveStream.cs b/Elementary/Audio/VolumeAdjustedWaveStream.cs new file mode 100644 index 0000000..ef60908 --- /dev/null +++ b/Elementary/Audio/VolumeAdjustedWaveStream.cs @@ -0,0 +1,52 @@ +using NAudio.Wave; + +namespace Elementary.Audio; + +public class VolumeAdjustedWaveStream : WaveStream +{ + private readonly WaveStream _waveStream; + private readonly float _volume; + + public VolumeAdjustedWaveStream(WaveStream waveStream, float volume) + { + _waveStream = waveStream; + _volume = volume; + } + + public override WaveFormat WaveFormat => _waveStream.WaveFormat; + + public override long Length => _waveStream.Length; + + public override long Position + { + get => _waveStream.Position; + set => _waveStream.Position = value; + } + + public override int Read(byte[] buffer, int offset, int count) + { + var read = _waveStream.Read(buffer, offset, count); + try + { + for (var i = 0; i < read; i += 2) + { + var sample = (short) ((buffer[offset + i + 1] << 8) | buffer[offset + i]); + sample = (short) (sample * _volume); + buffer[offset + i] = (byte) (sample & 0xFF); + buffer[offset + i + 1] = (byte) (sample >> 8); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return read; + } + + protected override void Dispose(bool disposing) + { + // _waveStream.Dispose(); + // base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/Elementary/Commands/Handler.cs b/Elementary/Commands/Handler.cs new file mode 100644 index 0000000..0f03afe --- /dev/null +++ b/Elementary/Commands/Handler.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using Discord.Audio; +using Discord.Commands; +using Discord.WebSocket; +using Elementary.Audio; + +namespace Elementary.Commands; + +public class Handler +{ + private readonly DiscordSocketClient _client; + private readonly CommandService _commands; + private readonly IServiceProvider _services; + private readonly AudioManager _audioManager; + private readonly MessageHandler _messageHandler; + + // Retrieve client and CommandService instance via ctor + public Handler(DiscordSocketClient client, CommandService commands, IServiceProvider services, + AudioManager audioManager, MessageHandler messageHandler) + { + _commands = commands; + _client = client; + _services = services; + _audioManager = audioManager; + _messageHandler = messageHandler; + } + + public async Task Setup() + { + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + + _client.MessageReceived += HandleMessage; + } + + private async Task HandleMessage(SocketMessage messageParam) + { + var message = messageParam as SocketUserMessage; + if (message == null || message.Author.IsBot) return; + + var argPos = 0; + + if (message.HasCharPrefix('!', ref argPos) || + message.HasMentionPrefix(_client.CurrentUser, ref argPos)) + { + SocketCommandContext context = new(_client, message); + + await _commands.ExecuteAsync( + context, + argPos, + _services); + } + + if (_audioManager.isConnected) + { + Console.WriteLine("Handling message"); + await _messageHandler.HandleMessage(message); + } + } +} \ No newline at end of file diff --git a/Elementary/Commands/MessageCommands.cs b/Elementary/Commands/MessageCommands.cs new file mode 100644 index 0000000..f5ecf80 --- /dev/null +++ b/Elementary/Commands/MessageCommands.cs @@ -0,0 +1,80 @@ +using Discord; +using Discord.Commands; +using Elementary.Audio; + +namespace Elementary.Commands; + +public class MessageCommands : ModuleBase +{ + private AudioManager _audioManager; + private readonly IServiceProvider _serviceProvider; + private readonly CommandService _commandService; + + public MessageCommands(AudioManager audioManager, CommandService commandService, IServiceProvider serviceProvider) + { + _audioManager = audioManager; + _commandService = commandService; + _serviceProvider = serviceProvider; + } + + [Command("help")] + [Summary("Displays a list of commands.")] + public async Task HelpAsync() + { + EmbedBuilder embedBuilder = new() + { + Title = "Elementary Commands", + Description = "A list of commands for Elementary.", + Color = Color.Blue + }; + + foreach (var module in _commandService.Modules) + { + string description = null; + foreach (var command in module.Commands) + { + var result = await command.CheckPreconditionsAsync(Context, _serviceProvider); + if (result.IsSuccess) description += $"!{command.Name} - {command.Summary}\n"; + } + + if (!string.IsNullOrWhiteSpace(description)) embedBuilder.AddField(module.Name, description); + } + + await ReplyAsync(embed: embedBuilder.Build()); + } + + [Command("say")] + [Summary("Echoes a message.")] + public Task SayAsync([Remainder] [Summary("The text to echo")] string echo) + { + return ReplyAsync(echo); + } + + [Command("join", RunMode = RunMode.Async)] + [Summary("Joins a voice channel.")] + public async Task JoinAsync(IVoiceChannel channel = null) + { + await _audioManager.JoinChannel(channel ?? (Context.User as IGuildUser)?.VoiceChannel); + } + + [Command("leave", RunMode = RunMode.Async)] + [Summary("Leaves a voice channel.")] + public async Task LeaveAsync() + { + await _audioManager.LeaveChannel(); + } + + [Command("play", RunMode = RunMode.Async)] + [Summary("Plays audio from a file.")] + public async Task PlayAsync([Remainder] [Summary("The path to the file")] string path) + { + await _audioManager.PlayAudio(path); + } + + [Command("stop", RunMode = RunMode.Async)] + [Summary("Stops playing audio.")] + public async Task StopAsync() + { + await _audioManager.StopAudio(); + } +} \ No newline at end of file diff --git a/Elementary/Config/Configuration.cs b/Elementary/Config/Configuration.cs new file mode 100644 index 0000000..c11915b --- /dev/null +++ b/Elementary/Config/Configuration.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration.Json; + +namespace Elementary.Config; + +public class AppSettings +{ + public DiscordSettings DiscordSettings { get; set; } +} + +public class DiscordSettings +{ + public string Token { get; set; } +} + +public class Configuration +{ + public AppSettings AppSettings { get; set; } + + public static Configuration LoadFromJson() + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", false, true); + var configuration = builder.Build(); + + var config = new Configuration(); + configuration.Bind(config); + return config; + } +} \ No newline at end of file diff --git a/Elementary/Dockerfile b/Elementary/Dockerfile new file mode 100644 index 0000000..4e2bd1b --- /dev/null +++ b/Elementary/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /src +COPY ["DiscordAudioBot/DiscordAudioBot.csproj", "DiscordAudioBot/"] +RUN dotnet restore "DiscordAudioBot/DiscordAudioBot.csproj" +COPY . . +WORKDIR "/src/DiscordAudioBot" +RUN dotnet build "DiscordAudioBot.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "DiscordAudioBot.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DiscordAudioBot.dll"] diff --git a/Elementary/Elementary.csproj b/Elementary/Elementary.csproj new file mode 100644 index 0000000..e84cb02 --- /dev/null +++ b/Elementary/Elementary.csproj @@ -0,0 +1,52 @@ + + + + Exe + net7.0 + enable + enable + Linux + Debug;Release + AnyCPU;x64 + + + + + .dockerignore + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/Elementary/Program.cs b/Elementary/Program.cs new file mode 100644 index 0000000..cbc2283 --- /dev/null +++ b/Elementary/Program.cs @@ -0,0 +1,76 @@ +using System.Collections.Concurrent; +using System.Reflection; +using Discord; +using Discord.Audio; +using Discord.Commands; +using Discord.WebSocket; +using Elementary.Audio; +using Elementary.Commands; +using ManagedBass; +using Microsoft.Extensions.DependencyInjection; +using NAudio.Wave; +using Configuration = Elementary.Config.Configuration; + +namespace Elementary; + +public class Program +{ + private DiscordSocketClient _client; + private IServiceProvider _services; + private CommandService _commands; + private Handler _handler; + private SozaiAPI _sozaiAPI; + private VoicevoxAPI _voicevoxAPI; + + public static void Main(string[] args) + { + new Program().Setup().GetAwaiter().GetResult(); + } + + private async Task Setup() + { + var configuration = Configuration.LoadFromJson(); + _client = new DiscordSocketClient(new DiscordSocketConfig + { + GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent + }); + _commands = new CommandService(); + _services = new ServiceCollection() + .AddSingleton(_client) + .AddSingleton(_commands) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddTransient() + .AddSingleton() + .BuildServiceProvider(); + + _sozaiAPI = _services.GetRequiredService(); + _voicevoxAPI = _services.GetRequiredService(); + + _handler = new Handler(_client, _commands, _services, _services.GetRequiredService(), + _services.GetRequiredService()); + + _client.Log += Log; + _commands.Log += Log; + + // Bass.Init(-1, 44100, DeviceInitFlags.NoSpeakerAssignment, IntPtr.Zero); + + await _handler.Setup(); + await _sozaiAPI.Setup(); + await _voicevoxAPI.Setup("http://localhost:50021"); + await _client.LoginAsync(TokenType.Bot, configuration.AppSettings.DiscordSettings.Token); + await _client.StartAsync(); + + + await Task.Delay(-1); //keep the program running + } + + private static Task Log(LogMessage msg) + { + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Elementary/appsettings.json b/Elementary/appsettings.json new file mode 100644 index 0000000..0e85222 --- /dev/null +++ b/Elementary/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "AppSettings": { + "DiscordSettings": { + "Token": "MTEzMDAzMjkxMTQzMzg1OTE5Mw.G8yVqt.HrC65f0t4dvZQIZ7iER4CV70pLvhAl8PIyknSs" + } + } +} \ No newline at end of file diff --git a/Elementary/bass.dll b/Elementary/bass.dll new file mode 100644 index 0000000..5ba4da7 Binary files /dev/null and b/Elementary/bass.dll differ diff --git a/Elementary/bassmix.dll b/Elementary/bassmix.dll new file mode 100644 index 0000000..a34eedc Binary files /dev/null and b/Elementary/bassmix.dll differ diff --git a/Elementary/libsodium.dll b/Elementary/libsodium.dll new file mode 100644 index 0000000..cd122b7 Binary files /dev/null and b/Elementary/libsodium.dll differ diff --git a/Elementary/opus.dll b/Elementary/opus.dll new file mode 100644 index 0000000..74a8e35 Binary files /dev/null and b/Elementary/opus.dll differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b74ca19 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Elementary + +## Elementary is a simple, lightweight, and easy to use Discord bot written in C# + +### Features + +- TTS +- Music +- Talking \ No newline at end of file