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

#まとめ
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~~
```
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">にゃーん</p>&mdash; チャップリン (@misumi3104) <a href="https://twitter.com/misumi3104/status/997669729434198016?ref_src=twsrc%5Etfw">2018年5月19日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#大まかな手順
### 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から同様にハッシュを計算し一致を確かめるのだろう