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 形式になっていません。 |
戻る