しょんぼり技術メモ

まいにちがしょんぼり

TwitterIRCGatewayをより(俺にとって)便利にする

※注意:本記事はすべてTwitterIRCGateway 2.xについての記述です。
また、TIGや付属のスクリプトはMITライセンスで提供されているため、
本記事におけるコードもMITライセンスとします。


TwitterIRCGateway(以下TIG)は、TwitterIRCクライアントから使うことができるゲートウェイアプリケーションです。とっても便利なのですが、あまり紹介されている記事がないので自分で書きます。


インストール方法や使い方などは、本家をご覧ください。

TypableMapとは

デフォルト設定のTIGでは、数あるTwitterクライアントのように、特定の発言に対して返信を行うことができません。@hogehoge ほげほげ と発言することで、誰かに対してreplyすることはできますが、どの発言に対してのreplyなのかがTIGにはわからないためです。

そこで、これを解決するためにTypableMapという機能を有効化します。TypableMapとは、受け取った全ての発言にある長さの文字列を結びつけ、その文字列で発言を特定するための機能です。具体的には、

12:34:56 syonbori: こうすることで協調型仮想計算機ができるんだ。(ka) ←コレ

このようになります。ここでは、この発言のTypableMapは"ka"となります。

この発言に返信するには、TypableMapの返信コマンド"re"を使います。具体的には、

re ka にわかには信じがたい

IRCクライアントから入力します。すると、

12:35:12 syonbori_test: @syonbori にわかには信じがたい

このように、@syonboriへの返信が行われます。Webなどで確認すればわかりますが、
syonbori宛、と表示されており、どの発言への返信なのかもわかります。

このTypableMapは、返信のほかお気に入りへの追加/削除も可能です。
返信と同様に、"fav"/"unfav"コマンドに続けてTypableMapを指定すると、
その発言をお気に入りに追加/削除することができます。

TypableMapの設定

TIGのバージョンアップに伴い、TypableMapの設定の仕方が変わっているのでここで紹介します。

  1. とりあえずTIGを起動し、IRCクライアントで接続します。
  2. #Console というチャンネルに参加します。(例: /join #Console)
  3. これで管理用コンソールにログインできました。設定を行う"Configコンテキスト"に移動するため、"config"と入力します。
  4. "show"と入力すると、設定可能な項目一覧と現在の値が表示されます。
  5. まずは、TypableMapを有効化します。"set EnableTypableMap true"と入力します。
  6. 次に、TypableMapの長さを指定します。"set TypableMapKeySize 1"とすると、TypableMapの文字列の長さが1×2で2文字になります。2を指定すれば、4文字になります。
  7. 最後に、TypableMapの文字列の色を指定します。"set TypableMapKeyColorNumber 4"とすると赤くなります。色と番号の関係は、http://hiki.koubou.com/IRC/?mIrcColorsをご覧ください。

これでTypableMapが有効になりました。一度設定を行えば、次回以降は同じことを行う必要はありません。

TypableMapとコマンド

TypableMapは次のようなコマンドで使用できます。{tm}は対象となる発言のTypableMap文字列です。

re(返信)
re {tm} 返信メッセージ で指定された発言への返信を行う
fav(お気に入りに追加)
fav {tm} で指定された発言をお気に入りに追加する
unfav(お気に入りから削除)
unfav {tm} で指定された発言をお気に入りから削除する
h(その人のホームのURLを表示)
h {tm} で指定された発言をした人のホーム(http://twitter.com/名前)のURLを表示する
u(その発言のURLを表示)
u {tm} で指定された発言のページのURLを表示する

TypableMapを拡張する

TIGでは、RubyPythonを使って機能拡張を行うスクリプトを作成することができます。

サンプルとして、TIGのディレクトリ/DLRScriptSamples にいくつかスクリプトが置いてあります。
このスクリプトを、実行ファイルのディレクトリ/Configs/ユーザ名/Scripts というディレクトリに配置することで、スクリプトが使用可能になります。ディレクトリが存在しない場合には、自分で作ってください。

typablemap.rb

RubyでTypableMapを拡張する処理を見ていきます。ここでは、ReTweetするための"rt"コマンドを追加している、typablemap.rbを見ていきます。

typablemap.rbの中身ですが、先頭の

module Misuzilla::IronRuby
  module TypableMap
    include Misuzilla::Applications::TwitterIrcGateway::AddIns::TypableMap

から

    setup
  end
end

まではおまじないだと思ってOKです。具体的には、AddIns::TypableMapをRubyで拡張する宣言や、スクリプト読み込み時にコマンドを追加するための関数、追加したコマンドを終了時に解放する関数などがあります。

"rt"コマンドを追加している部分は、

# TypableMap: rt コマンドを追加する
Misuzilla::IronRuby::TypableMap.register("rt", "ReTweet Command") do |p, msg, status, args|
  System::Diagnostics::Trace.WriteLine("RT: #{status.to_string}")

  Session.RunCheck(Misuzilla::Applications::TwitterIrcGateway::Procedure.new{
    updated_status = Session.update_status("RT: #{status.text} (via @#{status.user.screen_name})")
    Session.send_channel_message(updated_status.text)
  }, System::Action[System::Exception].new{|ex|
    Session.send_channel_message(msg.receiver, Server.server_nick, "メッセージ送信に失敗しました", false, false, true)
  })

  true # true を返すとハンドルしたことになりステータス更新処理は行われない
end

ここです。ぱっと見分かりづらいですが、流れは

  1. ReTweet発言を行う
  2. その発言結果をチャンネルに表示する

これだけです。わかりにくいのは、Session.RunCheckという関数にブロックを渡しているからでしょう。
RunCheck関数は、渡された処理を実行して、必要に応じてエラー処理を行っているようです。(未確認)

つまり、本質の部分は

    updated_status = Session.update_status("RT: #{status.text} (via @#{status.user.screen_name})")
    Session.send_channel_message(updated_status.text)

ここだけです。このあとのブロックは、エラー処理です。
ここでは、

"RT: #{status.text} (via @#{status.user.screen_name})"

として文字列を作成し、Session.update_status()関数を呼び出しています。
これにより、その発言がTwitterに対して行われます。返値は発言ステータスを表すStatusクラスのインスタンスです。
最後に、エコーバックのために発言した内容をsend_channel_message()関数で表示します。

なお、最後の"true"は返値です。trueを返すと、TypableMapコマンドが受理されたことを意味し、Twitterへの発言にはなりません。falseにしてしまうと、TypableMapコマンドそのものもTwitterに投稿されてしまうので要注意です。


これだけで、

rt {tm}

とすると、

RT: にわかには信じがたい (via @syonbori_test)

このような発言が行われるようになります。ね?簡単でしょ?

もっと自分用に拡張する

※ここで取り上げるコードの全文は末尾に記載しています


では、自分でいろいろと拡張してみましょう。
Scriptsディレクトリに、自分用の拡張スクリプトを配置します。
序盤はtypablemap.rbと一緒なので、typablemap.rbをコピーしてリネームすると良いでしょう。
もちろん、typablemap.rbに追記しても構いません。

スクリプトに変更を加えた場合でも、TIGを再起動する必要はありません。
#ConsoleのDLRコンテキストで、reloadコマンドを実行すれば、リロードが行われます。

  1. #Consoleにjoinする
  2. (exit コマンドでルートコンテキストに移動)
  3. DLR コマンドでDLRコンテキストに移動
  4. reload コマンドでリロード
発言者の情報を表示する

発言者の情報を表示するi(info)コマンドを、次のようにして作成します。"rt"コマンドと同じように、おまじないのあとに書いてください。

# TypableMap: i(info) コマンドを追加する
Misuzilla::IronRuby::TypableMap.register("i", "Info Command") do |p, msg, status, args|
  Session.RunCheck(Misuzilla::Applications::TwitterIrcGateway::Procedure.new{
    u=status.user
    outtext = " #{u.screen_name}: name=#{u.name} /" +
              " URL=#{u.url} / http://twitter.com/#{u.screen_name}" + 
              " Location=#{u.location}" + (u.protected ? " (protected)" : "")
    Session.send_server(Misuzilla::Net::Irc::NoticeMessage.new(
    			msg.receiver,
    			outtext
    			))
  }, System::Action[System::Exception].new{|ex|
    Session.send_channel_message(msg.receiver, Server.server_nick, "メッセージ送信に失敗しました", false, false, true)
  })

  true # true を返すとハンドルしたことになりステータス更新処理は行われない
end

発言者の情報が見たいだけで、Twitterに投稿する処理ではありません。
そのため、発言するためのupdate_status()関数は使わず、
send_server()関数を使って、サーバが発言者の情報をクライアントに返す処理にします。

実行例:

20:33:24  syonbori: @syonbori_test てすてす (ai)
20:33:36  syonbori_test: i ai
20:33:36  syonbori_test:  syonbori: name=sidとかterrorとか / 
          URL=http://d.hatena.ne.jp/syonbori_tech/ / http://twitter.com/syonbori
          Location=Tsukuba, Ibaraki, JAPAN
   ↑これはサーバからのNoticeメッセージで、実際にはTwitterに発言されない


TypableMapで指定された発言に関する情報は、status変数に格納されています。
status.user(status変数のuserメンバ)に、発言者の情報が格納されています。


これが分かれば、あとは文字列を適当に整形して、send_server()関数で表示させるだけです。

ね?簡単でしょ?


statusやstatus.userがどんなメンバを持つのかは、http://www.misuzilla.org/~mayuki/misc/twitterircgateway/core-docs/を見てください。SubVersionレポジトリから、プロジェクトを丸ごと落としてきて、コードを眺めるのも良いでしょう。

"re"コマンドを拡張する

どうやら、Rubyで"re"コマンドを定義すると、既にTIGが持っているTypableMapの"re"コマンドを上書きできるようです。

そこで、"re"コマンドを返信メッセージ無しで実行した場合に、返信発言を行わないように拡張してみましょう。
これは、"fav"と"re"を間違えてしまい、ふぁぼるつもりが空の返信発言になってしまった!ということを予防します。

# TypableMap: re コマンドをオーバーライド
Misuzilla::IronRuby::TypableMap.register("re", "Reply Command") do |p, msg, status, args|
  Session.RunCheck(Misuzilla::Applications::TwitterIrcGateway::Procedure.new{
    # 空reply(@hogehogeだけ)をチェックする
    if args.to_s != ""
      u=status.user
      reply_msg = "@#{u.screen_name} #{args}"
      updated_status = Session.update_status(reply_msg, status.id)
      Session.send_channel_message(updated_status.text)
    end
  }, System::Action[System::Exception].new{|ex|
    Session.send_channel_message(msg.receiver, Server.server_nick, "メッセージ送信に失敗しました", false, false, true)
  })

  true # true を返すとハンドルしたことになりステータス更新処理は行われない
end

まぁ、これだけです。ポイントは、args.to_s が空かどうかで発言するかどうかのチェックをしていること、update_status()関数の第二引数にIDを渡すと、そのIDの発言への返信になること、です。

ね?簡単でしょ?


コード全文:

module Misuzilla::IronRuby
  module TypableMap
    include Misuzilla::Applications::TwitterIrcGateway::AddIns::TypableMap

    @@commands = []

    def self.setup
      @@typablemap_proc = Session.AddInManager.GetAddIn(Misuzilla::Applications::TwitterIrcGateway::AddIns::TypableMapSupport.to_clr_type).TypableMapCommands

      # スクリプトアンロード時にコマンドを削除する
      Session.AddInManager.GetAddIn(Misuzilla::Applications::TwitterIrcGateway::AddIns::DLRIntegration::DLRIntegrationAddIn.to_clr_type).BeforeUnload do |sender, e|
        @@commands.each do |command|
          @@typablemap_proc.RemoveCommand(command)
        end
      end
    end

    def self.register(command, desc, &proc_cmd)
      @@commands << command
      @@typablemap_proc.AddCommand(command, desc, ProcessCommand.new{|p, msg, status, args|
        proc_cmd.call(p, msg, status, args)
      })
    end

    setup
  end
end


# TypableMap: i(info) コマンドを追加する
Misuzilla::IronRuby::TypableMap.register("i", "Info Command") do |p, msg, status, args|
  Session.RunCheck(Misuzilla::Applications::TwitterIrcGateway::Procedure.new{
    u=status.user
    outtext = " #{u.screen_name}: name=#{u.name} /" +
              " URL=#{u.url} / http://twitter.com/#{u.screen_name}" + 
              " Location=#{u.location}" + (u.protected ? " (protected)" : "")
    Session.send_server(Misuzilla::Net::Irc::NoticeMessage.new(
    			msg.receiver,
    			outtext
    			))
  }, System::Action[System::Exception].new{|ex|
    Session.send_channel_message(msg.receiver, Server.server_nick, "メッセージ送信に失敗しました", false, false, true)
  })

  true # true を返すとハンドルしたことになりステータス更新処理は行われない
end


# TypableMap: re コマンドをオーバーライド
Misuzilla::IronRuby::TypableMap.register("re", "Reply Command") do |p, msg, status, args|
  Session.RunCheck(Misuzilla::Applications::TwitterIrcGateway::Procedure.new{
    # 空reply(@hogehogeだけ)をチェックする
    if args.to_s != ""
      u=status.user
      reply_msg = "@#{u.screen_name} #{args}"
      updated_status = Session.update_status(reply_msg, status.id)
      Session.send_channel_message(updated_status.text)
    end
  }, System::Action[System::Exception].new{|ex|
    Session.send_channel_message(msg.receiver, Server.server_nick, "メッセージ送信に失敗しました", false, false, true)
  })

  true # true を返すとハンドルしたことになりステータス更新処理は行われない
end
Copyright © 2009 syonbori_tech

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.