C++でTwitterAPI を触ってみる[第１回　ツイートのみ]

# はじめに

### 注意！
・Quiita初投稿です。何か不備があるかもしれません。（コメントで教えてください）
・コード全文は<a href="https://github.com/DAICHI7241/CAIOS-for-Twitter">GitHub</a>に公開していますので、`LICENSE`に書かれていることを遵守しながら使用してください。

### C++でTwitterAPIを使いたい
WebAPIは自分で処理を実装しなくても、API提供側がほとんどの処理を行ってくれるというメリットがあります。しかし、その分API提供側が定める形式でコードを書かなければなりません。大抵のWebAPIは簡単な形式でPOSTかGETしておけばレスポンスが帰ってきますが、TwitterAPIに関してはそうは行きません。OAuth認証もしなければなりませんし、いろいろ大変なのです。

そんな大変さもあってか、C/C++でTwitterクライアントを作ろうとする人があまりません。連鎖的に、使われることが少ないライブラリを作ろうと思う人もそういませんから、C++でTwitterAPIを扱うライブラリが非常に少ないのが現状です。

そんな中、OAuthの勉強も兼ねて、OAuth認証も自前で実装してTwitterAPIを使ってみようとやっていたところ、思いの外上手く行ったので投稿したいと思います。

# TwitterAPIの使い方
基本的にはこの<a href="http://westplain.sakuraweb.com/translate/twitter/API-Overview.cgi">リファレンス</a>に載っているとおり、必要なパラメータを乗っけてPOSTするだけです
あと、<a href="https://qiita.com/alpha_kai_NET/items/cdd3ec27a70a77e06c59">こちら</a>も読んでおいたほうがいいと思います。

>TwitterではAPIを使うために、OAuthという認証プロトコルを用います。
そして、そのOAuthに従った形で、そのリクエストが正規のものであることを示し、適切なエンドポイントに適切なパラメーターを渡すことでAPIを使うことができます。

こうサラッと書かれていますが、ここが一番難しいところです。
HMAC-SHA1の部分など難しいところは、できるだけOpenSSLに頼っていきましょう。
次に方針を述べます。

# 方針
プログラムを作成していく方針はこうです。
**「C++を用いて、外部ライブラリにできるだけ依存しない、コンパクトなプログラムを作成する」**

とは言っておきながら、暗号化の部分とHTTPリクエストの部分は外部ライブラリに頼ります...。

暗号化の部分は<a href="https://www.openssl.org/source/">OpenSSL</a>を使用します。ご自身の環境にあったものを選んでください。
HTTPの部分は<a href="https://kagasu.hatenablog.com/entry/2017/10/07/190551">cpprestsdk</a>を使用します。リンク先の手順に沿ってインストールしてください。

C++のTwitterAPIライブラリで唯一完成しているものといえば、<a href="https://soramimi.jp/twicpps/index.html">Twicpps</a>でしょう。メディア投稿にも対応している素晴らしいライブラリです。が、問題はライブラリが大きすぎることです。なので、Twicppsを参考にコンパクトなライブラリにまとめたいと思います。

今回は第１回ですので、ツイートを実装していと思います。

# 開発環境
・Windows10 Home
・Visual Studio 2019
#プログラムを作成する
TwitterAPIを使用するには、keyが必要なのですが、これらは取得済みのものとして話を進めます。もしまだでしたら、<a href="https://www.torikun.com/entry/twitter-developer-api/">この辺のサイト</a>を見ながら、keyを取得してください。

まず、できるものは全て自分で実装したいので、プログラムに何が必要か考えます。

・OAuth認証
・Unicode対応させるためのstring系関数
・URLエンコード、base64エンコード
・HTTP(S)リクエスト

これだけあれば、TwitterAPIを使うことができるでしょう。HTTPリクエストはcpprestsdkに任せるとして、まずはOAuth認証から書いていきます。

###OAuth認証
認証には6つのパラメータを作成し、&で連結してPOSTします。本来であれば、`Authorization`というヘッダをくっつけてPOSTしなければならないのですが、テキストのみのツイートに関しては`Authorization`ヘッダはいらないようです。今後、画像の投稿などもサポートする予定ですが、そのときに詳しくやりたいと思います。
POSTは `https://api.twitter.com/1.1/statuses/update.json` に対して行われます。

|パラメーター|内容|
|:--------------------:|:--------------------:|
|oauth_consumer_key|Twitter consumer key|
|oauth_nonce|ランダムな文字列|
|　oauth_signature_method　|今回は"HMAC-SHA1"|
|oauth_timestamp| シグネチャ作成時のタイムスタンプ |
|oauth_token|Twitter access token|
|oauth_version|OAuthのバージョン 今回は"1.0"|

`oauth_consumer_key`と`oauth_token`は既に取得していますし、`oauth_signature_method`と`oauth_version`と`oauth_timestamp`は文字列をそのまま入れるだけですから、なんてことはないと思います。

問題となるならば、`oauth_nonce`でしょうか。（`oauth_timestamp`の`シグネチャ`に関しては後で書きます）

`oauth_nonce`は本当に何でもいい文字列なので、コードに直接適当な文字列を書いてもいいのですが、それではexeファイルを逆コンパイルされたときに丸見えになってしまいますから、安全性が低くなってしまいます。

そこで、今回はツイート毎に文字列を生産する方法を取ります。

```C++:CAIOS.hpp
auto CreateNonce = []() {
	static const char* chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
	const unsigned int max = 26 + 26 + 10 + 1;
	char tmp[50];
	srand((unsigned int)time(0));
	int len = 15 + rand() % 16;
	for (int i = 0; i < len; i++) {
		tmp[i] = chars[rand() % max];
	}
	return std::string(tmp, len);
};
```

このラムダ式関数を毎度呼び出すことで、安全性は十分確保できるでしょう。

問題はもう一つあります。
`oauth_timestamp`は「シグネチャ作成時のタイムスタンプ」であると書きましたが、`シグネチャ`とは一体なのでしょうか？

英語で書くと、`Signature`。意味は「署名」です。この署名こそが、OAuth認証の肝となる部分です。

APIを提供するサービス側は、アプリケーション側が送ってきたパラメータと、この署名を照合し、署名が正しくない場合、エラーを返します。これにより不正な第三者のリクエストを防ぐ事ができるのです。

この`Signature`の中身を簡単に説明すると、リクエストパラメーターをkeyと一緒に暗号化したものです。Twitterの場合、HMAC-SHA1方式で`Signature`を作成します。

<a href="https://syncer.jp/Web/API/OAuth/">こちらのサイト</a>で非常に分かりやすく解説していますので、よく読んでください。このリンク先の手順に沿って、`Signature`を作成するプログラムを書きます。

```C++:CAIOS.hpp
static string CreateSignature(string ConsumerSecret, string AccessSecret, vector<string>const& OAuth, METHOD method, int Start = 1) {
	string str, key, data, methods;
	char out[256];

	if (method == POST)methods = "POST";
	else methods = "GET";

	key = CAIOS::REST::URL_encode(ConsumerSecret) + "&" + CAIOS::REST::URL_encode(AccessSecret);

	data = methods + "&" + CAIOS::REST::URL_encode(OAuth[0]) + "&" + CAIOS::REST::URL_encode(CreateData(OAuth, method, Start));

	str = sha1(key, data);

	CAIOS::Twitter::OAuth::encode_base64((char*)str.c_str(), str.size(), out);

	return CAIOS::REST::URL_encode(out);
}
```

この辺からオリジナルの関数などが入ってきて分かりづらくなっていると思いますが、コード全文は<a href="https://github.com/DAICHI7241/CAIOS-for-Twitter">GitHub</a>で公開しているのでそちらを参照してください。

以下に、OAuthのパラメータを設定する関数を公開します。

```C++:CAIOS.hpp
static int IntOAuthParams(vector<string>* OAuth, METHOD method) {
	TwitterAPI_Keys key;

	auto CreateNonce = []() {
		static const char* chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
		const unsigned int max = 26 + 26 + 10 + 1;
		char tmp[50];
		srand((unsigned int)time(0));
		int len = 15 + rand() % 16;
		for (int i = 0; i < len; i++) {
			tmp[i] = chars[rand() % max];
		}
		return std::string(tmp, len);
	};

	string oauth_nonce = "oauth_nonce";
	oauth_nonce += "=";
	oauth_nonce += CreateNonce();
	OAuth->push_back(oauth_nonce);

	string oauth_timestamp = "oauth_timestamp";
	oauth_timestamp += "=";
	oauth_timestamp += to_string((int)time(nullptr));
	OAuth->push_back(oauth_timestamp);

	string oauth_token = "oauth_token";
	oauth_token += "=";
	oauth_token += key.Accesstoken;
	OAuth->push_back(oauth_token);

	string oauth_consumer_key = "oauth_consumer_key";
	oauth_consumer_key += "=";
	oauth_consumer_key += key.Consumer_Key;
	OAuth->push_back(oauth_consumer_key);

	string oauth_signature_method = "oauth_signature_method";
	oauth_signature_method += "=";
	oauth_signature_method += "HMAC-SHA1";
	OAuth->push_back(oauth_signature_method);

	string oauth_version = "oauth_version";
	oauth_version += "=";
	oauth_version += "1.0";
	OAuth->push_back(oauth_version);

	sort(OAuth->begin() + 1, OAuth->end());

	string oauth_signature = "oauth_signature";
	oauth_signature += "=";
	oauth_signature += CreateSignature(key.Consumer_Sec, key.Accesstoken_Aec, *OAuth, method);
	OAuth->push_back(oauth_signature);

	return 0;
}
```

これでパラメータは完成です。
あとはこれをPOSTするだけです。

### その他のツール
Unicode対応や文字コード変換のための関数です。

```C++:CAIOS.hpp
static string UTF8_to_SJIS(string message) {
	int n;
	wchar_t ucs2[1000];
	char utf8[1000];
	n = MultiByteToWideChar(CP_UTF8, 0, message.c_str(), message.size(), ucs2, 1000);
	n = WideCharToMultiByte(CP_ACP, 0, ucs2, n, utf8, 1000, 0, 0);
	return std::string(utf8, n);
}

static string SJIS_to_UTF8(std::string const& message) {
	int n;
	wchar_t ucs2[1000];
	char utf8[1000];
	n = MultiByteToWideChar(CP_ACP, 0, message.c_str(), message.size(), ucs2, 1000);
	n = WideCharToMultiByte(CP_UTF8, 0, ucs2, n, utf8, 1000, 0, 0);
	return std::string(utf8, n);
}

// string から wstring 変換
static wstring StringToWString(const string& refSrc, unsigned int codePage = CodePageID::ANSI) {
	vector<wchar_t> buffer(MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, nullptr, 0));
	MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size());
	return wstring(buffer.begin(), buffer.end());
}

// wstring から string 変換
static string WStringToString(const wstring& refSrc, unsigned int codePage = CodePageID::OEM) {
	vector<char> buffer(WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, nullptr, 0, nullptr, nullptr));
	WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size(), nullptr, nullptr);
	return string(buffer.begin(), buffer.end());
}

static string EraseString(string str, string erase) {
	for (size_t c = str.find_first_of(erase); c != string::npos; c = c = str.find_first_of(erase)) {
		str.erase(c, 1);
	}
	return str;
}
```

文字列をbase64エンコードするための関数です。

```C++:CAIOS.hpp
static int encode_base64(char* bufin, int len, char* bufout) {
	static unsigned char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	unsigned char* pin = (unsigned char*)bufin;
	unsigned char* pout = (unsigned char*)bufout;

	for (int i = 0; i < len - 2; i += 3) {
		*pout++ = base64[pin[0] >> 2];
		*pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
		*pout++ = base64[0x3F & ((pin[1] << 2) | (pin[2] >> 6))];
		*pout++ = base64[0x3F & pin[2]];
		pin += 3;
	}
	if (len % 3 == 1) {
		*pout++ = base64[pin[0] >> 2];
		*pout++ = base64[0x3F & (pin[0] << 4)];
		*pout++ = '=';
		*pout++ = '=';
	}
	else if (len % 3 == 2) {
		*pout++ = base64[pin[0] >> 2];
		*pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
		*pout++ = base64[0x3F & (pin[1] << 2)];
		*pout++ = '=';
	}
	*pout = '\0';
	return pout - (unsigned char*)bufout;
}
```

### ツイートする
あとはmain関数からtweet関数を呼び出すだけです。
（tweet関数はここでは記載していないので<a href="https://github.com/DAICHI7241/CAIOS-for-Twitter">GitHub</a>より参照してください）

```C++:main.cpp
#include <iostream>
#include "CAIOS.hpp"

int main() {
	string message;
	cout << "Tweet > "; getline(cin, message);
	CAIOS::Twitter::tweet(message);
	return 0;
}
```

# おわりに
いかがでしたでしょうか。
コード全文は載せていませんので、<a href="https://github.com/DAICHI7241/CAIOS-for-Twitter">GitHub</a>から全文をクローンしてください。
まだまだ不備があるかもしれませんが、一応400行程度のコードにまとめることができました。

今回、hppファイルに処理を書くというお行儀の悪いことをしましたが、インクルード1つだけでTwitterAPIが使えるとなれば話は別です。

これでOAuthの殆どは完成していますから、残りはちょっとの変更を加えるだけでタイムラインの取得など、他の処理も実装できるでしょう。

C++でTwitterAPIを操作するライブラリやドキュメントはまだまだ少ないですが、この記事が少しでもみんなさんのお役に立てば光栄です。