最近ホットなツイッターを昔ながらのクールなC言語でなんとか扱ってみた。

最近ホットなツイッターを昔ながらのクールなC言語でなんとか扱ってみた。

久しぶりのソフトウェアネタです。ちょっと時代の流れに乗って。。。みようとしました。

すこし前くらいから、ツイッターとかフェイスブックとかが流行ってますよね。昨今の話題はSNSとクラウドでしょうか。流行に乗るべく、マスコットにツイッターのタイムラインをしゃべらせる機能をオプションプログラムで実装してみることにしました。

(Twitter API) http://usy.jp/twitter/index.php?Twitter%20API

ところが、どうしたことかTwitterをC言語で扱う解説サイトがあんまりないのです。phpやperlのようにWEBをベースとしたものが多い様子。なるほど、Twitterのように最近出てきたものに関しては過去の資産はアドバンテージになりにくいようです。

しかも、Twitterの方も最近セキュリティを向上させる仕様変更を行ったようで、これで難易度がグッと上がってしまったようです。トホホ。。。

(IT Media記事)http://www.itmedia.co.jp/news/articles/1004/27/news063.html

いつもの通り前置きが長くなりましたが、やることはたいしたことないのはいつものことです。

まず、先ほどのAPIのページで唯一C/C++で紹介されているサイトにいってみるとすばらしいサンプルコードがあります。感謝です。ただ、これは投稿するアプリなので、タイムラインを取得するように変更する必要があります。

前準備としてツイッターにアプリを登録しておく必要があります。下にリンクを書きますが、心配なら『Twitter Developer』あたりでググってみてください。「Create an App」で下のページに移動します。

(Twitter developer) https://dev.twitter.com/apps/new

イメージ

登録に必要なのは『アプリ名』と『簡単な解説』、『ホームページのアドレス(アプリの公開サイト等)』の3つくらいです。登録がすんだら、『consumer key』と『consumer secret』というキーが発行されます。大事なのでちゃんと記録しておいてください。

手順としては、このキーを使ってアクセスして自分のアカウントにログインすると、認証許可のページになります。アプリ名などは先ほど登録したものです。

イメージ

ここで認証するとPINが発行されます。

イメージ

(本当は数字が表示される)

発行されたPINを使って再度アクセスして認証をもらいます。認証を得られると『access key』と『access secret』というキーが発行されます。

認証は1回すればよく、あとはさきほどの『consumer key』と『consumer secret』と合わせた4つのキーでアプリからアクセスできるようになります。

興味深いのは、アプリ側が取得しているのは『ランダム文字列と思われるキー』だけで、ユーザーIDやパスワードなどは知らないでもアクセスできるという点です。なるほど、セキュリティ的に強いというのはダテじゃないということなのでしょう。

それでは早速コード説明です。まず、上記のTwitter APIのページから『言語別、APIツールや参考になる資料』の『C++』のところに移動し『そらみみの声』さんのページに行きます。

(そらみみの声) http://www.soramimi.jp/twicpp/

ここで『twicpp.zip(ソースコードのみ)』をおいしく頂きまます。 『main.cpp』の『void twitter_example()』関数を参考にして3つの関数を作ります。

  1. 『consumer key』と『consumer secret』を使って認証許可のページを表示させる。
  2. 1.で取得したPINを使って『access key』と『access secret』を取得する。
  3. 4つのキーを使ってタイムラインを取得する。

1と2はそのまんまです。サンプルソースは投稿するプログラムなので3はちょっと手を加えます。

ソースコード

/* 1. PINを取得する関数 */

//ここはさっき取得したキーを入れておく
static char const consumer_key[] = "xxxxxxxxxxxxxxxxxxxxxx";
static char const consumer_secret[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
int twitter_getpincode( char *tkey_buf , int size )
{
    static const char *request_token_uri = "http://api.twitter.com/oauth/request_token"
    static const char *authorize_uri = "http://api.twitter.com/oauth/authorize"
    static const char *access_token_uri = "http://api.twitter.com/oauth/access_token"
    std::string c_key; //< consumer key
    std::string c_secret; //< consumer secret
    std::string t_key;
    std::string t_secret;
    std::string req_url;
    std::string reply;
    std::string postarg;
    //コンシューマーキーとコンシューマーシークレットを代入
    c_key = consumer_key;
    c_secret = consumer_secret;
    //アクセス
    {
        std::string reply;
        std::string req_url = oauth_sign_url2(request_token_uri, &postarg, OA_HMAC, 0, c_key.c_str(), c_secret.c_str(), 0, 0);
        reply = oauth_http_post(req_url.c_str(),postarg.c_str());
        if (!parse_reply(reply.c_str(), &t_key, &t_secret))
        {
            //処理に失敗しました。
            return FALSE;
        }
    }
    //t_keyコピー
    strncpy( tkey_buf , t_key.c_str() , size );
    //PINを表示させる
    {
        std::string req_url = oauth_sign_url2(authorize_uri, 0, OA_HMAC, 0, c_key.c_str(), c_secret.c_str(), t_key.c_str(), t_secret.c_str());
        printf("Opening...\n");
        puts(req_url.c_str());
        ShellExecute(0, 0, req_url.c_str(), 0, 0, SW_SHOW);
    }
    return TRUE;
}

注意するべきなのは、どうやら仮のaccess_keyが取得されているようで2.で必要なのでコピーしておく。(違うかも、でもこのキーは必要)

ソースコード

/* 2. PINを使ってアクセスキーとアクセスシークレットを取得する関数 */

int twitter_setpincode( char *tkey_buf , int code )
{
    static const char *access_token_uri = "http://api.twitter.com/oauth/access_token"
    std::string c_key; //< consumer key
    std::string c_secret; //< consumer secret
    std::string t_key;
    std::string t_secret;
    std::string req_url;
    std::string postarg;
    std::string reply;
    //コンシューマーキーとコンシューマーシークレットを代入
    c_key = consumer_key;
    c_secret = consumer_secret;
    {
        char buffer[256];
        std::string url = access_token_uri;
        url += "?oauth_verifier=";
        url += itoa( code , buffer , 10 );
        std::string req_url = oauth_sign_url2(url.c_str(), 0, OA_HMAC, 0, c_key.c_str(), 0, NULL , 0);
        std::string reply = oauth_http_get(req_url.c_str(), NULL );
        if (!parse_reply(reply.c_str(), &t_key, &t_secret))
        {
            //処理に失敗しました。
            return FALSE;
        }
    }
    // now retrieved 't_key' is access token and 't_secret' is access secret.
    printf("access key: %s\n", t_key.c_str());
    printf("access secret: %s\n", t_secret.c_str());

//とりあえずファイルにでも書き出しておく
    {
        FILE *fp;
        fp = fopen(".\\access.txt","a");
        fprintf( fp , "%s\r\n%s\r\n" , t_key.c_str() , t_secret.c_str() );
        fclose( fp );
    }
    return TRUE;
}

取得した『access key』と『access secret』をファイルに書き出すようになっています。先ほどの『consumer key』と『consumer secret』はアプリケーションで固定なのでプログラムコードに組み込んでしまっても問題ありませんが、こちらはユーザーによって異なるのでファイルなどに書き出して保存することになります。サンプルコードは平文ですが、実際に実装する際にはセキュリティに配慮する必要があります。

ではサンプルソースの『main.cpp』の『main関数』を書き換えて実験してみます。
Socket::initialize();は初期化するために必要なので残します。

ソースコード

int main (int argc, char **argv)
{
    int pin;
    char tkey_buf[256];

    Socket::initialize();

    twitter_getpincode( tkey_buf , 256 );
    scanf( "%d" , &pin );
    twitter_setpincode( tkey_buf , pin );
    return 0;
}

これでキーが取得できたと思います。

続いてタイムラインの取得です。
サンプルソースは投稿するアプリなので書き換えが必要です。
ポイントとなるのは以下の2つの関数。
oauth_sign_url2
oauth_http_post

まず、oauth_sign_url2は第2引数の有無で”GET(取得)”か”POST(投稿)”かを判別しています。ちゅうことで、ここをNULLにします。( 当初は&postargになっているハズ )

つづいて、oauth_http_postはそのままoauth_http_getに変更します。
なお、最初のtwitter_getpincodeにはゲットとポストの両方が扱われているので参考になります。

URLは http://api.twitter.com/1/statuses/home_timeline.xmlになります。

後ろに続く文字列で取得する範囲を指定します。
?since_id=id(ID) あるツイートのID以降を取得
?count=n(数) 最新のn個を取得
詳しくはTwitter APIのサイトにある『Twitter API 仕様書日本語訳』を参照してください。

(Twitter API 仕様書日本語訳) http://watcher.moe-nifty.com/memo/docs/twitterAPI50.txt

ソースコード

/* 3.タイムラインを取得する */

//とりあえずテストなのでプログラムにさっき取得したキーをプログラムに入れておく
static char const access_key[] = "00000000-xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
static char const access_secret[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
int twitter_timeline( char *lastid , char *text , int text_size )
{
    std::string c_key;
    std::string c_secret;
    std::string t_key;
    std::string t_secret;
    std::string req_url;
    std::string reply;
    char buffer[256];
    c_key = consumer_key;
    c_secret = consumer_secret;
    t_key = access_key;
    t_secret = access_secret;
    std::string uri = "http://api.twitter.com/1/statuses/home_timeline.xml"
    uri += "?since_id=";
    uri += lastid;
    /* 最新の○件の場合 */
    /* uri += "?count="; */
    /* uri += "5"; */
    req_url = oauth_sign_url2(uri.c_str(), 0, OA_HMAC, 0, c_key.c_str(), c_secret.c_str(), t_key.c_str(), t_secret.c_str());
    reply = oauth_http_get(req_url.c_str(), NULL );
  /* ファイルに書き出す */
FILE *fp;
fp = fopen(".\\test.xml","w");
fputs( reply.c_str() , fp );
fclose( fp );
    return TRUE;
}

とりあえず『xmlファイル』として書き出すように作りました。
ブラウザ等で確認してみてください。
プログラム内でデータを使う場合はバッファのままの方が扱い易いと思います。
1.、2.の認証は不要ですが、Socket:initialize()は必要なので忘れないようにしてください。

さて、これをオプションプログラムに組み込みます。
基本的には上記の関数をそのまんま使います。

main.cppの中にある関数は『parse_reply』を使いますので残して、あとは消してもOKです。
ほかのコードはそのまま手を加えずにプロジェクトに組み込みます。

続いて、load関数にSocket:initialize()を書き加えます。
あとはタイマースレッドあたりを使って、定期的にアクセスしてタイムラインを取得します。

最新 = 最初の<text>と</text>に挟まれた文字列を取得して、FUNCTION-NOTICEでキャラクターに送ります。ただし、取得された文字列は半角英数字は良いのですが日本語などは『数値実体参照』とよばれる方法でエンコードされています。

例えば、お馴染みの『∀』という文字は『&#08704;』といった具合です。このままでは正しく表示できないのでデコードします。これは、『&# + (16bit unicode の文字コード) + ;』でできています(『;』もゴミじゃないです)。なので、上からサーチしていって&#を見つけたら数字を WidecharToMultibyteで変換することで通常のWin32のアプリで扱えるShift_JISなどに変換できます。Fy MascotはUTF-8を扱うのでUTF-8に変換します。

ソースコード

int gicp_sendnotice( char *tweet )
{
    char master[256];
    char follower[256];
    char witchcraft[256];
    char incantation[2048];
    char *buffer;
    char *gicp;
    int length;
    /* 各要素を作る */
    sprintf( master ,
"<place>OPTION</place><detail><name>FY_TWITTER</name></detail>" );
    sprintf( follower , "<place>MASCOT</place><detail></detail>" );
    sprintf( witchcraft , "<class>FUNCTION</class><order>NOTICE</order>" );
    sprintf( incantation ,
"<sender>Fy Twitter</sender><string>%s</string>\r\n<userdata></userdata>" , tweet );
    buffer = gicp_setstring( master , follower , witchcraft , incantation );
/* GICP用のメモリにコピーする */
    length =  strlen(buffer)+1;
    gicp = (char*)gicp_malloc( length );
    memset( gicp , 0 , length );
    strncpy( gicp , buffer , length-1 );

/* すでにUTF-8なので二重変換しないように気をつける */
/* gicp_sendcom ではダメ */
    if(!gicp_com( &gicp , &length ))
    {
        //エラー
    }
    gicp_free( gicp );
    free( buffer );
    return TRUE;
}

こんなところです。
では早速組み込んでみます。

イメージ

いまいち、画像ではわかりませんが、タイムラインから取得して表示しています。表示できる文字数が少ないので読みづらいですけどね~。

これでちっとは今風になったでしょうか。ツイッターに限らずネットとか活用して色々できたら面白そうだなぁなどと考えているところです。