import urllib
import hmac, sha
import time, random
# この関数の返り値を Authorization ヘッダーに設定する
def getOAuthHeader(url,method,params):
# 事前に確保した consumerKey, consumerSecret, accessToken, accessTokenSecret が設定されていること
oAuthParams = {
"oauth_consumer_key" : consumerKey,
"oauth_signature_method" : "HMAC-SHA1",
"oauth_timestamp" : str(int(time.time())),
"oauth_nonce" : str(random.getrandbits(64)),
"oauth_version" : "1.0",
"oauth_token" : accessToken
}
allParams=params.copy()
allParams.update(oAuthParams)
allParamList=[]
for key in sorted(allParams):
allParamList.append(key+"="+allParams[key])
msg=method+"&"+urllib.quote(url,"")+"&"+urllib.quote("&".join(allParamList),"~")
h=hmac.new(consumerSecret+"&"+accessTokenSecret,msg,sha)
oAuthParams["oauth_signature"]=h.digest().encode("base64").strip()
oAuthParamsList=[]
for key in oAuthParams:
oAuthParamsList.append(key+"=\""+urllib.quote(oAuthParams[key])+"\"")
return "OAuth "+(", ".join(oAuthParamsList))
import urllib, urllib2
import mimetypes
# 他の API でも使えるように汎用化した関数
def getAPIRequest(url,method,authentication,params={}):
multipart=False
newParams={}
for key in params:
value=params[key]
if value==None:
pass
elif isinstance(value,int):
newParams[key]=str(value)
elif isinstance(value,float):
newParams[key]=str(value)
elif isinstance(value,str):
newParams[key]=urllib.quote(value,"~")
elif isinstance(value,unicode):
newParams[key]=urllib.quote(value.encode("utf-8"),"~")
elif isinstance(value,tuple):
newParams[key]=value
multipart=True
params=newParams
url=url.encode("utf-8")
if method=="GET":
paramList=[]
for key in params:
paramList.append(key+"="+params[key])
request=urllib2.Request(url+"?"+"&".join(paramList))
else:
request=urllib2.Request(url)
if authentication:
# OAuth 対応のために必要なのはここだけ
if not multipart:
header=getOAuthHeader(url,method,params)
else:
header=getOAuthHeader(url,method,{})
request.add_header("Authorization",header)
if method=="POST":
if not multipart:
data=[]
for key in params:
data.append(key+"="+params[key])
request.add_data("&".join(data))
else:
BOUNDARY="---AntunBoundaryBoundaryBoundary"
data=""
for key in params:
data+="--"+BOUNDARY+"\r\n"
if not isinstance(params[key],tuple):
data+="Content-Disposition: form-data; name=\""+str(key)+"\""+"\r\n"
data+="\r\n"
data+=str(params[key])+"\r\n"
else:
fileType=mimetypes.guess_type(params[key][0])[0]
assert fileType!=None,"Could not determine file type"
data+="Content-Disposition: form-data; name=\""+str(key)+"\"; filename=\""+str(params[key][0])+"\""+"\r\n"
data+="Content-Type: "+str(fileType)+"\r\n"
data+="\r\n"
data+=str(params[key][1])+"\r\n"
data+="--"+BOUNDARY+"--"+"\r\n"
request.add_header("Content-Type","multipart/form-data; boundary="+BOUNDARY)
request.add_header("Content-Length",str(len(data)))
request.add_data(data)
else:
assert multipart==False
return request
# statuses/update 固有の処理はこんなにシンプル
def updateStatuses(tweet):
params={
"status" : tweet,
}
request=getAPIRequest("https://api.twitter.com/1.1/statuses/update.json","POST",True,params)
stream=urllib2.urlopen(request)
stream.read()
stream.close()
import json
def getUserStreams():
request=getAPIRequest("https://userstream.twitter.com/1.1/user.json","GET",True,{})
return urllib2.urlopen(request,timeout=90.0)
stream=self.twitter.getUserStreams()
response=u""
# 1行目のフォロワーリストを読み飛ばし (User Stream の場合だけ)
while True:
c=stream.read(1)
if c==u"\n":
break
response+=c
while True:
# readline() でも一応動きますが、仕様 (関数の実装) 上、正確なリアルタイムでレスポンスが得られなくなるので、read() で読んでます
response=u""
while True:
c=stream.read(1)
if c==u"\n":
break
response+=c
response=response.strip()
# 接続維持のために、時々空行が送られてくる
if response=="":
continue
response=json.loads(unicode(response),"utf-8")
print(json.dumps(response,indent=4,ensure_ascii=False))
| event キーの値 | イベント内容 | source | target | target_object | created_at | 備考 |
|---|---|---|---|---|---|---|
| follow | フォロー | フォローした人 | フォローされた人 | なし | フォローした時間 | |
| unfollow | アンフォロー | アンフォローした人 (=自分) | アンフォローされた人 | なし | アンフォローした時間 | 他人からされた時はこない |
| user_update | 自分のプロフィールを更新した | プロフィールを更新した人 (=自分) | プロフィールが更新された人 (=自分) | なし | プロフィールを更新した時間 | |
| user_delete | アカウントが消された | アカウントが消された人 | アカウントが消された人 | "client_application" の文字列 | アカウントが消された時間 | アカウント復活はこない 2014/4/9頃から導入 |
| user_suspend | アカウントが凍結された | アカウントが凍結された人 | アカウントが凍結された人 | "client_application" の文字列 | プロフィールを更新した時間 | アカウント凍結解除はこない 2014/4/9頃から導入 |
| block | ブロック | ブロックした人 | ブロックされた人 | なし | ブロックした時間 | |
| unblock | ブロック解除 | (未確認) | (未確認) | (未確認) | (未確認) | |
| mute | ミュート | ミュートした人 | ミュートされた人 | (未確認) | ミュートした時間 | 他人からされた時はこない 2014/7/23以降の近いうちに導入 |
| unmute | ミュート解除 | ミュート解除した人 | ミュート解除された人 | (未確認) | ミュート解除した時間 | 他人からされた時はこない 2014/7/23以降の近いうちに導入 |
| favorite | お気に入りに登録 | 登録した人 | 登録された人 | 登録されたツイート | 登録した時間 | |
| unfavorite | お気に入りから外す | 外した人 | 外された人 | 外されたツイート | 外した時間 | |
| favorited_retweet | 自分のリツイートがお気に入りに登録された | 登録した人 | 登録された人 (=自分) | 登録されたツイート | 登録された時間 | 公式には告知・ドキュメントなし 2014/11/11頃に導入 |
| retweeted_retweet | 自分のリツイートがリツイートされた | リツイートした人 | リツイートされた人 (=自分) | リツイートされたツイート | リツイートされた時間 | 2015/1/20から順次導入 |
| quoted_tweet | 自分のツイートが引用リツイートされた | 引用リツイートした人 | 引用リツイートされた人 (=自分) | 引用リツイート | 引用リツイートされた時間 | 2015/5/27の数週間後から導入 |
| list_created | 自分がリストを新規作成した | リストを作成した人 (=自分) | リストが作成された人 (=自分) | 作成されたリストの情報 | リストを作成した時間 | 非公開リストの情報はこない |
| list_destroyed | 自分がリストを削除した | リストを削除した人 (=自分) | リストが削除された人 (=自分) | 削除されたリストの情報 | リストを削除した時間 | 非公開リストの情報はこない |
| list_member_added | リストに追加 | リストに登録した人 | リストに登録された人 | 登録されたリストの情報 | リストに登録した時間 | 非公開リストの情報はこない |
| list_member_removed | リストから外す | リストから外した人 | リストから外された人 | 外された後のリストの情報 | リストから外した時間 | 非公開リストの情報はこない |
| list_updated | リストの情報が更新された | リストの情報を更新した人 | 更新されたリストの持ち主 (=source) | 更新されたリストの情報 | リストを更新した時間 | 非公開リストの情報はこない 他人のフォローしたリストの場合は未確認 |
| list_user_subscribed | リストをフォローした | リストをフォローした人 | リストをフォローされた人 | フォローされたリストの情報 | リストをフォローした時間 | |
| list_user_unsubscribed | リストをフォロー解除した | リストをフォロー解除した人 | リストをフォロー解除された人 | フォロー解除されたリストの情報 | リストをフォロー解除した時間 | |
| access_revoked | アプリ連携の許可を取り消した | 許可を取り消した人 | 許可を取り消されたアプリの作者 | 取り消されたアプリとトークンの情報 | 許可を取り消した時間 | 他人に取り消された場合は未確認 (たぶんこない) |
| access_unrevoked | アプリ連携の許可を取り消しを止めた | 許可の取り消しを止めた人 | 許可の取り消しを止められたアプリの作者 | 取り消しを止めたアプリとトークンの情報 | 許可の取り消しを止めた時間 | 他人に取り消された場合は未確認 (たぶんこない) |
| 識別方法 | 種類 | 備考 |
|---|---|---|
| retweeted_status キーがある | リツイート | タイムラインにリツイートを表示しない設定にしているユーザーのリツイートも流れてくる |
| direct_message キーがある | ダイレクトメッセージ | direct_message.sender.* から direct_message.recipient.* に direct_message.* のメッセージ |
| delete キーがある | ツイート削除 | delete.status.user_id が delete.status.id のツイートを消した リツイート元ツイートの削除、リツイートの取り消しもこれに含まれる (両者の区別は付かない) ダイレクトメッセージの削除はこないらしい |
| scrub_geo キーがある | 位置情報の削除 | scrub_geo.user_id が scrub_geo.up_to_status_id のツイートの位置情報を消した 設定画面の「全ての位置情報を削除する」で発生 |
| その他 | ツイート |
| API (https://api.twitter.com/1.1/) | 内容 | 認証 |
|---|---|---|
| POST account_activity/all/env_name/webhooks.json | Webhookを登録して、妥当性チェック | アプリを作成したアカウント |
| PUT account_activity/all/env_name/webhooks/webhook_id.json | 登録済みのWebhookの妥当性チェック | アプリを作成したアカウント |
| GET account_activity/all/webhooks.json | Webhookの登録状況を取得 | アプリを作成したアカウント or Application-only OAuth |
| GET account_activity/all/env_name/webhooks.json | Webhookの登録状況を取得 | アプリを作成したアカウント or Application-only OAuth |
| DELETE account_activity/all/env_name/webhooks/webhook_id.json | 登録済みのWebhookを解除 | アプリを作成したアカウント |
import json
import urlparse
import base64
import hashlib
import hmac
import SocketServer
import BaseHTTPServer
class Server(SocketServer.ThreadingMixIn,BaseHTTPServer.HTTPServer):
allow_reuse_address=True
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
protocol_version="HTTP/1.1"
def do_GET(self):
query=urlparse.urlparse(self.path).query
query=dict(map(lambda arg:(arg.split("=",1)+[None])[:2],query.split("&")))
if "crc_token" in query:
crcToken=query.get("crc_token")
# 事前に確保した consumerSecret が設定されていること
response={
"response_token" : "sha256="+
base64.b64encode(hmac.new(
consumerSecret.encode(),msg=crcToken.encode(),digestmod=hashlib.sha256
).digest()).decode(),
}
response=json.dumps(response)
self.send_response(200)
self.send_header("Content-type","application/json")
self.send_header("Content-Length",str(len(response)))
self.end_headers()
self.wfile.write(response)
self.wfile.flush()
server=Server(("localhost",8080),Handler)
server.serve_forever()
| API (https://api.twitter.com/1.1/) | 内容 | 認証 |
|---|---|---|
| POST account_activity/all/env_name/subscriptions.json | 自分を購読対象として登録 | 購読するアカウント |
| GET account_activity/all/env_name/subscriptions.json | 自分が購読対象として登録済みか確認 | 購読するアカウント |
| DELETE account_activity/all/env_name/subscriptions.json | 自分を購読対象から解除 | 購読するアカウント |
| API (https://api.twitter.com/1.1/) | 内容 | 認証 |
|---|---|---|
| GET account_activity/all/env_name/subscriptions/list.json | アプリに登録済みの購読対象の一覧を取得 | Application-only OAuth |
| GET account_activity/all/subscriptions/count.json | アプリに登録済みの購読対象数を取得 | Application-only OAuth |
| エラー番号 | HTTP ステータスコード | エラーメッセージ | 内容 |
|---|---|---|---|
| 34 | 404 | Sorry, that page does not exist | 指定された API が存在しない。 API のアドレスが間違っていないか確認しましょう。 account/update_profile_image など問題修正までに時間が掛かる場合に、 一時的にこの状態にされていることがあります。 |
| 44 | 400 | attachment_url parameter is invalid. | attachment_url には引用リツイートするツイートの URL しか渡せません。 |
| 50 | 404 | User not found. | ダイレクトメッセージ指定で存在しないユーザーを指定している。 ツイートの頭に特定のテキストがあるとコマンドとして認識 されますが、「D 存在しない人」や「M 存在しない人」を 使おうとした時にこのエラーになります。 ツイートの頭の文を変えましょう。 |
| 67 | Backend service is unavailable | Twitter のバックエンドシステムが落ちている ? | |
| 68 | 410 | The Twitter REST API v1 will soon stop functioning. Please migrate to API v1.1. https://dev.twitter.com/docs/api/1.1/overview. | 廃止された Twitter API 1.0 を呼んでいる。 Twitter API 1.1 に移行しましょう。 |
| 88 | 429 | Rate limit exceeded | API の利用回数制限に達した。 X-Rate-Limit-Reset ヘッダーで示された UTC 時間まで待ちましょう。 |
| 89 | 401 | Invalid or expired token | Bearer token が正しくない。 認証の指定が間違ってないか、 Bearer token が期限切れになってないか確認しましょう。 |
| 93 | 403 | This application is not allowed to access or delete your direct messages | 許可されていないダイレクトメッセージアクセス。 My applications の アクセスレベル設定でダイレクトメッセージが許可されているか確認しましょう。 |
| 99 | 403 | Unable to verify your credentials | OAuth2 Bearer Token の認証に失敗している。 Basic 認証の指定が間違っていないか見直しましょう。 |
| 130 | 503 | Over capacity | Twitter のシステムが限界に達している。 しばらく待ちましょう。 |
| 131 | 500 | Internal error | Twitter のシステムの原因不明の内部エラー。 しばらく待ってみて、治りそうにない場合は Twitter API のサポート掲示板 で指摘しましょう。 |
| 135 | 401 | Could not authenticate you | OAuth の認証に失敗している。 oauth_timestamp がずれていないか、Authorization ヘッダーの生成が間違っていないか見直しましょう。 |
| 150 | 403 | You cannot send messages to users who are not following you | ダイレクトメッセージ指定のツイートができない。 ツイートの頭に特定のテキストがあるとコマンドとして認識 されますが、「D フォローされてない人」や「M フォローされてない人」を 使おうとした時にこのエラーになります。 ツイートの頭の文を変えましょう。 |
| 185 | 403 | User is over daily status update limit | 1日のツイート数制限に達した。 しばらく待ちましょう。 88, 191 番のようには、復旧時間は分かりません。 公式サポート によると、2〜3時間待つべきです。 |
| 186 | 403 | Status is over 140 characters | ツイートが 140 文字を超えている。 ツイートを 140 文字以下に抑えましょう。 |
| 187 | 403 | Status is a duplicate | 重複ツイートしようとしている。 すでにツイート済みのテキストを送っているので、ツイートをやめましょう。 |
| 188 | 403 | Status contains malware. | ツイートに含まれるリンク先が不正サイトと判定されている。 |
| 189 | 403 | Error creating status | 原因不明。 statuses/update_with_media でまれに発生。 少し間を置いてもう一度 API を呼び出すと成功するようです。 |
| 190 | 403 | Status creation failed: ツイートが長すぎます. | ツイートが長すぎる。 ツイート中に URL が含まれており t.co に変換した結果、140 文字を超えるとおそらく 186 番ではなく、こちらが出るのだと思われます。 URL 込みのツイートを 140 文字以下に抑えましょう。 |
| 191 | 403 | User アカウント名 is over daily photo limit | 画像アップロード数の制限に達した。 X-MediaRateLimit-Reset ヘッダーで示された UTC 時間まで待ちましょう (ツイート数制限に引っかかった時は復旧時間は分からない)。 |
| 215 | 400 | Bad Authentication data | OAuth 認証に失敗した。 リクエストの Authorization ヘッダーを正しくしましょう。 |
| 220 | 403 | Your credentials do not allow access to this resource | 許可されていない API を呼んだ。 Bearer token では使えない API を呼んだか、 公式アプリにだけ許されている隠し API を呼んだので、使えません。 |
| 226 | 403 | This request looks like it might be automated. To protect our users from spam and other malicious activity, we can't complete this action right now. Please try again later. | SPAM 判定された。 おそらくほとんど同じ内容を非常に短時間に連続した時に。 メッセージにあるように、しばらく後にツイートしましょう。 |
| 324 | 400 | User id ユーザーID doesn't own media id メディアID | 他人がアップロードしたメディアを添付しようとしている。 |
| 326 | 403 | To protect our users from spam and other malicious activity, this account is temporarily locked. Please log in to https://twitter.com to unlock your account. | アカウントハック判定された。 アカウントのメールアドレスにパスワードリセットのお知らせが 届いているはずなので、しかるべき対応をしましょう。 |
| 420 | Exceeded connection limit for user | Streaming API の接続が多すぎる。適度に抑えましょう。 このエラーだけレスポンスのメッセージが JSON 形式になっていません。 |
戻る