discord.net備忘録【C#】

discord botがC#で作れる？！となって早速作っているので備忘録
discord.netの参考記事少ない・・・ということで
私も情報提供位しよう。（という名の備忘録）

# 導入
[こちら](http://atriasoft.work/cs/537 "【C#】Discord .NETでbotを作る")のページを参考に初期の初期は作成

# 制作物

## 管理ファイル
ここでbotの諸々情報を設定する

```ini:kanri.ini
;----------------------
; botの設定
;----------------------
[bot]
; トークン
Token=
; 接頭文字
BotUse=/

;----------------------
; マスタIDの設定
;----------------------
[Master]
; bot所有者
Owner=
; ログ
Log=

;----------------------
; 各種ID設定
;----------------------
[Id]
; 認証サーバー
ServerID=
```

#### トークン
botのトークンを記載。
#### 接頭文字
botを動かすときに頭につける文字の指定。
この文字+コマンド名でbotを操作する。
#### bot所有者
botの所有者のユーザーIDを記載。所有者限定コマンド等。
#### ログ
botのログ出力チャンネルIDを記載。
#### 認証サーバー
botを認証させるサーバーID。特定のサーバー以外では使えなくする。
## botのメイン

```cs:Program.cs

using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
namespace discordbot1
{
    class Program
    {
        private DiscordSocketClient client;
        public static CommandService commands;
        public static IServiceProvider services;

        private string ErrMsg = null;
        private static ulong serverid = ulong.Parse(Config.ID.License);

        static void Main(string[] args)
            => new Program().MainAsync().GetAwaiter().GetResult();

        public async Task MainAsync()
        {
            client = new DiscordSocketClient(new DiscordSocketConfig
            {
                LogLevel = LogSeverity.Info
            });
            client.Log += Log;
            commands = new CommandService();
            services = new ServiceCollection().BuildServiceProvider();
            client.MessageReceived += CommandRecieved;
            string token = Config.bot.BotToken;
            await commands.AddModulesAsync(Assembly.GetEntryAssembly(), services);
            await client.LoginAsync(TokenType.Bot, token);
            await client.StartAsync();

            await Task.Delay(-1);
        }

        /// <summary>
        /// 何かしらのメッセージの受信
        /// </summary>
        /// <param name="msgParam"></param>
        /// <returns></returns>
        private async Task CommandRecieved(SocketMessage messageParam)
        {
            var message = messageParam as SocketUserMessage;

            //メッセージがnullの場合
            if (message == null)
                return;

            //発言者がBotの場合無視する
            if (message.Author.IsBot)
                return;
            try
            {
                var context = new CommandContext(client, message);

                var CommandContext = message.Content;
                /* ----- ▲ ----- ここまでは導入部のソースマルコピ ----- ▲ ----- */

                //接頭語が規定でない場合無視(1)
                if (Common._Left_(CommandContext, Config.bot.StrBotUse.Length) != Config.bot.StrBotUse) { return; }
                else
                {
                    CommandContext = Common._Right_(CommandContext, CommandContext.Length - Config.bot.StrBotUse.Length);
                }

                // サーバー登録されているか(2)
                if (context.Guild.Id != serverid)
                {
                    await Logger.warning(message, client, "サーバー認証失敗", "サーバーの登録がされていません");
                    return;
                }

                // コマンド判定
                if (CommandContext == "ロール付与") //(3)
                {
                    var role = context.Guild.Roles.FirstOrDefault(x => x.Name == "お手伝い");
                    await (context.User as IGuildUser).AddRoleAsync(role);
                    
                }
                else if (Common._Left_(CommandContext, 5) == "引数確認 ") //(4)
                {
                    CommandContext = Common._Right_(CommandContext, CommandContext.Length - 5);
                    string[] hikisuu = Common._argument_(CommandContext);
                    string[] field = new string[hikisuu.Length];
                    for (int i = 0; i < hikisuu.Length; i++)
                    {
                        field[i] = "引数" + (i + 1);
                    }
                    await Logger.CommandMessage(message, client, "引数確認", "入力された引数は以下の通りです", field, hikisuu, true);
                }
                else if (CommandContext == "shutdown") //(5)
                {
                    await Logger.infomation(message, client, "システムを終了します。", "再起動は所有者までお問い合わせ下さい");
                    Environment.Exit(0);
                }
                else // (6)
                {
                    await Logger.warning(message, client, "コマンドが見つかりませんでした", Config.bot.StrBotUse + "helpで使い方を確認");
                }
            }
            catch (Exception ex) // (7)
            {
                await Logger.error(message, client, "エラーが発生しました。", ex.Message.ToString(), "CommandRecieved");
            }
        }
/* ----- ▼ ----- ここからは導入部のソースマルコピ ----- ▼ ----- */
        private Task Log(LogMessage message)
        {
            Console.WriteLine(message.ToString());
            return Task.CompletedTask;
        }
    }
}
```

#### (1)接頭文字判定
接頭文字でこのbotを使おうとしたか判定する
#### (2)サーバー登録判定
メッセージの送信されたサーバーIDが認証サーバーで登録されているか判定する。
#### (3)ロール付与
お手伝いという名前のロールを付与する。
x.IdにするとロールIDで判定
#### (4)引数確認
スペース単位で区切った引数を確認する。
#### (5)シャットダウン
discord上からbotのシステムを落とすコマンド。
#### (6)見つからなかった場合
見つからなかった場合はメッセージを送信。(尚、現時点でhelpコマンドは未実装)
#### (7)その他、エラーが発生した場合
エラーメッセージをログ出力。
## 埋め込みメッセージで応答（送ったチャンネル＋ログチャンネル）

```cs:Logger.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;

namespace discordbot1
{
    class Logger
    {
        private static ulong channelid = ulong.Parse(Config.Master.Log);

        public static async Task CommandMessage(SocketUserMessage message, DiscordSocketClient client, string title, string detail,string[] field,string[]value,bool Inline = false)
        {
            var embed = new EmbedBuilder();
            if (title != null) { embed.WithTitle(title); }
            if (detail != null) { embed.WithDescription(detail); }
            for (int i = 0; i < field.Length; i++)
            {
                string fld = null;
                string val = null;
                try
                {
                    fld = field[i];
                    val = value[i];
                }
                finally
                {
                    if (!String.IsNullOrEmpty(fld)&& !String.IsNullOrEmpty(val)) 
                    {
                        embed.AddField(fld, val, Inline); 
                    }
                }
                
            }

                embed.WithColor(Color.Green);
            embed.WithFooter(message.Author.Username);
            embed.WithTimestamp(DateTime.Now);

            var chatchannnel = client.GetChannel(channelid) as SocketTextChannel;
            await chatchannnel.SendMessageAsync(embed: embed.Build());
            Console.WriteLine("Message:" + detail);
            await message.Channel.SendMessageAsync(embed: embed.Build());
        }


        public static async Task infomation(SocketUserMessage message, DiscordSocketClient client, string title, string detail)
        {
            var embed = new EmbedBuilder();
            if (title != null) { embed.WithTitle(title); }
            if (detail != null) { embed.WithDescription(detail); }
            embed.WithColor(Color.Blue);
            embed.WithFooter(message.Author.Username);
            embed.WithTimestamp(DateTime.Now);

            try
            {          
                var chatchannnel = client.GetChannel(channelid) as SocketTextChannel;
                await chatchannnel.SendMessageAsync(embed: embed.Build());
            }
            finally
            {
                Console.WriteLine("Info:" + detail);
                await message.Channel.SendMessageAsync(embed: embed.Build());
            }
        }
        public static async Task warning(SocketUserMessage message, DiscordSocketClient client, string title, string detail)
        {
            var embed = new EmbedBuilder();
            if (title != null) { embed.WithTitle(title); }
            if (detail != null) { embed.WithDescription(detail); }
            embed.WithColor(Color.Gold);
            embed.WithFooter(message.Author.Username);
            embed.WithTimestamp(DateTime.Now);

            try
            {
                var chatchannnel = client.GetChannel(channelid) as SocketTextChannel;
                await chatchannnel.SendMessageAsync(embed: embed.Build());
            }
            finally
            {
                Console.WriteLine("Warning:" + detail);
                await message.Channel.SendMessageAsync(embed: embed.Build());
            }
        }
        public static async Task error(SocketUserMessage message, DiscordSocketClient client, string title, string detail, string modulename)
        {
            var embed = new EmbedBuilder();
            embed.WithTitle(title);
            embed.WithDescription(detail);
            embed.WithColor(Color.Red);
            embed.WithFooter(modulename);
            embed.WithTimestamp(DateTime.Now);

            try
            {
                var chatchannnel = client.GetChannel(channelid) as SocketTextChannel;
                await chatchannnel.SendMessageAsync(embed: embed.Build());
            }
            finally
            {
                Console.WriteLine("Error:" + detail);
                embed.WithDescription("詳細はログチャンネルをご確認ください。");
                embed.WithFooter(message.Author.Username);
                await message.Channel.SendMessageAsync(embed: embed.Build());
            }
        }
    }
}


```

#### 基本
送信のあったチャンネルとログチャンネルに情報を埋め込みメッセージで送信する。
#### エラー
送信のあったチャンネルにはエラーが発生した旨を送り、ログチャンネルにはエラー詳細を送る。


## 管理ファイルの読み書きあれこれ

```cs:Config.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace discordbot1
{
    class Config
    {
        #region INIファイルアクセス用API関数定義
        [DllImport("KERNEL32.DLL")]
        private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName);

        [DllImport("KERNEL32.DLL")]
        private static extern uint WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName);


        #endregion
        /// <summary>
        /// iniファイル名
        /// </summary>
        private const string CONFIG_FILE_NAME = "kanri.ini";
        /// <summary>
        /// iniファイルパス
        /// </summary>
        private static string IniFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, CONFIG_FILE_NAME);

        /// <summary>
        /// iniファイル文字列取得
        /// </summary>
        /// <param name="section">セクション</param>
        /// <param name="key">キー</param>
        /// <param name="defaultValue">デフォルト値</param>
        /// <returns></returns>
        private static string GetConfigString(string section, string key, string defaultValue)
        {
            StringBuilder sb = new StringBuilder(1024);
            GetPrivateProfileString(section, key, defaultValue, sb, (uint)sb.Capacity, IniFilePath);
            return sb.ToString();
        }
        /// <summary>
        /// iniファイル文字列設定
        /// </summary>
        /// <param name="section">セクション</param>
        /// <param name="key">キー</param>
        /// <param name="Value">設定値</param>
        private static void SetConfigString(string section, string key, string Value)
        {
            WritePrivateProfileString(section, key, Value, IniFilePath);
        }
        /// <summary>
        /// botセクション
        /// </summary>
        public class bot
        {
            /// <summary>
            /// セクションキー
            /// </summary>
            private const string SECTION = "bot";
            public static string BotToken
            {
                get { 
                    string ret = GetConfigString(SECTION, "Token", null);
                    if (string.IsNullOrEmpty(ret))
                    {
                        throw new Exception("BOTトークンの取得に失敗しました。");
                    }
                    return ret;
                }
            }
            /// <summary>
            /// 接頭文字
            /// </summary>
            public static string StrBotUse
            {
                get { return GetConfigString(SECTION, "BotUse", "/"); }
            }

        }
        /// <summary>
        /// idセクション
        /// </summary>
        public class ID
        {
            /// <summary>
            /// セクションキー
            /// </summary>
            private const string SECTION = "Id";
            public static string License
            {
                get
                {
                    string ret = GetConfigString(SECTION, "ServerID", null);
                    if (string.IsNullOrEmpty(ret))
                    {
                        throw new Exception("認証サーバーIDの取得に失敗しました。");
                    }
                    return ret;
                }
            }
        }
        /// <summary>
        /// マスタセクション
        /// </summary>
        public class Master
        {
            /// <summary>
            /// セクションキー
            /// </summary>
            private const string SECTION = "Master";
            public static string Owner
            {
                get
                {
                    string ret = GetConfigString(SECTION, "Owner", null);
                    if (string.IsNullOrEmpty(ret))
                    {
                        throw new Exception("所有者の取得に失敗しました。");
                    }
                    return ret;
                }
            }
            public static string Log
            {
                get
                {
                    string ret = GetConfigString(SECTION, "Log", null);
                    if (string.IsNullOrEmpty(ret))
                    {
                        throw new Exception("Logチャンネルの取得に失敗しました。");
                    }
                    return ret;
                }
            }

        }
        

    }
}

```

#### 基本
iniファイルの読み出し、書き込みをあらかじめ定義しておく。

## なんとなく便利なやつ
[参考](https://dobon.net/vb/dotnet/vb2cs/mid.html "C#でMid、Left、Right関数の代わりになるものは？")

```cs:Common.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace discordbot1
{
    class Common
    {
        /// <summary>
        /// 文字列の指定した位置から指定した長さを取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="start">開始位置</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string _Mid_(string str, int start, int len)
        {
            if (start <= 0)
            {
                throw new ArgumentException("引数'start'は1以上でなければなりません。");
            }
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null || str.Length < start)
            {
                return "";
            }
            if (str.Length < (start + len))
            {
                return str.Substring(start - 1);
            }
            return str.Substring(start - 1, len);
        }

        /// <summary>
        /// 文字列の指定した位置から末尾までを取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="start">開始位置</param>
        /// <returns>取得した文字列</returns>
        public static string _Mid_(string str, int start)
        {
            return _Mid_(str, start, str.Length);
        }

        /// <summary>
        /// 文字列の先頭から指定した長さの文字列を取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string _Left_(string str, int len)
        {
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null)
            {
                return "";
            }
            if (str.Length <= len)
            {
                return str;
            }
            return str.Substring(0, len);
        }

        /// <summary>
        /// 文字列の末尾から指定した長さの文字列を取得する
        /// </summary>
        /// <param name="str">文字列</param>
        /// <param name="len">長さ</param>
        /// <returns>取得した文字列</returns>
        public static string _Right_(string str, int len)
        {
            if (len < 0)
            {
                throw new ArgumentException("引数'len'は0以上でなければなりません。");
            }
            if (str == null)
            {
                return "";
            }
            if (str.Length <= len)
            {
                return str;
            }
            return str.Substring(str.Length - len, len);
        }
        /// <summary>
        /// 引数の分解
        /// </summary>
        /// <param name="input">入力値</param>
        /// <returns>分解された出力値</returns>
        public static string[] _argument_(string input,int Keta = 0)
        {
            string[] output = input.Split(" ");
            if (Keta == 0)
            {
                return output;
            }
            else
            {
                string[] output2 = new string[Keta];
                for (int i = 0; i < output2.Length; i++)
                {
                    try
                    {
                        output2[i] = output[i];
                    }
                    catch
                    {
                        output2[i] = "";
                    }
                }
                return output2;
            }
            
        }
    }
}
```

#### \_argument_関数
引数を読み取り、配列として格納する。


# さいごに
「いやiniの読み書きとか、引数とかいろいろ全部Stringでやるなよ」とかもっといい方法はいっぱいあると思いますが、備忘録なので雑で許してください。