arcanum_jp’s blog

おっさんの日記

Twitter botが作りたい。

 Twitter botが作りたくて調べています。botにツイートされたら構文解析してツイートし返すという簡単なものです。いつものツイッターIDでbotを作ってしまうとbot向けにツイートされたものでタイムラインが埋まってしまいますので、いつも使っているツイッターIDと、bot用のIDは分ける必要があるようです。


 Javaの場合はtwitter4jというライブラリがあるらしく、TwitterAPIを生で使うなんてそんな・・・と言うヘタレな僕はこのライブラリを神と崇め使っていきたいと思います。


 Twitterアプリケーションを作る場合はそのアプリケーションをTwitterに登録申請する必要があります。bot用のIDでログインした後、次のURLでTwitterアプリケーションを登録します。

ようこそ開発者のみなさん! Twitterアプリケーションプラットフォーム・ベータ版へ。
連携アプリケーションはまだ始まったばかりですが、開発を手助けするいくつかの機能
を公開しています。今すぐにユーザの皆様をTwitterの世界につなげましょう!

http://twitter.com/oauth_clients/new
  • アプリケーションのアイコン
    • 画像は700KB以下でなくてはいけません。
    • 対応フォーマットはGIF、JPEGPNGです。
    • デフォルトのアイコンは72px x 72pxみたいです。
  • アプリケーション名
    • アプリケーション名を入力する。
    • 「熱いィートbot」とか
  • アプリケーションの説明
    • 説明を入力します。
    • 「フォロワーに熱いメッセージを送り応援するbot」とか
  • アプリケーションのウェブサイトURL
    • Webサイト系のTwitterアプリなんかは、この部分にTopページを入力する
  • 所属会社/団体
    • 個人とか
  • サイト
    • 自分のサイト、ブログとか。
  • アプリケーションの種類
    • クライアントアプリケーション
    • ブラウザアプリケーション
  • コールバックURL
    • アプリケーションの種類が、ブラウザアプリケーションの場合必須
  • 標準のアクセスタイプ
    • Read & Write
    • Read-only
  • Twitterでログインする
    • ユーザーにログインさせて使わせるアプリの場合必要
    • botの場合は不要?

登録に成功すると、次のようなデータが表示されます。

# Consumer key
XXXXXXXXXXXXXXXXXXXXXX
# Consumer secret
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# フォローをリクエストしました。
http://twitter.com/oauth/request_token
# Access token URL
http://twitter.com/oauth/access_token
# Authorize URL
http://twitter.com/oauth/authorize

このConsumer key と Consumer secretがTwitterアプリケーション側で必要になるのでメモしておきます。尚、この画面は、「設定」⇒「コネクション」⇒「連携アプリの追加と設定はこちらから変更できます。」の「こちら」のリンクで見ることができます。


 このConsumer keyとConsumer secretのほかにAccess tokenとAccess token secretが必要です。これは、OpenIDのログインのように、botのIDの認証が必要になり、その後、発行されます。


 Access tokenの取得には、twitter4jのサイトにはサンプルがあります。Eclipseなんかで実行すると、標準出力にURLが出力されますので、このURLをブラウザーに貼り付けて、botのIDで認証を行います。すると、PIN番号が表示されますので、またEclipseなどの実行したところに戻ってこの番号を入力します。

最初はユーザアカウントへのアクセス権がありません。以下のように authorization URLに
ユーザを誘導し、AccessToken を取得する必要があります。

http://twitter4j.org/ja/code-examples.html#oauth


 このサンプルを使う注意点として、最期にTwitter#updateStatus()を使っていますので、先ほどのアプリケーションの登録で入力した「標準のアクセスタイプ」の項目は「Read & Write」である必要があります。あとTwitter#updateStatus()を使用する際の引数がこのサンプルクラスへの実行引数になっているので、面倒な場合はこの引数部分を任意の文字列に変更します。実行したあと、Eclipseなどの実行した標準出力と、bot用のタイムラインにTwitter#updateStatus()で指定された引数が出力されます。



 他にもAccess tokenの取得には以下のページでも公開されています。

以下の TwitterOAuthAccessTokenGetter クラスを利用して、
認可キー(TokenとTokenSecret)を取得する。

http://www.nilab.info/zurazure2/001125.html

以下は、上記のサイトのものをコピペして20行目をちょっと変更しただけです。NI-Labさん、ありがとうございます。こちらの方が「標準のアクセスタイプ」の項目は「Read & Write」である必要はなく分かりやすいようですね。

import java.io.*;
import twitter4j.*;
import twitter4j.http.*;
 
public class TwitterOAuthAccessTokenGetter {
  
  public static void main(String[] args) throws Exception {
    
    // http://twitter.com/oauth_clients/new にてアプリケーションを登録した際の
    // Consumer key と Consumer secret を使用
    String consumerKey = ""; // Consumer key をセット
    String consumerSecret = ""; // Consumer secret をセット
    
    // 認証用URLを取得
    TwitterFactory factory = new TwitterFactory();
    Twitter twitter = factory.getOAuthAuthorizedInstance(consumerKey, consumerSecret);
    RequestToken requestToken = twitter.getOAuthRequestToken();
    String authorizationURL = requestToken.getAuthorizationURL();
    
    System.out.println(authorizationURL);
    System.out.println("認証を許可したらウェブブラウザにPINコードが表示されます。");
    System.out.println("PINコードを入力して[Enter]キーを押してください。");
    
    // 入力待ち
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    String pin = r.readLine();
    
    // AccessTokenオブジェクトを生成 
    AccessToken accessToken;
    if(pin.length() > 0){
      accessToken = twitter.getOAuthAccessToken(requestToken, pin);
    }else{
      accessToken = twitter.getOAuthAccessToken();
    }
 
    // 認証情報を表示
    int userId = twitter.verifyCredentials().getId();
    String token = accessToken.getToken();
    String tokenSecret = accessToken.getTokenSecret();
    System.out.println("UserId=" + userId);
    System.out.println("Token=" + token);
    System.out.println("TokenSecret=" + tokenSecret);
  }
}

 これでAccess tokenとAccess token secretが取得できました。ちなみに先ほど登録したアプリケーションは、「設定」⇒「コネクション」に表示されるようになります。設定を変更したければ、再度、「連携アプリの追加と設定はこちらから変更できます。」の「こちら」のリンクをたどっていきます。


 このAccess tokenを取得する行為ですが、何度もすると当然Access tokenはそのたびに異なります。しかし一度取得したAccess tokenは有効期限がないらしいので、自分のIDにTwitterアプリケーションとしてbotを登録するのであれば1回取得したAccess tokenを未来永劫使い回しでいいのでしょうか。

ご指摘の通り、現在の所 Twitter では Access Token の有効期限を設けていません。
ユーザーがアクセス許可を手動で取り消す可能性がありますので、それに対する対応と同じです。
有効期限向け固有の実装は恐らく必要ありません。
メソッドコール時に例外が発生(恐らく TwitterException.getStatusCode()== 401)したら
Access Token が無効になったという処理にしておけば良いと思います。

http://d.hatena.ne.jp/kenji_jp/20100308/1268064528

※ 改行はnigredoにて行った

 Webアプリ系は、上記のコメントのように401が帰ってきたら再度access tokenを取得する処理へ画面を移行させるのでしょう。botのように未来永劫リッスンし続けるようなアプリの場合はどうすんのかなぁ・・・と思ってしまった。多分何もしなくとも良いんだろうけど。まぁ、それは後で考えるとして・・・

2010/12/14追記
 たまにAccess Tokenが無効になる。多分Twitter自身が再起動なんかして、無効になるのかな?

 自分ツイートのフッキングはUserStreamListenerの実装クラスであるUserStreamAdapterクラスを継承したものを作ればよいようです。このリスナーインターフェースの説明については、以下に詳しい説明があります。

UserStreamListenerインターフェイスはStatusListenerインターフェイス
サブインターフェイスでUserStreamのデータを受信するために使用します。
StatusListenerで受信出来るデータに加え、デスクトップアプリケーションの
表示の更新をする際に必要な全てのデータを受信出来ます。

http://sites.google.com/site/elekmole/twitter4jtop/streaming-api-method/userstreamlistener


 次に、このアプリケーションをコネクションに追加したユーザーのツイートをツイートされたイベントをフッキングする処理を書く。以下はblog.mtzw.jp/?p=78で公開されているコードのコピペしてちょっと変更。mtzwさんありがとう。

TwitterのStreamingAPIがとても楽しいので、簡単なサンプルコードをのっけてみる。
※twiter4jは2.1.5以上が必要です。

http://blog.mtzw.jp/?p=78


 以下は上記のサイトからコピペしたコードに、自分用のメモとしていくつか追加したものです。

import twitter4j.Status;
import twitter4j.TwitterStream;
import twitter4j.TwitterStreamFactory;
import twitter4j.UserStreamAdapter;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;
import twitter4j.util.TimeSpanUtil;

public class StreamingSample extends UserStreamAdapter {

    static TwitterStream twitterStream;
    static {
        ConfigurationBuilder builder = new ConfigurationBuilder();
        builder.setUseSSL(true);
        // -- 以下は http://twitter.com/apps/new からもらってくる
	builder.setOAuthConsumerKey("ConsumerKey");
        builder.setOAuthConsumerSecret("ConsumerSecret");
        // -- 以下は ConsumerKeyとConsumerSecretを基に取得したもの
        builder.setOAuthAccessToken("AccessToken");
        builder.setOAuthAccessTokenSecret("AccessTokenSecret");
        // -----
        Configuration conf = builder.build();

        twitterStream = new TwitterStreamFactory(conf).getInstance();
    }

    public void start() {
        //ts.setStatusListener(this);	// deprecated
        ts.addListener(this);
        twitterStream.user();
    }

    public void onStatus(Status status) {
        System.out.println(new StringBuilder()
                .append("- ")
                .append(status.getUser().getScreenName())
                .append(": ")
                .append(status.getText())
                .append(" [")
                .append(TimeSpanUtil.toTimeSpanString(status.getCreatedAt()))
                .append("]"));
    }

    public void onException(Exception e) {
        e.printStackTrace();
    }

    void onFollow(User source, User target){
        // source <-- 誰が
        // target <-- 誰をフォローした
        
    }

    void onUnfollow(User source, User target){
        // source <-- 誰が
        // target <-- 誰をフォロー解除した
    }

    public static void main(String[] args) {
        new StreamingSample().start();
    }

} 

 これでこのコードを実行するとストリームが開き、待ち状態になるので、bot用のIDやこのbot用IDフォローしたIDでツイッターでツイートしてみる。そうすると標準出力に出力されます。

 onStatus()でツイートを解析してリツイートやり何なりしてやればよいようです。フォローしていないとこのメソッドは当然ですが呼び出されないので、onFollow()でbotの対象ユーザーをフォローしてやる、onUnfollow()でbotの対象ユーザをフォローの解除をしてあげればよいようです。


 上記のコード、Eclipseで実行すると、ストリームが開きっぱなしになります。何度も実行するとプロセスが複数残ったままになるので、実際のTwiterアプリでは、ストリームを閉じる処理をどっかに記載する必要があるようです。TwitterStream#shutdown()がそれにあたるんだと思います。<-- これウソ

2011/01/21追記

 もちろん自分宛のダイレクトメッセージなんかも受信できます。そのときはpublic void onDirectMessage(DirectMessage mess)をオーバーライドします。各オーバーライドしたメソッドを実装する際に注意点ですが、自分宛のメッセージのほかに、自分が流したメッセージも来てしまうので注意です。例えば、上のonFollow( )で、フォローしてくれた人にダイレクトメッセージを送信したとします。すると、自分が流したダイレクトメッセージもonDirectMessage( )にきてしまいます。なので、自分宛のメッセージを無視する処理を忘れずに。

void onFollow(User source, User target){

    // source <-- 誰が
    // target <-- 誰をフォローした

    // どうにかしてTwitterインスタンスを取得する。    
    TwitterFactory tf = new TwitterFactory(...);
    Twitter tw = tf.getInstance();

    // フォローし返して感謝メッセージ
    tw.createFriendship(source.getScreenName());
    tw.sendDirectMessage(source.getId(), "Thank U!!");
    
}

public void onDirectMessage(DirectMessage mess){

    // どうにかしてTwitterインスタンスを取得する。    
    TwitterFactory tf = new TwitterFactory(...);
    Twitter tw = tf.getInstance();

    // ダイレクトメッセージが自分が出したものなら無視
    if(mess.getSender().getScreenName().equals("俺様")){
        return;
    }

    ... その他処理

}

※try〜catchは無視。あくまでイメージです。

 コレを見て、onFollowでフォロー仕返したら、自分のフォローもonFollow( )に来るんでは・・・ハイ、正解です。無視するなり、なんなりしてください。