mikutter blog

mikutterのアナウンスなど

World系プラグインを実装する 1日目 外部ファイルからMessageを読み込む

この記事は、以下の記事の続きです。

mikutter.hatenablog.com

今回は、おしゃぶりプラグインが作成するタブに表示するMessageを、外部JSONLファイルから読むようにします。 最終的にこのプラグインは任意のファイルに投稿を追記し、あとから読み込むことができるようにしたいので、とりあえず外部JSONLファイルからMessage Modelを作ることに成功すれば、完成に一歩近づいたといえるでしょう。

本日のコードはおしゃぶりプラグインのリポジトリのday1というタグ のリビジョンをチェックアウトすることで確認できます。記事内でもこのコードを引用しますが、実際に動かして確認するにはこれを利用してください。

前回の実装

  message = Plugin::Oshaburi::Message.new(description: 'hello, World!', created: Time.now, user: Plugin::Oshaburi::User.instance)

  Delayer.new do
    timeline(:oshaburi_test) << message
  end

もともとはこのような実装でした。 変数 message にPlugin::Oshaburi::Messageのインスタンスをバインドし、あとで格納しています。 表示される投稿の内容は上記のコードのとおり「Hello, World!」で、ハードコーディングされてしまっています。

今回の実装

  Delayer.new do
    File.open(File.join(__dir__, 'test.oshaburi')) do |istream|
      timeline(:oshaburi_test) << istream.map do |json_str|
        Plugin::Oshaburi::Message.new(
          JSON.parse(
            json_str,
            symbolize_names: true
          )
        )
      end
    end
  end

Delayerの中では、 File.open によって、test.oshaburiというファイルを読み込んでいます。 先にこのファイルの中身を見てみます。

{"user":{"username":"toshi","nodename":"seminole"},"description":"hello, World!","created":"2018-01-12T03:34:03+09:00"}
{"user":{"username":"toshi","nodename":"seminole"},"description":"初音ミク","created":"2018-01-12T03:39:45+09:00"}

JSONLとは、JSONを改行区切りで書き連ねたものです。このJSONがそれぞれ、Message Modelのもととなるデータです。 このデータを行ごとに走査してMessage Modelを作っているコードは、上記のコードのうち以下の部分です。

        Plugin::Oshaburi::Message.new(
          JSON.parse(
            json_str,
            symbolize_names: true
          )
        )

json_str が1つのJSONです。やっていることは非常にシンプルで、JSONをパースして、その内容を加工せずにそのままPlugin::Oshaburi::Messageのコンストラクタに渡せば、Plugin::Oshaburi::Messageを作成できています。

JSONデータをよく見てみる

JSONLだと、JSONが一行にまとめられていて読みづらいため、整形して読んでみましょう。

{
    "created": "2018-01-12T03:34:03+09:00",
    "description": "hello, World!",
    "user": {
        "nodename": "seminole",
        "username": "toshi"
    }
}

構造を見たいだけなので最初のひとつだけをとりあえず。 created description user の3つのキーが含まれていることがわかります。

次に、Plugin::Oshaburi::Messageの定義を見てみましょう。

module Plugin::Oshaburi
  class Message < Diva::Model
    include Diva::Model::MessageMixin

    register :oshaburi_message, name: "おしゃぶりテキスト"

    field.has :user, Plugin::Oshaburi::User, required: true
    field.string :description, required: true
    field.time :created, required: true
  end
end

順番は違いますが、こちらにも created description user の3つのフィールドがあります。このJSONは、Plugin::Oshaburi::Messageのコンストラクタにそのまま渡すことができる形式に予めなっていたのです。

値の自動変換

上記の3つのフィールドの定義を見ると、Modelが要求する値の種類と実際のデータのクラスが異なるものがあります。

合致する

まずdescriptionですが、これは3つのうち唯一合致しています。Model側では string を要求していて、JSONでは文字列になっているので、Stringクラスのインスタンスが渡されるでしょう。

timeに文字列を渡す

created はどうでしょうか。フィールドの制約には time と書かれていて、前回の実装ではTimeのインスタンスを渡してましたが、今回は "2018-01-12T03:34:03+09:00" という文字列(Stringのインスタンス)が渡されます。

お察しの通りこれはISO8601形式の時刻としてパースされ、格納時にTimeに変換されます。 Divaのフィールドの制約は、入力バリデーションと格納時の値の変換の2つの役割があります。time制約がかかったフィールドにTimeのインスタンスを格納すれば、何も変換されずにそのまま入りますが、文字列の場合はISO8601と仮定してTimeに変換された結果、IntegerならUNIX Timeと仮定してTimeに変換した結果が格納されます。 この機能はJSONをパースした結果をそのまま渡すことを想定して実装されています。今回の例だと、JSONの中に時刻を入れる場合は、たいていISO8601形式の文字列にするでしょうから、理にかなっていると思います。

Modelを要求するフィールドにHashを渡す

最後の user フィールドですが、制約としては Plugin::Oshaburi::User を渡すことになっています。しかしJSONのほうでは次のようなデータとなっており、パースされるとHashになります。

    {
        "nodename": "seminole",
        "username": "toshi"
    }

ここでも、timeのとき同様格納時の変換が起こります。特定のModel(今回はPlugin::Oshaburi::User)を要求するフィールドにHashを渡した場合、次のようなコードの実行結果が実際には格納されることになります。

Plugin::Oshaburi::User.new(hash)

DivaのコンストラクタにはJSONをパースした結果をそのまま渡せるということは、今確認しているとおりです。今回渡しているHashには nodename username の2つのキーがありますね。くどいのでいちいちここには書きませんが、Plugin::Oshaburi::Userの定義では、この2つのフィールドが要求されています

このように、どんなに複雑に入れ子になったDivaオブジェクトでも、同じように入れ子にしたJSON(Hash)を渡してやれば、一発でインスタンスを作ることができます。この性質をうまく使えば、TwitterMastodonなどの現実のサービスが返してくる複雑なJSONレスポンスを、非常に簡潔なコードでmikutter向けに加工できる可能性があります。

次回について

今回の書き換えは非常に短く、DivaがJSONを意識して設計されていることを紹介して終わってしまいました。

今回はJSONLファイルをリポジトリに含めています。これを書き換えてmikutterを起動すると、おしゃぶりタブに表示される投稿の数や内容がファイルに連動していることが確認できると思います。

次回はついに書き込みと行きたいところですが、おしゃぶりWorldを実装し、World作成画面で任意の位置にあるJSONLファイルを読み込むことができるようにしたいと思います。Worldがあれば、アカウントをおしゃぶりWorldに切り替えてPostBoxから投稿することで書き込みすることができるでしょう。専用のUIやコマンド(ショートカットキー)を使わせるのではなくTwitterと同じように書き込むことができるのがmikutterらしい姿です。