PHPでOAuth1認証をして、はてブのAPIを叩いてブクマする

スポンサーリンク
プログラミング

経緯

手動ブクマだるいなと思って探してたらメール投げてブクマする奴がなくなっていた
【Hatena自動ブクマ】Hatena Bookmark AutoPostが使えなくなっていた話

なので、OAuthを用いたAPIを叩こうと思い(これならまだ長生きしそう)だし、要件やら勉強がてらやらでPHPを使い、素でOAuth1を書きました(やたらと細かくてつまづきまくった)

scopeとかはよしなにすると良いと思う()

以下のコードが主

<?php

namespace hatebu;

class Hatebu
{
    private $oauth_consumer_key;
    private $oauth_consumer_secret_key;
    private $request_token_url = "https://www.hatena.com/oauth/initiate";
    private $access_token_url = 'https://www.hatena.com/oauth/token';
    private $bookmark_api_url = 'http://api.b.hatena.ne.jp/1/my/bookmark';
    private $oauth_signature_method = 'HMAC-SHA1';
    private $oauth_version = '1.0';

    /**
     * Hatebu constructor.
     * @param $consumer_key
     * @param $oauth_consumer_secret_key
     */
    public function __construct($consumer_key, $oauth_consumer_secret_key)
    {
        $this->oauth_consumer_key = $consumer_key;
        $this->oauth_consumer_secret_key = $oauth_consumer_secret_key;
    }

    /**
     * @return array
     */
    public function get_request_token()
    {
        $method = 'POST';
        $url = $this->request_token_url;
        $authorization = array(
            'oauth_callback' => "oob",
            'oauth_consumer_key' => $this->oauth_consumer_key,
            'oauth_nonce' => md5(uniqid(rand(), true)),
            'oauth_signature_method' => $this->oauth_signature_method,
            'oauth_timestamp' => time(),
            'oauth_version' => $this->oauth_version,
        );

        $body = array(
            'scope' => 'read_public,write_public,read_private,write_private'
        );
        $authorization['oauth_signature'] = $this->create_signature(
            $authorization,
            $method,
            NULL,
            $url,
            $body
        );

        $response = $this->send_request($url, $authorization, $method, $body);
        $responses = explode("&", $response);
        return array(
            'oauth_token' => rawurldecode(explode("=", $responses[0])[1]),
            'oauth_token_secret' => rawurldecode(explode("=", $responses[1])[1])
        );
    }

    /**
     * @param $oauth_request_token
     * @param $oauth_request_token_secret
     * @param $oauth_verifier
     * @return array
     */
    public function get_access_token($oauth_request_token, $oauth_request_token_secret, $oauth_verifier)
    {
        $method = 'POST';
        $url = $this->access_token_url;
        $authorization = array(
            'oauth_consumer_key' => $this->oauth_consumer_key,
            'oauth_nonce' => md5(uniqid(rand(), true)),
            'oauth_signature_method' => $this->oauth_signature_method,
            'oauth_timestamp' => time(),
            'oauth_token' => $oauth_request_token,
            'oauth_verifier' => $oauth_verifier,
            'oauth_version' => $this->oauth_version
        );
        $authorization['oauth_signature'] = $this->create_signature(
            $authorization,
            $method,
            $oauth_request_token_secret,
            $url
        );
        $response = $this->send_request($url, $authorization, $method);
        $responses = explode("&", $response);
        return array(
            'oauth_token' => rawurldecode(explode("=", $responses[0])[1]),
            'oauth_token_secret' => rawurldecode(explode("=", $responses[1])[1]),
            'url_name' => rawurldecode(explode("=", $responses[2])[1]),
            'display_name' => rawurldecode(explode("=", $responses[3])[1])
        );
    }

    public function post_bookmark(String $bookmark_url, String $comment, String $oauth_access_token, $oauth_access_token_secret)
    {
        $url = $this->bookmark_api_url;
        $method = 'POST';
        $authorization = array(
            'oauth_consumer_key' => $this->oauth_consumer_key,
            'oauth_nonce' => md5(uniqid(rand(), true)),
            'oauth_signature_method' => $this->oauth_signature_method,
            'oauth_timestamp' => time(),
            'oauth_version' => $this->oauth_version
        );

        $body = [
            'url' => $bookmark_url,
            'comment' => $comment,
        ];
        // ここでsignatureにbodyを含めると、認証が通らなくなる
        $authorization['oauth_signature'] = $this->create_signature(
            $authorization,
            $method,
            $oauth_access_token_secret,
            $url
        );

        return json_decode(
            $this->send_request($url, $authorization, $method, $body),
            true
        );
    }

    /**
     * @param $url
     * @param $authorization
     * @param $method
     * @param $body
     * @param $additional_headers
     * @return bool|string
     */
    private function send_request($url, $authorization, $method, $body = NULL, $additional_headers = NULL)
    {
        $oauthHeader = 'OAuth ';
        foreach ($authorization as $key => $val) {
            $oauthHeader .= $key . '="' . rawurlencode($val) . '",';
        }
        $oauthHeader = substr($oauthHeader, 0, -1);
        $header = array(
            'Authorization:' . $oauthHeader,
            'User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0',
            'Expect:',
        );
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
        curl_setopt($curl, CURLOPT_TIMEOUT, 30);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        if (isset($additional_headers)) {
            $header = array_merge($header, $additional_headers);
        }

        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        curl_setopt($curl, CURLINFO_HEADER_OUT, true);
        if (isset($body)) {
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
        }

        $res = curl_exec($curl);
        $res_info = curl_getinfo($curl);
        if ($res_info['http_code'] != 200 && $res_info['http_code'] != 201) {
            var_dump($res_info);
            var_dump($res);
            exit(1);
        }

        curl_close($curl);
        return $res;
    }

    /**
     * @param $authorization
     * @param $method
     * @param $token_secret
     * @param $url
     * @param null $body
     * @return string
     */
    private function create_signature($authorization, $method, $token_secret, $url, $body = NULL)
    {
        if (isset($body)) {
            $parameter_array = array_merge($authorization, $body);
        } else {
            $parameter_array = $authorization;
        }

        ksort($parameter_array);
        $signatureBaseString = '';
        foreach ($parameter_array as $key => $val) {
            $signatureBaseString .= $key . '=' . rawurlencode($val) . '&';
        }
        $signatureBaseString = substr($signatureBaseString, 0, -1);
        $signatureBaseString = sprintf("%s&%s&%s", $method, rawurlencode($url), rawurlencode($signatureBaseString));
        if (isset($token_secret)) {
            $signingKey = sprintf("%s&%s", rawurlencode($this->oauth_consumer_secret_key), rawurlencode($token_secret));
        } else {
            $signingKey = rawurlencode($this->oauth_consumer_secret_key) . '&';
        }

        return base64_encode(hash_hmac('sha1', $signatureBaseString, $signingKey, true));
    }
}

叩く

こんな感じに書いて、デバックめっちゃしてた

<?php
require_once('hatebu.php');
$consumer_key = 'CONSUMER_KEY';
$consumer_secrete_key = 'SECRET KEY';
var_dump($consumer_key, $consumer_secrete_key);

var_dump('Create Request Token');
$hatebu = new hatebu\Hatebu($consumer_key, $consumer_secrete_key);
$request_tokens = $hatebu->get_request_token();
var_dump($request_tokens);

var_dump('Create Access Token');
print_r("https://www.hatena.ne.jp/oauth/authorize?oauth_token=" . $request_tokens['oauth_token'] . "\n");
print_r('input verify token: ');
$verify_token = trim(fgets(STDIN));
$access_tokens = $hatebu->get_access_token(
    $request_tokens['oauth_token'],
    $request_tokens['oauth_token_secret'],
    $verify_token
);
var_dump($access_tokens);

var_dump("POST_BOOKMARK_________________");

$result = $hatebu->post_bookmark(
   'https://www.nozograph.com/',
    'API叩いてブクマしてみた',
    $access_tokens['oauth_token'],
    $access_tokens['oauth_token_secret']
);

ほんで、こんな感じにできる
スクリーンショット 2019-04-12 21.11.13.png

以上

所感

世に溢れてる、OAuth1ライブラリの中身を知れた
OAuth1といってもサービスによって微妙に仕様が違かったりするっぽく(歴史的な背景とかあるらしい)ので汎用的にやるの難しそうだなあと言った気持ち

OAuth1自体は、
Consumer key を取得して OAuth 開発をはじめよう
を見ればなんとなくわかるけど、HeaderのContent-Typeやらrequeset tokenを取得するとき だけ Sigunaturebodyを含むみたいなハマりどころがあります。
あと、urlencode周りで詰まりまくった

callbackを設定した方が良さそうなのでそのうちやりたい。

何か間違ってたり、色々ありましたらコメント等ください。

composerとか使って、ライブラリ化?すると良さそうですね(そのうちやります)

Special Thanks

コメント

タイトルとURLをコピーしました