Hatena::ブログ(Diary)

mole-studio

 

2013-08-16

DelphiからTwitterに投稿する

使用ライブラリ

procedure tweet(text : string);
  function Encode(a:String):string;
  var
    i: Integer;
    s: string;
  begin
    s := string(HTTPEncode(UTF8Encode(a)));
    Result:='';
    for i := 1 to length(s) do
    begin
      if s[i]='+' then
        Result:=Result + '%20'
      else
        Result:=Result + s[i]
    end;
  end;
var
  HMAC : TOAuthSignatureMethod_HMAC_SHA1;
  req : TOAuthRequest;
  consumer: TOAuthConsumer;
  token: TOAuthToken;

  idHTTP : TIdHTTP;
  source: TStringList;
  // header_param_str : string;
  i: Integer;
const
  update_url = 'http://api.twitter.com/1.1/statuses/update.json';
begin
  consumer:=TOAuthConsumer.Create(consumer_key, consumer_secret);
  HMAC := TOAuthSignatureMethod_HMAC_SHA1.Create;
  token := TOAuthToken.Create(oauth_token, oauth_token_secret);
  req := TOAuthRequest.Create(update_url);

  req := req.FromConsumerAndToken(consumer, token, update_url);
  // 投稿文を含めてSignする
  // このとき半角スペースは'+'ではなく'%20'にクォートしなければいけないが、
  // HTTPEncodeでは'+'にしかクォートされないので自分で'+''%20'に置換する
  req.Parameters.Add('status='+Encode(text));

  // 使っているOAuthライブラリがGETのみの対応だったので
  // oauth_methodをグローバル変数に書き換えて GET から POST に変更
  // OAuth.pas の526行目参照
  oauth_method := 'POST';
  // req.Sign_Request(HMAC, consumer, token);
  // OAuth.pasをよく読むと Sign_Request2 ならばヘッダを param1=value,param2=value 
  // というように , = 区切りでパラメーターを結合をしてくれることが判明
  req.Sign_Request2(HMAC, consumer, token);

  // ヘッダ文にはstatusは含めないので削除する
  req.Parameters.Delete(req.Parameters.Count-1);

  // 連結
  // header_param_str := req.Parameters[0];
  // for i := 1 to req.Parameters.Count - 1 do
  //   header_param_str := header_param_str + ',' + req.Parameters[i];
  // Sign_Request2で結合したものが得られるので不要


  idHTTP := TIdHTTP.Create(nil);
  // idHTTPのエンコード周りのバグ。この設定しにしないと半角のプラス" + "とかが投稿できない。
  idHTTP.HTTPOptions := idHTTP.HTTPOptions - [hoForceEncodeParams];
  // あとはヘッダにOAuthパラメーターを追加してPOSTするだけ
  // idHTTP.Request.CustomHeaders.AddValue('Authorization', 'OAuth ' + header_param_str);
  // , = 区切りで結合されたパラメーターが GetString で取得できる
  // idHTTP.Request.CustomHeaders.AddValue('Authorization', 'OAuth ' + req.GetString);

  source := TStringList.Create;
  source.Add('status='+Encode(text));

  idHTTP.Post(update_url, source);
  source.Free;

  consumer.Free;
  HMAC.Free;
  token.Free;
  req.Free;
  idHTTP.Free;
end;
  • 2013 9/1ちょっと修正

2013-07-18

TwitterでOAuth認証を通すときの注意点まとめ(随時更新)

2013-07-17

Qt5.1でTwitterのOAuthで投稿

    QString consumer_key = "cs";
    QString consumer_secret = "cs";
    QString oauth_token = "ot";
    QString oauth_token_secret = "ots";

    QString update_url = "http://api.twitter.com/1.1/statuses/update.json";

    QString tweet = this->ui->textEdit->toPlainText();

    QMap<QString, QString> params;
    params["oauth_consumer_key"] = consumer_key;
    params["oauth_nonce"] = "45450721";
    params["oauth_signature_method"] = "HMAC-SHA1";
    params["oauth_timestamp"] = QString::number(time(NULL));
    params["oauth_token"] = oauth_token;
    params["oauth_version"] = "1.0";
    params["status"] = QString::fromUtf8(tweet.toUtf8().toPercentEncoding());


    QString params_str;

    QMapIterator<QString, QString> i(params);
    while(i.hasNext()){
        i.next();
        params_str += QString("%1=%2&").arg(i.key()).arg(i.value());
    }
    params_str.resize(params_str.size() -1 );


    QByteArray message = "POST&" + update_url.toUtf8().toPercentEncoding() + "&" + params_str.toUtf8().toPercentEncoding();

    QByteArray key = consumer_secret.toUtf8() + "&" + oauth_token_secret.toUtf8();

    QMessageAuthenticationCode code(QCryptographicHash::Sha1);
    code.setKey(key);
    code.addData(message);
    QByteArray signature = code.result().toBase64();

    params.remove("status");
    params["oauth_signature"] = QString::fromUtf8(signature.toPercentEncoding());

    QString header_params_str = "";
    QMapIterator<QString, QString> i2(params);
    while(i2.hasNext()){
        i2.next();
        header_params_str += i2.key() + "=" + i2.value() + ",";
    }
    header_params_str.resize(header_params_str.size() -1 );


    QUrl url(update_url);
    QNetworkRequest request(url);

    request.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
    request.setRawHeader( "Authorization", "OAuth " + header_params_str.toUtf8() );

    QUrlQuery query;
    query.addQueryItem("status", tweet);

    manager->post(request, query.query(QUrl::FullyEncoded).toUtf8());

かなりすっきり

QtでTwitterのTL取得

メモ

void MainWindow::on_pushButton_2_clicked()
{
    QString consumer_key="ck";
    QString consumer_secret="cs";
    QString oauth_token="ot";
    QString oauth_token_secret="ots";

    QString update_url="http://api.twitter.com/1.1/statuses/home_timeline.json";

    QMap<QString,QString>params;
    params["oauth_consumer_key"]=consumer_key;
    params["oauth_nonce"]="45450721";
    params["oauth_signature_method"]="HMAC-SHA1";
    params["oauth_timestamp"]=QString::number(time(NULL));
    params["oauth_version"]="1.0";
    // params["status"]=QString::fromUtf8(tweet.toUtf8().toPercentEncoding());
    params["oauth_token"]=oauth_token;

    QString params_str;
    QList<QString> keys = params.keys();
    for(int i=0; i < keys.count(); i++){
        params_str+=QString("%1=%2&").arg(keys[i]).arg(params[keys[i]]);
    }

    params_str.resize(params_str.size()-1);

    QByteArray message = "GET&"+update_url.toUtf8().toPercentEncoding()+"&"+params_str.toUtf8().toPercentEncoding();
    QByteArray key = consumer_secret.toUtf8()+"&"+oauth_token_secret.toUtf8();

    QMessageAuthenticationCode code(QCryptographicHash::Sha1);
    code.setKey(key);
    code.addData(message);
    QByteArray signature = code.result().toBase64();

    params["oauth_signature"] = QString::fromUtf8(signature.toPercentEncoding());

    QUrl url(update_url);
    QUrlQuery query;
    for(int i=0;i<params.keys().count();i++){
        query.addQueryItem(params.keys()[i], params[params.keys()[i]]);
    }
    url.setQuery(query);

    QNetworkRequest request(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
    manager->get(request);
}

「Qtのconnectとdisconnectの速度について」の結果として

 lambda式を使いました。QNetworkAccessManagerを保持するクラスに、メンバーにstd::function<void(QNetworkReply*)>なlambdaを持たせ、finishedを割り当てたSLOTでそいつを呼び出すようにして、post/get枚にlambda式をパラメタに取ってメンバーのlambda式に代入する、と。


 これでconnect、disconnectを毎回行うこと無く通信の手段ごとにcallbackを取ることができる。


 でもfinishedのコールバックにconnectならメソッドも使えるのに対して、このやり方ではlambda式しか使えないのが難点。overloadしてもいいけど美しくないわ。


 難しいなあ。

2013-07-16

Qtのconnectとdisconnectの速度について

 これまではサークルの活動でHtml5+CSS3+JSをもりもりざくざく、手元の細々したことをPythonでやるみたいな、生活をしていましたが、さいきんQtで遊んでいます。

 そのおかげかかすでに期末でしょっぱな再試にひっかかり、おそらく今日の試験も引っかかったでしょう。サークルないでは留年に最も近い男などとひどいいわれようです。まあ揺るぎない事実ではあるんですけど。


・・・


 まあとにかくQtですよ。

 Twitterクライアント作りたいんですよね。それもコンシューマーキーを投稿は自分のやつTL読み込みは公式の割れキーとか好きに差し替えられて、大学のWiFiには勝手にログインしてくれて、ふぁぼ爆リプ爆その他がスクリプトで駆動したりするやつ。

 いろいろ考えてはいるけど、そもそもC++も若干あやしい上に、まだQtの使い方を学んでいる段階。そんで、QObject派生クラスを作るわけだが、Qtの特徴とも言えるSIGNAL/SLOTのconnectとdisconnectちゃんのパフォーマンスがどれほどのものか、とても気になる。

 ネットワークなので接続は全部非同期なんだけど、投稿だったりTL取得だったりいろいろな通信を全部、ある程度GETとPOSTの2つの関数にまとめておきたい。そしてQNetworkAccessManagerのfinishedに、通信の種類ごとにSLOTを割り当てたいわけ。QNetworkAccessManagerは通信用のクラスに一個だけ持たせて、通信の種類ごとに通信の直前に古いSLOTをdisconnectして(もしくは接続完了後にdisconnectして)、これからする通信用のSLOTをconnectする、ってのがパッと思いつく実装。


 要はイベントの差し替えをしたくて、つまり、「connect/disconnectにヘンテコなマクロとか使うけど、頻繁に呼び出すのはパフォーマンス的にどうなのよ!!」って話。


 内部的にはただの代入で軽リスク???じゃーガンガンconnect/disconnectいこうぜ!

 内部ではものっそいごちゃごちゃやってて頻繁なconnect/disconnectは非推奨で、別の方法が推奨されてる???じゃー誰か教えてくれ!



 というはなし。

2013-06-25

C++/QtでOAuthを使ってTwitterに投稿する

 C++OAuthというゲロ面倒くさそうなものを書いたので、あとから同じような人の助けになるべくエントリーを残す。

 当方C++Qtも、超初心者。


 Qtは4.8.4。GUIアプリ。テキストエディットとボタン置いた。

 proファイルには、

LIBS += -lcrypto

を追加しておく

細かい部分は察して。

#include <openssl/sha.h>
#include <openssl/hmac.h>
 ・
 ・
void MainWindow::on_pushButton_clicked()
{
    // この場ではこの4つの文字列の解説はしない
    QString consumer_key = "ck"
    QString consumer_secret = "cs";
    QString oauth_token = "ot";
    QString oauth_token_secret = "ots";

    // ホストは api.twitter.com かつ、 1.1 を指定しないといけない
    // xmlを使いたいが廃止された模様
    QString update_url = "http://api.twitter.com/1.1/statuses/update.json";

    // 投稿文
    QString tweet = this->ui->textEdit->toPlainText();


    QMap<QString, QString> params;
    params["oauth_consumer_key"] = consumer_key;
    // 適当な文字列と言われているが、ガチで適当でいい
    params["oauth_nonce"] = "45450721";
    params["oauth_signature_method"] = "HMAC-SHA1";
    params["oauth_timestamp"] = QString::number(time(NULL));
    params["oauth_token"] = oauth_token;
    params["oauth_version"] = "1.0";
    // パラメーターは全てURIエンコード必須だが、URIエンコードして文字が
    // 変わる可能性があるのはstatusのみ。なのでこいつだけエンコードすればよい
    params["status"] = QString::fromUtf8(tweet.toUtf8().toPercentEncoding());


    QString params_str;

    // "&"で結合。Qtってjoinないのか?
    QMapIterator<QString, QString> i(params);
    while(i.hasNext()){
        i.next();
        params_str += QString("%1=%2&").arg(i.key()).arg(i.value());
    }
    params_str.resize(params_str.size() -1 );


    // sha1する本文
    QByteArray message = "POST&" + update_url.toUtf8().toPercentEncoding() + "&" + params_str.toUtf8().toPercentEncoding();

    // sha1するキー
    QByteArray key = consumer_secret.toUtf8() + "&" + oauth_token_secret.toUtf8();

    // 直接opensslから呼び出す
    // この場合はstd::stringを経由することになる
    unsigned char res[SHA_DIGEST_LENGTH + 1];
    unsigned int reslen;
    HMAC(EVP_sha1(), (unsigned char*)key.data(), key.length(), (unsigned char*)message.data(), message.length(), res, &reslen);
    std::string result;
    result = (char*)res;
    result.erase(reslen);

    // std::stringからはfromStdStringよりもfromRawDataでQByteArrayにしたほうが工数は減る
    // こいつをBase64でエンコードしシグニチャを作成
    QByteArray signature = QByteArray::fromRawData(result.c_str(), result.length()).toBase64();


    // statusはpostのパラメータになるので削除
    params.remove("status");
    // 先ほどのstatus同様、signature以外はURIエンコードしても文字の変化はないので、
    // signatureのみエンコードしておく
    params["oauth_signature"] = QString::fromUtf8(signature.toPercentEncoding());

    QString header_params_str = "";
    QMapIterator<QString, QString> i2(params);
    while(i2.hasNext()){
        i2.next();
        // OAuthのパラメータはカンマ区切り
        header_params_str += i2.key() + "=" + i2.value() + ",";
    }
    header_params_str.resize(header_params_str.size() -1 );


    // アクセスマネージャ作成
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);

    // urlセット
    QUrl url(update_url);
    QNetworkRequest request(url);

    // これは必ずヘッダに付与しないといけない
    request.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
    // 肝心なOAuthの認証部分
    request.setRawHeader( tr("Authorization").toUtf8(), (tr("OAuth ") + header_params_str).toUtf8() );

    // 完了のシグナルをセット
    QObject::connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));

    // postを飛ばす。エンコードを忘れない
    manager->post(request, tr("status=").toUtf8() + tweet.toUtf8().toPercentEncoding());
}

sha1でHMAC作る部分だけど、Qt4.8のQCryptographicHashではキーの設定ができない。

Qt5.1ではQMessageAuthenticationCodeというのがあって、そっちではキーが設定できるので、もっとエレガントになる。


参照にしたサイト

やるおのサイト、とても参考にさせてもらってます。



Connection: close