4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#でTRPG用のDiscordのBotを開発したよくある話

Posted at

まあ表題で完結してるんですが、一応事細かに書いて行きたいと思います。
先に言っておくと運用コストとかルールブックの著作権だとかでいろいろ問題があるのでBotの公開はありません。

開発動機

ダイスロールをしてくれるDiscordのBotといえば Sidekick などが有名で、多くのTRPGサーバーで使用されています。
例えば /r 1d100 と打てば100面ダイスのダイスロールをしてくれます。便利ですね。

でもやっぱりそれだけでは(機能が)足りないし、可愛げがない

TRPGといえば有名所で言えばSW2.0や、DX3rdですね。僕の参加しているDiscordグループでもやはりこの2つがメジャーです(本当はネクロニカとかアリアンロッドもしたいんですけど)。
そしてSW2.0(ソード・ワールド2.0)でよくやる処理と言えば

  • 成長
  • 経歴表

などがあります。
DX3rd(ダブルクロス The 3rd Edition)でよくやる処理といえば

  • 経験表
  • 覚醒表
  • 衝動表

などがあります。
これを通常のダイスロールで行うのは面倒です。
まあ、サイコロを1つ1つ転がして一喜一憂する楽しみも否定されるべきではないと思いますが、それなら(Botを)使わなければいいだけの話なので僕は作ります。

可愛げ?

必要です。

ほしい機能

  • 経歴表とかを振る
  • SSMSと連携して卓データの管理をする(卓の時間になったら通知したりする機能)
  • 葵ちゃんの声で喋って欲しい(個人の趣味)

開発言語

C#です。今回Botを制作するにあたり

を使っています。

実施詳細

まずDiscordのBotをサクッと作ります。
これについてはググれば ■ここ とか ■ここ とか ■ここ とかが出てくるので紹介しません。

埋め込みオブジェクトで自己紹介させる

いわゆる--helpコマンドですね。これはなんてことはなくて、一部抜粋すれば

Introduction.cs
public override async Task<bool> Run(SocketMessage msg)
{
    var asm = Assembly.GetExecutingAssembly();

    //AssemblyCopyrightの取得
    var copyright = ((AssemblyCopyrightAttribute)Attribute.GetCustomAttribute(asm, typeof(AssemblyCopyrightAttribute))).Copyright;
    //バージョンの取得
    var version = asm.GetName().Version.ToString();

    var abuilder = new EmbedAuthorBuilder();
    abuilder.WithName("[Botの名前]");
    abuilder.WithIconUrl("[アイコンのURL]");
    abuilder.WithUrl("[マニュアルのURL]");

    var builder = new EmbedBuilder();

    builder.WithTitle("[タイトル]");
    builder.WithThumbnailUrl("[アイコンのURL]");
    builder.WithAuthor(abuilder);
    builder.WithColor(Color.DarkRed);
    builder.WithDescription("[Botの説明]");

    builder.AddInlineField("マニュアル", "[マニュアルのURL]");
    builder.AddInlineField("名前", "[Botの名前]");
    builder.AddInlineField("特別協賛", "[特別協賛のリスト(実際は関数でJoinしています)]");
    builder.AddInlineField("バージョン", version);
    builder.AddInlineField("コピーライト", copyright);

    await msg.Channel.SendMessageAsync(string.Empty, false, builder); //ここらへんは実際はWrapしたFactoryクラスに任せてる

    return true;
}

みたいに書けばいいだけです。楽でいいわ。ありがとうDiscord.NETの人。

SSMSと連携するコマンド

これも楽で、App.config<connectionStrings>を書いていろいろ書いてたらできます。

SqlConnectionFactory.cs
public static string ConnectionString => ConfigurationManager.ConnectionStrings["Database"].ConnectionString;
public static SqlConnection CreateConnection()
{
    return new SqlConnection(ConnectionString);
}

public static void RunSql(Action<SqlConnection> action)
{
    using (var con = CreateConnection())
    {
        action(con);
    }
}

とか書いてコマンドの方からRunSqlを呼び出してあげれば楽でいいです。

CommandSample.cs
public override async Task<bool> Run(SocketMessage msg)
{
    var 好感度 = default(int);

    SqlConnectionFactory.RunSql(con =>
    {
        var cmd = con.CreateCommand();
        cmd.CommandText = "SELECT 好感度 FROM UserData WHERE ID=@id";
        cmd.Parameters.AddWithValue("@id", msg.Author.Id);
        con.Open();

        var sdr = cmd.ExecuteReader();
        if(sdr.HasRows)
        {
            if (sdr.Read())//コマンドによってwhileだったりifだったりする
            {
                好感度 = (int)sdr["好感度"];
            }
        }
    });

    //ここに処理を書く

    return true;
}

Botに喋らせる

仕組みを考える

Discord -> 拙作プログラム1 -> 葵ちゃん -> 拙作プログラム2 -> Discord

でいけると思います。

  • 拙作プログラム1
  • Discordからメッセージを受け取り葵ちゃんに読ませる( UIAutomation )
  • 葵ちゃんのウィンドウをSpyでSpySpyするとWPFでできているのがわかる(らしい)のでこの方法を採用(したい)
  • 拙作プログラム2
  • 葵ちゃんの生音声をDiscordにリダイレクト
  • 難関

最初からUIAutomationに挑むのは難関なので、試しにRemoteTalkという便利なものが用意されている棒読みちゃんに喋らせます。
つまり

Discord -> 拙作プログラム1 -> RemoteTalk.exe -> 棒読みちゃん -> 拙作プログラム2 -> Discord

でやります。

拙作プログラム1

Processで呼んであげるだけなので楽です。

VoiceProxy.cs
private static Process VoiceProxy { set; get; }

public static void Start()
{
    VoiceProxy = Process.Start(@"[パス]\Discord Audio Stream Bot.exe");
}
        
public static void Exit()
{
    VoiceProxy.WaitForExit();
}

public static void TalkBouyomi(string message)
{
    Process.Start(@"[棒読みちゃんのパス]\RemoteTalk\RemoteTalk.exe", $"/Talk {message}");
}

Nullチェックとか各種検査(例えばmessageに棒読みちゃんに有効なパラメータとかが入ってた場合とか、| rm -rfとか)をしてないのが気になりますが、いいんです。コストがかかるだけです。YAGNIだよ兄貴。

拙作プログラム2

これが問題で、まず1つ思いついたのはNET DUETTOの出力を入力にしてくれるデバイスを拝借して、葵ちゃんの出力をマイクとして使用する方法です。

葵ちゃん -> 《NET DUETTO(出力) -> NET DUETTO(入力)》 -> 拙作プログラム2 -> Discord

で、多分これで問題ないと思うんですが、どうやってコード上でBotのマイクを設定すればいいんでしょうか……?

そんなニッチな需要のものググっても流石に出てこないだろうしな~~~出ました。神か。

…………
(ソースコード)読めない……

という訳でバイナリをありがたく拝借し、この項目についてはひとまず完成となりました。詳しく知りたいならDiscord Audio Stream Botのリポジトリを見るといいぞ。僕は諦めました。

ボイチャのユーザーを移動させたい

卓をやっていると、その時間に普段のGeneralなボイチャから、特定のシステムのボイチャに集まりたいことがあります。
ググってもあまり出てこなくて地味にハマったところなので一応。ソースコード中のClientは接続済みのDiscordSocketClientです。

CommandMoveVoiceUser.cs
var guild = Bot.Client.GetGuild([サーバーのID]);
var vcID = [ボイチャのID];
var channel = guild.GetVoiceChannel(vcID);
var user = guild.GetUser((ulong)[移動させたいユーザーのID]);
await user.ModifyAsync(e => e.Channel = channel);

でできます。

参考

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?