10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonの標準ライブラリのみでツイートしてみた

Posted at

#まとめ
Requestsを使わずurllib等のpython2.7の標準ライブラリのみでOAuth1.0の署名付きリクエストを送信する関数を書きました。この関数でTwitterのRestAPIを使いツイートしました。

#なぜ実装した?
RequestsやTweepyなどは依存関係が複雑でGoogle App Engineに搭載できなかったから

#にゃーんと呟く

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2
import time, random
import hmac, hashlib, base64
from urllib import quote, urlencode

#リクエストを送信する関数
#   method: "POST" or "GET"
#   url: e.g. "https://www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html"
#   param: e.g. {"status":"にゃーん"}
#   header: e.g. {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"}
#
#   return: {"code":200,"body":"something"}
def request(method, url, param, header):
	method = method.upper()
	param = urlencode(param)
	if method == "POST":
		pass
	if method == "GET":
		url += "?" + param
		param = None
	try:
		req = urllib2.Request(url, param, header)
		handler = urllib2.urlopen(req)
		return {"body": handler.read(), "code": handler.getcode()}
	except urllib2.HTTPError as e:
		print(e.fp.read())

#OAuth1.0の署名付きリクエストを送信する関数
#   consumer_key: Consumer Key (API Key)
#   consumer_secret: Consumer Secret (API Secret)
#   access_token: Access Token
#   access_token_secret: Access Token Secret
#   method: e.g. "POST"
#   url: e.g. https://api.twitter.com/1.1/statuses/update.json
#   param: {"status":にゃーん}
#
#   return {"code":200,"body":"something"}
def oauth10(consumer_key, consumer_secret, access_token, access_token_secret, method, url, param):
	for k,v in param.items():
		if isinstance(v,unicode):
			v=v.encode("utf-8")
		param[k]=str(v)
	baseparam = {
		"oauth_token": access_token,
		"oauth_consumer_key": consumer_key,
		"oauth_signature_method": "HMAC-SHA1",
		"oauth_timestamp": str(int(time.time())),
		"oauth_nonce": str(random.getrandbits(64)),
		"oauth_version": "1.0"
	}
	signature = dict(baseparam)
	signature.update(param)
	signature = '&'.join('{0}={1}'.format(quote(key, ''), quote(signature[key], '~')) for key in sorted(signature))
	signature = ("{0}&{1}".format(consumer_secret, access_token_secret), '{0}&{1}&{2}'.format(method.upper(), quote(url, ''), quote(signature, '')))
	signature = base64.b64encode(hmac.new(signature[0], signature[1], hashlib.sha1).digest())
	header = dict(baseparam)
	header.update({"oauth_signature": signature})
	header = ",".join("{0}={1}".format(quote(k, ''), quote(header[k], '~')) for k in sorted(header))
	header = {"Authorization": 'OAuth {0}'.format(header)}
	return request(method, url, param, header)

consumer_key="ひみつ"
consumer_sec="ひみつ"
access_key="ひみつ"
access_sec="ひみつ"

r=oauth10(consumer_key,consumer_sec,access_key,access_sec,"POST","https://api.twitter.com/1.1/statuses/update.json",{"status":"にゃーん"})

print(r["code"])#200
print(r["body"])#{"created_at":"Sat May 19 06:42:23 +0000 2018","id":9977291606~~

#大まかな手順

0.用意するもの

認証に必要な情報 説明
consumer_key Consumer Key (API Key)
consumer_secret Consumer Secret (API Secret)
access_token Access Token
access_token_secret Access Token Secret
method RESTAPIに指定されている送信方法。今回はPOST
url APIのURL "https://api.twitter.com/1.1/statuses/update.json"
param APIのパラメータ。今回はつぶやく内容を指定。{"status":"にゃーん"}

1.基本的なパラメータを集める

認証に必要な情報 説明
oauth_token アプリケーションの利用者を示す文字列
oauth_consumer_key アプリケーションを示す文字列
oauth_signature_method 署名に用いるハッシュ関数を示す文字列
oauth_timestamp UNIX時刻
oauth_nonce リクエストごとに固有の文字列。なんでもいい。
oauth_version OAuth1.0を使うので"1.0"
    baseparam = {
        "oauth_token": access_token,
        "oauth_consumer_key": consumer_key,
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": str(int(time.time())),
        "oauth_nonce": str(random.getrandbits(64)),
        "oauth_version": "1.0"
    }

2.APIのパラメーターを加える

signature = dict(baseparam)
signature.update(param)

3.合わさったパラメータを文字列に変える

全てのキーと値はパーセントエンコードする。
キーをABC順に並べる
a=nurupo&b=ga みたいに連結する

signature = '&'.join('{0}={1}'.format(quote(key, ''), quote(signature[key], '~')) for key in sorted(signature))

4.ハッシュ関数とbase64で変換した署名を作成する

method&url&さっきの連結した文字列
というメッセージを
consumer_secret&access_token_secretで署名する

signature = ("{0}&{1}".format(consumer_secret, access_token_secret), '{0}&{1}&{2}'.format(method.upper(), quote(url, ''), quote(signature, '')))
signature = base64.b64encode(hmac.new(signature[0], signature[1], hashlib.sha1).digest())

5. 1.のパラメータと署名を合わせてヘッダに追加

    header = dict(baseparam)
    header.update({"oauth_signature": signature})
    header = ",".join("{0}={1}".format(quote(k, ''), quote(header[k], '~')) for k in sorted(header))
    header = {"Authorization": 'OAuth {0}'.format(header)}

6. 送信

#リクエストを送信する関数
#   method: "POST" or "GET"
#   url: e.g. "https://www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html"
#   param: e.g. {"status":"にゃーん"}
#   header: e.g. {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"}
#
#   return: {"code":200,"body":"something"}
def request(method, url, param, header):
    method = method.upper()
    param = urlencode(param)
    if method == "POST":
        pass
    if method == "GET":
        url += "?" + param
        param = None
    try:
        req = urllib2.Request(url, param, header)
        handler = urllib2.urlopen(req)
        return {"body": handler.read(), "code": handler.getcode()}
    except urllib2.HTTPError as e:
        print(e.fp.read())
request(method, url, param, header)

APIのサーバー側ではmethod,URL,Authorization,paramから同様にハッシュを計算し一致を確かめるのだろう

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?