今更ながらターミナルで動くTwitterクライアントを作った(C++)

激しく今更感ありますが、Linuxのターミナル等で動くTwitterクライアントを作ってみました。
(Update 2014-07-25)
Windows環境(MSYS2のminttyなど)でも使用できます。

単純な投稿と検索とホーム表示という一応最低限な実装はしています。

1ラインで投稿とかできるので、cronと併用してBOT的な使い方もできます。

Githubにコードを置いたので、実際のコードはここを見てください。
https://github.com/chromabox/cmdline_twitter

あと、バイナリはありません。各自アプリのキーをとってきてコンパイルしてください。

ライセンスは一応MITライセンスになってます。
一応テストはしましたが、未保証です。

##事の経緯

c++でTwitterクライアントを実装する場合は多分すでに良いライブラリがありそうですが、自分で色々理解したかったので実装してみました。
(SSLのあたりは面倒だったのでlibcURLを使ってます)

おかげさまで、OAuthへの理解も中途半端でしたが概ね理解できました。

##スクリーンショット
こんな感じです
(UserStreaming指定でタイムラインを表示した例)
![scr001.png](https://qiita-image-store.s3.amazonaws.com/0/45799/ca07d3de-8a3e-30c4-e523-66f7db7119b9.png)

##使い方
githubのREADME
(https://github.com/chromabox/cmdline_twitter/blob/master/README.md)
をみれば書いてありますが、コンパイル必須です。

#コンパイル方法

cmdline_twitterは以下のパッケージを使用しています。

* g++
* STL(libstdc++)
* libcurl-dev (opensslでもnssでも何でもいい)

ので、あらかじめインストールしてください。

Ubuntuの場合は次のようにしたら良いかもしれません。

```console:
$ sudo apt-get install g++ boost-all libstdc++6 libboost-all-dev libcurl4-nss-dev
```

Windows環境(MSYS2)の場合は次のようにすると良いかもしれません

```console:
$ pacman -S gcc gcc-libs libcurl  libcurl-devel
````

次に、http://blog.uklab.jp/web/add-application-for-twitter/ のあたりを参考に、Twitterのdepeloper登録を行い、適当にアプリケーションの登録を行ってください。
コールバックURLは空欄でかまいません。

次に、sample_setkey.shを開いて、CONSUMER_KEY と CONSUMER_SECRET を書き換えます。
書き換え終わったら、

```console:
$ . ./sample_setkey.sh
$ make
```

でクライアントが作成されます。
. ./sample_setkey.sh は環境変数を設定しています。
一度設定したら、ターミナルを閉じるまでしなくてOKです。

#コマンドライン

コマンドは ctw です。
以下のオプションがあります。

* -h | --help          Print this message
* -a | --auth          [再]認証を行う -u オプションでエイリアスを指定できます
* -p | --post status   タイムラインへ投稿
                     -i オプションでそのIDに対してのリプライ動作<bt>
                     (@は自分で付けてください。@省略時は@が自動付与されます)<bt>
* -s | --search word   ワードで検索
* -r | --readtl      ホームのタイムラインを読む<br>
                     -x オプションでUserStreamを使って読む(以後のオプションは無視)<br>
-n オプションでユーザ名指定すると指定ユーザを<br>
-n オプションで""と指定すると自分の発言を読む<br>
-n オプションで"@"と指定すると自分へのメンションを読む
* -d | --del           発言の削除 -iでID指定
* -R | --Retweet       リツイートする -iでID指定
* -F | --Fav           お気に入りに追加する -iでID指定
* -l | --list name     自分のリストnameの内容を読む<br>
                     nameで""と指定すると自分のリスト一覧を表示する
* -n | --name        指定が必要な場合のユーザスクリーンネーム
* -u | --user alies    エイリアス名指定:省略可(-a とも併用可能
* -x | --xstream       Streaming APIを使う(使用可能な場合)
* -T | --Test          (テスト用)APIのエンドポイントを指定してAPIリクエストを行う<br>
                     -x オプションでStreaming向け接続を行う
* -v | --verbose       (デバッグ用)余計な文字を出力しまくる

まず認証する必要があるので、-a オプションで認証をしてください。
-u オプションでユーザエイリアスを指定することができます。(指定しなくても運用はできます)
エイリアスは複数アカウントを運用するときに使うと便利です。

ユーザのトークンキーとシークレットは /home/現在のLinuxログインユーザ名/.ctw/ 以下に保存されます。
(~/.ctw/ 以下です)
.authkey_エイリアス名 がそれですので、取り扱いには注意してください。

#例示

* エイリアス名eggで認証する
ctw -u egg -a

* エイリアス名eggで投稿
ctw -u egg -p 'i am egg'
(シングルクォーテーションで囲っておくとbashなどの特殊文字に引っかかりません)

* エイリアス名eggのHomeタイムラインを見る
ctw -u egg -r

* ｢github｣という投稿があるかどうか検索する
ctw -s github

##制限など

* タイムラインの取得はデフォルトではREST APIを使っています。<br>
UserStreamで取得したい場合は、-r -x としてください。
* 今のところ、Homeタイムラインの表示でRTや@は表示しない仕様になっています。
* しかし、Userタイムラインの表示はRTや@は表示します。
* ターミナルはUTF8と仮定しています。
* MSYS2の環境ではタイムラインの時刻が標準時刻になっています。

##その他

使用している端末によっては全角文字がずれます。
例；■など
これは、Unicodeのある文字の文字幅があいまい(ambiguous)と定義されていることに由来しています。
http://ja.wikipedia.org/wiki/%E6%9D%B1%E3%82%A2%E3%82%B8%E3%82%A2%E3%81%AE%E6%96%87%E5%AD%97%E5%B9%85

ambiguous設定が指定できる端末では一応ちゃんと表示することはできます。
もし、Ubuntuを使っている場合は以下に書いてある方法で解決するかmltermを使うとよさそうです
https://gist.github.com/sgk/5991138

ただし、mlterm以外の上記方法では今度は罫線が２文字として認識されてしまうので、使用状況によっていろいろとお試しください。
(Ubuntuがデフォルトでこうなってしまうのは、おそらくユーザの使用状況によって変わるので決めかねているのかもしれません)



##TODO

* 画像系アップロードへ対応
* 対話モードの追加
* なるべくライブラリに依存しない
* 見栄えをよくする
* 色指定できるようにする
* 他の環境への対応


##つまづいたこととか

* Twiterは投稿日時をUTCで返すので時間を表示するときにローカル時間に直すところとか。<br>
そういうコードは滅多に書かないので、ちょっとなやんだ。　
* OAuthで、認証に必要なパラメータだけでなく、POSTやGETデータもまとめて昇順にしてHMACしないといけないところ。<br> 最初は別々にするのかと思ってたので通らないときがあった。
* libCurlでPOSTデータが何もない場合でPOSTリクエストを送ると落ちる件。<br>POSTは何かしらのデータがあって当たり前なので正しいといえば正しいのかも…
* 文字コードを16進変換するところで恥ずかしいハマり方をした。

```cpp:

  const char* s = src.c_str();
  ...
  stream << setfill('0') << setw(2) << uppercase << hex 
     << static_cast<unsigned int>(*s);

```

とかやってて、特に日本語で正しく出力されない場合があって悩んだこと。
(E2を返してほしいのに、FFFFFFE2とかなってた。setwまったく効かない上になんかマイナス拡張されてる感)

なお、こうやったら解決した。

```cpp:

  ...
  stream << setfill('0') << setw(2) << uppercase << hex 
     << static_cast<unsigned int>(static_cast <unsigned char>(*s));

```

sprintf()使えば悩み無用だったんですけど、使ったら負けな気がしたので…。
なお、boostのformat()でも同じことが起こりますので、変数の型には気をつけましょう。


##参考文献など

* picojsonのkazuho氏 (https://github.com/kazuho/picojson)
JSONの解析に使用しています。picojsonはSTLの親和性高くて便利です
* OAuthの仕様書など　(http://www.openid.or.jp/document/)