mikutterをいじりたおす 第1回

大学の実験「大規模ソフトウェアを手探る」でmikutterを扱ったお話。

数回にわたって、僕がmikutterをどんなふうにいじり、どんなふうに改悪したのかをまとめていきます。少し冗長で長く感じる(冗長な表現!)かもしれませんが、まったくでかいプログラムに触れたことがない人でもいじれるくらいの細かさを心がけたつもりです。ただし、Rubyの文法はちゃんと知っているという前提で進めていきます。

タグ、もしくはカテゴリ「lecture_doss」で本授業の記事のみ参照することができます。

※ 本投稿は一応正式なレポートです。
※ 一部Gitや発表プレゼンでの実装と異なる部分があるかもしれません。ご了承ください。

もくじ

第1回: mikutterとは / パクツイ機能をつける ←いまここ
第2回: ワードミュート(前半)
第3回: ワードミュート(後半)
第4回: 画像投稿(前半)
第5回: 画像投稿(後半)
第6回: 動画投稿
第7回: 動画再生

mikutterとは

ここを見ているみなさんには説明が必要ないと思う。知らない人は先生に聞けばよいだろう。

この記事ではmikutter 3.2.9を基本としているが、レポート作成の段階では10月29日付けでgitからcloneしたバージョンを用いている。しかし同じ方法で実装が可能なはず……である。

実際にソースコードをダウンロードしていじりながら読んでいくと飲み込みやすいだろう。

パクツイ実装

ツイートを右クリックして出るコンテキストメニューに「パクツイ」ボタンを設置して、それを押すと返信投稿欄にそのツイートのテキストだけをそのままコピーする、という機能。

実際はプラグインだけで実装できる気がするが、今回は実験用にコアな部分をいじっていきたいと思う。

実装フロウ

  1. まず引用RTボタンの実装を調べ、それと同じ機能で別名のメニューアイテムを作ってみる
  2. そのメニューアイテムを実行した場所を追って、投稿欄を作り、そのデフォルトテキストを設定する場所を突き止める
  3. デフォルトテキスト書き換えの実装を行う

引用RTボタンを探す

コンテキストメニューに「引用」というボタンがある。こいつがどこで実装されているかを探そう。シェルで

find ./ -type f -print | xargs grep '引用'

などとして全ファイルgrepをかける。zshなら find ./**/*.rb としたほうが .rb ファイルだけで絞れるのでおすすめ。bashでもまあもうちょい工夫すればできるけど。

さて、実行するとこんな感じ。

[ishotihadus@~/Documents/mikutter_dev]% find **/*.rb -type f -print | xargs grep '引用'
core/plugin/command/command.rb:          name: _('引用'),
core/plugin/set_input/set_input.rb:      boolean(_('引用(非公式ReTweet)の場合はフッタを付与しない'), :footer_exclude_retweet).

下の set_input.rb はちょっと関係なさそうなので、上の command.rb の中身を見てみる。すると下のようなコマンドの定義がたくさんされていることがわかる。これ1つ1つがコンテキストメニューに追加する操作であるということが容易に想像できる。

command(:legacy_retweet,
        name: _('引用'),
        condition: Plugin::Command[:HasOneMessage, :CanReplyAll],
        visible: true,
        role: :timeline) do |opt|
    opt.widget.create_reply_postbox(opt.messages.first.message, retweet: true) end

これを近場にコピーして、「引用」の文字列を「パクツイ」に書き換えてみよう。すると、「パクツイ」がコンテキストメニューに追加されていることがわかる。

スクリーンショット 2015-10-21 0.24.49

さて、ここで「引用」ボタンが消えていることにお気づきだろうか。そう、どうやら元の引用ボタンを上書きしているようだ。上のcommand実装を見てみて、コマンドを特定していそうなワードといえば、最初のオプションの :legacy_retweet 以外にない。というわけで、これを :pakutweet などとしてもう一度実行しよう。すると「引用」ボタンが復活したはずだ。

なおこのcommandを特定しているであろうワードは、このコンテキストでは他の場所で使われていないようなので、特に無視して良いだろう(参考: slugってなに?)。

さて、do 〜 endのなかでこのメニューが実行された時の挙動が書かれているようである、というのは一目瞭然だ。次のセクションではこれをもとに引用RTの動きを追っていく。

補足

ここまでの内容は、Writing mikutter pluginのこの節にとてもわかりやすく書いてあるので、実際はこちらを読むのが早い。

引用RT実装を追う

さきほど do〜end のなかでメニュー実装の挙動が書かれていると説明した。そこで問題なのが、create_reply_postbox という関数である。いかにも返信投稿ボックスを作る、という関数だ。引数にはmessageとretweetフラグがあり、どうやらツイートと引用RTのフラグを渡しているに違いない。

この create_reply_postbox で全grepをかけてみると、core/plugin/gui/timeline.rbでこの関数が定義されていることがわかる。どうやらこのタイムラインウィジェット自体に返信投稿ボックスを作る関数があるらしい。さてこの定義を見てみよう。

# _in_reply_to_message_ に対するリプライを入力するPostboxを作成してタイムライン上に表示する
# ==== Args
# [in_reply_to_message] リプライ先のツイート
# [options] Postboxのオプション
def create_reply_postbox(in_reply_to_message, options = {})
  i_postbox = Plugin::GUI::Postbox.instance
  i_postbox.options = options
  i_postbox.poster = in_reply_to_message
  notice "created postbox: #{i_postbox.inspect}"
  self.add_child i_postbox
end

どうやら GUI::Postbox というクラスを作って自分自身(タイムラインウィジェット)に追加しているらしい。

このクラスは当然core/plugin/gui/postbox.rbにあって、これを見てみる。しかしそれっぽい、つまり初期テキストを作っているような実装がない。どうやら、これは上辺の部分で、初期テキストを作ったり、テキストをtwitterに投稿したりする部分は別にあるようだ。

postbox.rbの中で、post_it! という関数があるのが気になる。しかもこの中で別の関数を呼び出しているようだ。これを追っていけば投稿欄の実体にたどり着けるかもしれない。

この中で呼びだされている gui_postbox_post という関数っぽいものをまた全grepかけて探してみる。するとcore/plugin/gtk/gtk.rbに on_guit_postbox_post という謎の物体があることがわかる(むしろこれ以外に引っかからない)。このファイルを開いて関数の定義を見てみよう。

on_gui_postbox_post do |i_postbox|
  postbox = widgetof(i_postbox)
  if postbox
    postbox.post_it end end

とてもシンプルだ……。しかし、post_itという関数があることがわかる。これで全grepをかけると、core/mui/gtk_postbox.rb にこの関数の定義があることがわかる。

さて、ゴールはもう少しだ。このgtk_postbox.rb、長い。長いということは、中身がちゃんと実装されている可能性が高い。340行くらいしかないので、「def」の文字を目grepしながらどんな実装があるのかを見ていく。すると、下の方に post_set_default_text という関数が定義されているではないか!

def post_set_default_text(post)
  if @options[:delegated_by]
    post.buffer.text = @options[:delegated_by].post.buffer.text
    @options[:delegated_by].post.buffer.text = ''
  elsif retweet?
    post.buffer.text = " RT @" + @postable.idname + ": " + @postable.to_show
    post.buffer.place_cursor(post.buffer.start_iter)
  elsif reply?
    post.buffer.text = reply_users + ' ' + post.buffer.text end
  post.accepts_tab = false end

「 RT @」なんていう文字列もあるし、これがデフォルトテキストを作る場所ということだろう。

とりあえず、post.accepts_tab = fase と end の間に、p post.buffer.text などとしてみよう。すると、引用RTボタンを押すと同時に投稿欄に入っているテキストが標準出力される。

ここまでで、command.rbで定義されたcreate_reply_postbox関数を呼び出すと、巡り巡ってgtk_postbox.rbの中で投稿ボックスが作られ、その途中でpost_set_default_textという関数でデフォルトテキストが作られることが判明した。

補足

mui/gtk_postbox.rbに辿り着くまでいくらかゴリ押しがあった。特にpost_itに関してはひどい。一応i_postboxがGUI::Postboxのインスタンスであること、およびcore/plugin/gtk/slug_dictionary.rbにGUI::PostboxのウィジェットがGtk::PostBoxであって、このことをwidgetofによって得ているとわかればすんなり追えるが、実際にはそこまで簡単にはわからないし、何も知らない状況ではpost_itで検索するほかないだろう。

デフォルトテキストを書き換える

上のコードを見てみると、if文の条件が(1つめのよくわからないものをのぞいて)retweet?とかreply?とかのフラグを見ているようだ。この2つの定義を探すと、同じファイルにこのような記述がある。

def reply?
  @postable.is_a?(Retriever::Model) end

def retweet?
  @options[:retweet] end

reply?のほうはちょっとよくわからないが、retweet?のほうはわかりやすい。どうやら渡されたオプションを読んでいるようだ。このretweet、オプションだということは、投稿欄を作った時におそらく指定されているに違いない。引用RTのボタンの定義にretweet: trueとあったので、これと対応している気がしないでもない。

というわけで見切り発車でpakutweetというオプションがtrueになったらパクツイだとしてテキストをそのまま貼る実装をしよう。まずは上のretweet?と同様にpakutweet?を定義する。retweet?の直後にでも置いておこう。

def pakutweet?
  @options[:pakutweet] end

次にpost_set_default_textを書きかえる。引用RTは「 RT @screen_name: text」と作られるはずなので、該当コードと見比べると、post.buffer.textが投稿欄のテキスト、@postable.to_showがツイートのテキストだろう。したがって、if文に次のような条件を追加してみる。

elsif pakutweet?
  post.buffer.text = @postable.to_show

なお引用RTだとplace_cursorという関数が呼び出されているが、これはテキストボックスのカーソルを先頭に持ってくる作業であろうというのは理解できるだろう。

最後に、command.rbで定義したパクツイボタンのコールバックで呼び出す関数create_reply_postboxのオプションをretweet:trueからpakutweet:trueに変更して、実行してみる。

ツイートを右クリックして出てきたコンテキストメニューに「パクツイ」ボタンが配置され、これを押すとタイムライン上部に投稿欄が現れ、ここにそのツイートのテキストだけが表示された。

補足

post_set_default_textの1つめの条件文は、投稿中のテキストボックスに関する条件である。mikutterでは投稿ボタンを押すと、その下に新しい投稿ボックスを作り、そこから投稿する仕組みになっている。投稿ボタンを押した後、新しく作られたものは、@options[:delegated_by]に元の投稿ボックスが代入されている(そうでなければnil)。なお@options[:delegated_by]への代入や新しい投稿ボックスの作成は、gtk_postbox.rbにあるdelegateという関数内で行われている。

この知識は最後、画像投稿を実装する際に必要になってくるが、該当記事でこれを読み解いていくので覚えていなくともよい。

次回予告

次回はワードミュート機能を実装する予定です。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です