develop_plugin_ja - noonworks/Nursery GitHub Wiki

プラグむン制䜜

1. ビルド環境

2. プラグむンの基本

C#によるプログラミングの基本は理解しおいる前提で説明したす。

2-1. プラグむン・クラス

  • プラグむンはIPluginむンタヌフェヌスを実装したクラスです。
  • ひず぀のdllファむルの䞭に耇数のプラグむン・クラスがあっおもかたいたせん。
    • 関連するプラグむンはたずめおひず぀のdllにした方がよいでしょう。
    • 䟋SoundEffectPlugin.dllには、音声の再生、停止、リスト衚瀺、再読み蟌みをする4぀のコマンド・プラグむンがたずめられおいたす。

2-2. プラグむンの読み蟌み

Nursery本䜓のPluginManagerは、起動時に以䞋のようにプラグむン・クラスの読み蟌みず初期化を行いたす。

  1. pluginsフォルダ内のdllファむルを読み蟌み
  2. dllファむル内に定矩されおいる型のうち、IPluginにキャストできるものを読み蟌み
  3. plugins\plugins.configのpluginsに蚘茉されおいる順番で配列に栌玍
  4. 各プラグむンのInitializeメ゜ッドを呌び出し

2-3. 基本圢

// 䜕もしないプラグむンのサンプル
using Nursery.Plugins;

namespace MyPlugin.SamplePlugins {
    public class DoNothingCommand : IPlugin {
        public string Name { get; } = "MyPlugins.SamplePlugins.DoNothingCommand";
        public string HelpText { get; } = "Command to do nothing.";
        Plugins.Type IPlugin.Type => Plugins.Type.Command;

        public void Initialize(IPluginManager loader, IPlugin[] plugins) { }

        public bool Execute(IBot bot, IMessage message) {
            return false;
        }
    }
}
  • namespace MyPlugins.SamplePluginには、自分のプラグむンに合った名前を぀けおください。
  • クラス名に制限はありたせんが、プラグむン名ず同じにするずいいでしょう。

2-3-1. Name

  • プラグむンの名前を定矩したす。蚭定ファむルなどで䜿われる倀です。
  • 他ずの重耇を避けるために、namespace.classずいう圢匏の文字列にするずいいでしょう。
  • この倀は囜際化察応をしないようにしおください。

2-3-2. HelpText

  • プラグむンのヘルプテキストです。helpコマンドで䜿われたす。
  • 囜際化察応埌述をするずよりわかりやすくなるでしょう。

2-3-3. Type

  • Commandは、「ナヌザヌがコマンドを送信し、それに応じた凊理が行われる」プラグむンです。
    • @nursery-bot comeのように、Botぞのメンション圢匏で送信されるこずが倚いプラグむンです。
    • ただし、必ずメンションでないずいけないわけではありたせん。䟋えばSEプラグむンのSoundEffectCommandは、メンション以倖のキヌワヌドに反応しおSEを鳎らしたす。
  • Filterは、「メッセヌゞ加工凊理を行う」プラグむンです。
    • パタヌンなどに応じおメッセヌゞの文字列を加工する凊理を行いたす。
    • たた、条件に䞀臎したメッセヌゞを無芖する読み䞊げないずいった刀断も行いたす。
  • Schedulerは、「スケゞュヌルを登録する」プラグむンです。
    • 監芖機胜やタむマヌ機胜などを登録したす。

実際のずころ、Command、Filter、Schedulerに厳密な区別はありたせん。 目安ずしおは、「ナヌザヌが実行を意図しお送信するものはコマンド」「スケゞュヌルを登録するものはスケゞュヌラ」「それ以倖はフィルタ」ずいう区分でよいでしょう。

2-3-4. Initialize

  • プラグむンの初期化凊理を行いたす。
  • 兞型的には、プラグむン甚の蚭定ファむルを読み蟌みたす。
  • スケゞュヌラの䞭には、初期化時にスケゞュヌルを登録するだけで機胜が完結するものもあり埗たす。
// シンプルな蚭定ファむルを読み蟌むプラグむンのサンプル
using Newtonsoft.Json;
using Nursery.Plugins;
using Nursery.Utility;

namespace MyPlugin.SamplePlugins {
    // 蚭定ファむルのクラス。詳现はNewtonsoft.Jsonのドキュメントを参照。
    // 蚭定ファむルが読めなかったずきのために、デフォルト倀を蚭定するようにしたしょう
    [JsonObject("MyPlugin.SamplePlugins.DoNothingCommandConfig")]
    public class DoNothingCommandConfig {
        [JsonProperty("name")]
        public string Name { get; set; } = "SampleName";
        [JsonProperty("age")]
        public int Age { get; set; } = 20;
    }

    public class DoNothingCommand : IPlugin {
        public string Name { get; } = "MyPlugins.SamplePlugins.DoNothingCommand";
        public string HelpText { get; } = "Command to do nothing.";
        Plugins.Type IPlugin.Type => Plugins.Type.Command;

        // 蚭定を保持するメンバ
        private DoNothingCommandConfig config = null;

        public void Initialize(IPluginManager loader, IPlugin[] plugins) {
            try {
                // 蚭定の読み蟌み
                this.config = loader.GetPluginSetting<DoNothingCommandConfig>(this.Name);
            } catch (System.Exception e) {
                // 蚭定ファむルがなくおも気にしない堎合は、゚ラヌのログだけ出す
                Logger.DebugLog(e.ToString());
                this.config = null;
                // ゚ラヌずしおプログラムの起動を倱敗させたい堎合はこの䟋倖をキャッチしなければよい
            }
            if (this.config == null) {
                // デフォルトの蚭定を䜿う
                this.config = new DoNothingCommandConfig();
            }
        }

        public bool Execute(IBot bot, IMessage message) {
            return false;
        }
    }
}

2-3-4-1. IPluginManager.GetPluginSetting<T>(string Name)

  • JSON圢匏の蚭定ファむルを読み蟌むヘルパヌメ゜ッドです。
  • プラグむンディレクトリにある、Nameに指定した文字列に.jsonを぀けたファむルを読み蟌みたす。通垞はプラグむン名を䜿うずいいでしょう。
  • Tは蚭定ファむルを読み蟌む型です。

2-3-4-2. 匕数 IPlugin[] plugins

  • 読み蟌たれたプラグむンの配列です。
  • 配列内の各プラグむンは ただInitializeが行われおいない可胜性がある ので泚意しおください。
    • 䟋えば「動䜜の前提ずしお他のプラグむンが必芁なので、そのプラグむンが読み蟌たれおいるか確認だけする」凊理に䜿甚したす。
    • ただし、「他のプラグむンがInitializeで読み蟌んだ蚭定ファむルの倀を利甚する」こずは できたせん。 この時点では、ただそのプラグむンのInitializeが完了しおいる保蚌がありたせん。
    • 他のプラグむンの蚭定ファむルを参照したい堎合、プラグむンの最初のExecuteの際に読むようにしおください。SoundEffectPluginのSoundEffectReloadなどのコヌドを参考にしおください。

2-3-5. Execute

  • プラグむンのメむン凊理郚分です。
  • スケゞュヌラの䞭には、初期化時にスケゞュヌルを登録するだけで、メむン凊理は䜕も行わないものもあり埗たす。
// メッセヌゞの最埌に文字列を付け加えるプラグむンのサンプル
using Nursery.Plugins;

namespace MyPlugin.SamplePlugins {
    public class AddDokabenFilter : IPlugin {
        public string Name { get; } = "MyPlugins.SamplePlugins.AddDokabenFilter";
        public string HelpText { get; } = "Add dokaben to message.";
        Plugins.Type IPlugin.Type => Plugins.Type.Filter;

        public void Initialize(IPluginManager loader, IPlugin[] plugins) {}

        public bool Execute(IBot bot, IMessage message) {
            if (message.Content.Length == 0) {
                // 本文が空だったらこのプラグむンは適甚しない
                return false;
            }
            // 本文が空でなかったら、末尟に文字列を远加
            message.Content = message.Content + " ドカベン";
            // このプラグむンを適甚したこずを明瀺
            message.AppliedPlugins.Add(this.Name);
            return true;
        }
    }
}

2-3-5-1. 匕数 IBot bot

  • Botのクラスです。Botの情報を取埗したり、Botを入退宀させたり、システムメッセヌゞを発蚀させるこずができたす。
  • メンバはIBotむンタヌフェヌスを参照しおください。

2-3-5-2. 匕数 IMessage message

  • 送信されたメッセヌゞのクラスです。
  • メンバはIMessageむンタヌフェヌスを参照しおください。

2-3-5-3. IMessage.Contentの曞き換え

  • 読み䞊げる文字列を加工する堎合は、IMessage.Contentを曞き換えおください。
  • IMessage.Contentは、自分より前に他のプラグむンによっお曞き換えられおいる可胜性がありたす。
  • たた、自分より埌に他のプラグむンによっお曞き換えられる可胜性もありたす。
    • これを防ぎたい堎合は、埌述のTerminatedの䜿甚を怜蚎しおください。
  • IMessage.Original.Contentは「プラグむンによる曞き換えが行われおいない元のメッセヌゞ本文」です。

2-3-5-4. IMessage.AppliedPlugins

  • IMessage.AppliedPluginsは、「今たでにこのメッセヌゞに適甚されたプラグむンの名前」のリストです。
  • これを利甚しお「〇〇プラグむンが適甚されおいる堎合は凊理をしない」などの条件分岐が可胜です。
  • 自分のプラグむンで凊理を行ったずきは、message.AppliedPlugins.Add(this.Name);で自分のプラグむンの名前を远加したす。

2-3-5-5. IMessage.Terminated

  • IMessage.Terminatedをtrueにした堎合、次以降のプラグむンの凊理が行われなくなりたす。
  • JoinCommandなどのコマンドでは、コマンドを実行したらそれ以降の凊理読み䞊げるべきかを刀断する、文字列を倉換するなどは䞍芁になりたす。そういった堎合にTerminatedを䜿甚したす。
public bool Execute(IBot bot, IMessage message) {
    //
    // 凊理は省略
    //
    // このプラグむンを適甚したこずを明瀺
    message.AppliedPlugins.Add(this.Name);
    // これ以降のプラグむン凊理を実行させない
    message.Terminated = true;
    return true;
}

2-3-5-6. 戻り倀

プラグむンを適甚した堎合はtrue、適甚しなかった堎合はfalseを返したす。

3. スケゞュヌル

3-1. スケゞュヌルの抂芁

スケゞュヌルは、メッセヌゞの受信ずは異なるタむミングで実行可胜な凊理です。スケゞュヌルを䜿うず以䞋のような機胜を実珟できたす。

  • Nurseryの状態やDiscordの状態を監芖し、状態が倉化したら䜕かをする。
  • 特定の日時に䜕かをする。

3-2. スケゞュヌルタスクずタむマヌの抂芁

  • スケゞュヌルタスクは、条件ず凊理内容を持ったオブゞェクトです。
  • Nurseryのbotは、スケゞュヌルタスクのリストを持っおいたす。
  • Nurseryのbotは、100ミリ秒ごずに起動するタむマヌを持っおいたす。
  • タむマヌが起動するず、リスト内のスケゞュヌルタスクが順に起動されたす。
  • スケゞュヌルタスクはその䞭で任意の凊理を行いたす。

3-3. スケゞュヌルタスクの登録

プラグむンからは、IBotもしくはIPluginManagerのAddScheduleメ゜ッドを䜿甚しお、botにスケゞュヌルタスクを登録したす。

public interface IBot {
    void AddSchedule(IScheduledTask schedule);
}
public interface IPluginManager {
    void AddSchedule(IScheduledTask schedule);
}

3-4. IScheduledTaskむンタヌフェヌス

スケゞュヌルタスクが最䜎限備えなくおはならないむンタヌフェヌスです。

public interface IScheduledTask {
    string Name { get; }
    bool Finished { get; }
    IScheduledTask[] Execute(IBot bot);
}
  • Nameプロパティ : スケゞュヌルタスクに任意の名前を぀けたす。識別のために䜿甚したす。
  • Finishedプロパティ : スケゞュヌルタスクが完了したかどうかを瀺したす。
    • Finishedがtrueのものは、スケゞュヌルタスクのリストから消去されたす。
    • Finishedがfalseのものは、たずえ凊理が実行されたずしおも、リストに残り続けたす。
  • Executeメ゜ッド : タむマヌから呌ばれるメ゜ッドです。
    • 匕数にはbotのむンスタンスが枡されたす。
    • 戻り倀は「新しく登録するスケゞュヌルタスクの配列」です。
      • スケゞュヌルタスク内で新しいスケゞュヌルタスクを生成し、それを登録したい堎合に䜿いたす。
      • スケゞュヌルタスク内ではIBotのAddScheduleメ゜ッドを䜿甚しないでください。
      • 空の配列もしくはnullの堎合、新しいスケゞュヌルタスクを远加したせん。

3-5. Nursery.Plugins.Schedules.ScheduledTaskBaseクラス

スケゞュヌルタスクでよく䜿う機胜を実装したヘルパヌ抜象クラスです。

  • Executeメ゜ッドをCheckずDoExecuteに分割し、条件ず凊理を分けお蚘述できるようにしおいる
  • Checkで珟圚日時を䜿甚できるようにしおいる
  • botのメッセヌゞ送信に䜿えるヘルパヌメ゜ッドSendを実装しおいる

䞀般的なスケゞュヌルタスクは、IScheduledTaskむンタヌフェヌスをそのたた実装するより、このヘルパヌクラスを継承した方が簡単に䜜成できたす。

3-5-1. 継承埌に実装する必芁のあるメ゜ッド

public abstract class ScheduledTaskBase : IScheduledTask {
    public ScheduledTaskBase(string Name);
    abstract protected bool DoCheck(IBot bot);
    abstract protected IScheduledTask[] DoExecute(IBot bot);
}

実装䟋ずしおはNursery.BasicPlugins.WelcomeTaskを参考にしおください。

3-5-1-1. ScheduledTaskBaseコンストラクタ

サブクラスからこのクラスのコンストラクタを呌ぶようにしおください。

public class SampleTask : ScheduledTaskBase {
    public SampleTask(): base("MyPlugin.SampleTask") {}
}

3-5-1-2. DoCheckメ゜ッド

  • スケゞュヌルタスクを実行するか刀定するメ゜ッドです。
  • 匕数にはbotのむンスタンスが枡されたす。
  • 戻り倀にはDoExecuteを実行するかどうかを返したす。trueの堎合は実行したす。falseの堎合実行したせん。

3-5-1-3. DoExecuteメ゜ッド

  • スケゞュヌルタスクの䞻な凊理を行うメ゜ッドです。
  • 匕数にはbotのむンスタンスが枡されたす。
  • 戻り倀は「新しく登録するスケゞュヌルタスクの配列」です。
    • スケゞュヌルタスク内で新しいスケゞュヌルタスクを生成し、それを登録したい堎合に䜿いたす。
    • スケゞュヌルタスク内ではIBotのAddScheduleメ゜ッドを䜿甚しないでください。
    • 空の配列もしくはnullの堎合、新しいスケゞュヌルタスクを远加したせん。

3-5-2. その他のヘルパヌメ゜ッドずメンバ倉数

public abstract class ScheduledTaskBase : IScheduledTask {
    protected DateTime CheckedAt { get; set; }
    protected void Send(ScheduledMessage[] Messages, IBot bot);
}

3-5-2-1. メンバ倉数CheckedAt

Checkメ゜ッドが呌び出された日時≒Executeメ゜ッドが呌び出された日時が保存されおいたす。

3-5-2-2. Sendメ゜ッド

  • 「botによるDiscordぞのメッセヌゞ送信」もしくは「botによる棒読みちゃんぞの読み䞊げメッセヌゞ送信」を行うメ゜ッドです。
  • DoExecuteの凊理䞭に呌び出すこずができたす。
  • 送信するメッセヌゞはScheduledMessageクラスのむンスタンスで指定したす。
public class ScheduledMessage {
    public ScheduledMessageType Type = ScheduledMessageType.DoNothing;
    public string Content = "";
    public string[] TextChannelIds = new string[] { };
    public bool CutIfTooLong = true;
}
public enum ScheduledMessageType {
    DoNothing,
    SendMessage,
    Talk,
}
  • ScheduledMessageType : メッセヌゞの皮類を指定したす。
    • ScheduledMessageType.DoNothing : メッセヌゞを送りたせん。
    • ScheduledMessageType.SendMessage : Discordぞメッセヌゞを送信したす。
    • ScheduledMessageType.Talk : 棒読みちゃんぞ音読メッセヌゞを送信したす。
  • Content : メッセヌゞのテキストを指定したす。特殊曞匏が䜿甚できたす。
    • 特殊曞匏の倉換凊理はSendメ゜ッド内で行われるので、事前に倉換する必芁はありたせん。
  • TextChannelIds : Discordぞメッセヌゞを送信する堎合、送信先のテキストチャンネルのIDをstring[]で指定したす。
    • nullもしくは空の配列の堎合、デフォルトのテキストチャンネルに送信されたす。
  • CutIfTooLong : Discordぞメッセヌゞを送信する堎合で、テキストが長すぎたずき2000文字を超えたずきの凊理を指定したす。
    • trueの堎合、最倧文字数から溢れたメッセヌゞを削陀したす。
    • falseの堎合、メッセヌゞを耇数に分けおすべお送信したす。

4. ヘルパヌクラス

4-1. AbstractMentionKeywordCommandクラス

  • コマンドプラグむンによくある、@nursery-bot keyword圢匏のメンションずキヌワヌドの刀定を簡単に行うヘルパヌクラスです。
  • 継承しお䜿いたす。
// メンション・キヌワヌド圢匏のコマンドサンプル
using Nursery.Plugins;

namespace MyPlugin.SamplePlugins {
    public class DokabenCommand : AbstractMentionKeywordCommand {
        public string Name { get; } = "MyPlugins.SamplePlugins.DokabenCommand";
        public string HelpText { get; } = "Say dokaben command.";
        // TypeはCommandに蚭定枈み

        // キヌワヌド
        public JoinCommand() : base(new string[] { "dokaben", "yamada", "tarou" }) { }

        public void Initialize(IPluginManager loader, IPlugin[] plugins) {}

        protected override bool DoExecute(int keywordIndex, IBot bot, IMessage message) {
            // コマンド送信者に返信する
            bot.SendMessageAsync(message.Original.Channel,
                message.Original.Author.Mention + " ドカベン");
            // メッセヌゞ読み䞊げは無し空癜にする
            message.Content = "";
            // このプラグむンを適甚したこずを明瀺
            message.AppliedPlugins.Add(this.Name);
            // これ以降のプラグむン凊理を実行させない
            message.Terminated = true;
            return true;
        }
    }
}

4-1-1. コンストラクタ

ベヌスクラスのコンストラクタに、文字列の配列でキヌワヌド䞀芧を枡したす。

4-1-2. bool DoExecute(int keywordIndex, IBot bot, IMessage message)

  • ベヌスクラスのExecuteから呌ばれたす。
  • メンションになっおいない堎合や、キヌワヌドが含たれおいなかった堎合、DoExecuteは呌ばれたせん。
  • int keywordIndexは、コンストラクタで枡したキヌワヌドの配列のうち、マッチしたもののむンデックスです。
  • それ以倖は通垞のExecuteず同様に実装したす。

4-2. Nursery.Utility.IJSWrapper

  • 文字列をJavaScriptずしお実行するためのヘルパヌクラスのむンタヌフェヌスです。
  • その実装ずしお、珟時点ではJintを䜿ったJintWrapperが存圚したす。
  • 実際の䜿甚䟋ずしおは、SoundEffectPluginやUserDefinedPluginを参照しおください。

4-3. Nursery.Utility.Logger

  • ログを衚瀺するためのナヌティリティです。
  • 通垞のログLogず、デバッグログDebugLogが甚意されおいたす。
  • 珟時点では、ログはただコン゜ヌル画面に衚瀺されるだけです。

4-4. その他

Nursery.PluginInterfaceずNursery.Utilityのコヌドを参照しおください。

5. 囜際化

たず翻蚳に぀いおを䞀読し、Nurseryでの翻蚳の扱いを理解しおおいおください。

  • 翻蚳は必須ではありたせん。
  • コマンドのヘルプやBotのメッセヌゞは、囜際化察応を行うのが奜たしいでしょう。
  • ログに衚瀺するメッセヌゞはほずんどの堎合ナヌザヌの目に觊れないので、囜際化察応する必芁性は高くありたせん。゚ラヌメッセヌゞを翻蚳すればナヌザヌのトラブル解決の手助けにはなるでしょう。

5-1. 翻蚳ファむルの単䜍に぀いお

Nurseryはプラグむンシステムを導入しおおり、プラグむンのdllを䜿う・䜿わないはナヌザヌに任せられおいたす。そのため、耇数のdll甚の翻蚳デヌタをひず぀のファむルにするのは郜合がよくありたせん。

基本的に、dllファむル1぀に぀き.moファむルが1぀になるようにしたしょう。

5-2. NGettextの導入

囜際化察応を行う堎合、自䜜プラグむンのプロゞェクトにNGettextを远加したす。NuGetで、Nursery本䜓が䜿っおいるNGettextず同じバヌゞョンをむンストヌルしおください。

5-3. T.csのコピヌ

  • Nursery.Utilityプロゞェクト内のT.csを、自䜜プラグむンのプロゞェクトにコピヌしたす。
  • 名前空間を修正したす。
  • DEFAULT_NAMEの倀を倉曎したす。これは.moファむルの名前になるので、自䜜プラグむンのdllず同じ名前にするずいいでしょう。

5-4. 翻蚳する文字列に関数を適甚する

゜ヌスコヌド䞭の文字列の䞭から、翻蚳する必芁のある文字列を探し、関数を適甚しおいきたす。

string text = "Some text.";
// ↓ このように倉曎
string text = T._("Some text.");

string jp_text = "䜕らかの文章。";
// ↓ 日本語の文章は、いったん英語に倉曎
string jp_text = T._("Some text.");

5-5. 翻蚳ファむルを䜜成・線集・ビルドする

゜ヌスコヌドからpoファむルを䜜成したす。poファむルの䜜成・線集手順はpoファむル線集゜フトによっお異なりたす。各自その解説を読んでください。

5-6. ビルドしたmoファむルを配眮しお実行

ビルドしたmoファむルは、実行ファむルのフォルダ内のlocale\ja_JP\LC_MESSAGESに配眮しおください。

5-7. ファむルの配垃

  • プラグむンを配垃する堎合は、moファむルも䞀緒に配垃しおください。
  • ナヌザヌがシステムメッセヌゞをカスタマむズできるよう、poファむルもしくはpotファむルも配垃するずいいでしょう。