├── requirements.txt ├── twispy ├── __init__.py ├── handler.py ├── request.py ├── utils.py └── api.json ├── setup.py ├── LICENSE ├── .gitignore └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /twispy/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from twispy.request import Request 4 | from twispy.handler import API 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | with open('README.md') as f: 7 | readme = f.read() 8 | with open('LICENSE') as f: 9 | li = f.read() 10 | 11 | setup( 12 | name='Twispy', 13 | description='A Lightweight & Full Powered Twitter API Wrapper.', 14 | long_description=readme, 15 | author='NephyProject', 16 | url='https://github.com/NephyProject/Twispy', 17 | license=li, 18 | packages=find_packages(exclude=('tests',)), 19 | package_data={"twispy": ["api.json"]}, 20 | install_requires=["requests"] 21 | ) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2017 Nephy Project Team [https://github.com/NephyProject/] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /twispy/handler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import os 4 | from collections import OrderedDict 5 | from typing import List, Dict, Union 6 | 7 | from twispy.request import Request 8 | 9 | with open(os.path.abspath(os.path.dirname(__file__)) + "/api.json", "rb") as f: 10 | api_dict = json.loads(f.read().decode()) 11 | 12 | class API: 13 | __slots__ = list(api_dict.keys()) + ["_request", "_do", "streaming"] 14 | 15 | def __init__(self, ck, cs, at, ats, uuid=None, deviceId=None): 16 | self._request = Request(ck, cs, at, ats, uuid, deviceId) 17 | self._do = self._request.do 18 | self.streaming = self._request.streaming 19 | 20 | def __getattr__(self, name): 21 | def func(**kwargs) -> Union[List, Dict]: 22 | if name in api_dict: 23 | api = api_dict[name] 24 | 25 | data = OrderedDict() 26 | for array in api["data"]: 27 | key, value = array[0:2] 28 | data[key] = value 29 | 30 | if key in kwargs: 31 | data[key] = str(kwargs[key]) 32 | 33 | if data[key] == False: 34 | # optional argument 35 | del data[key] 36 | continue 37 | if data[key] == None: 38 | # necessary argument 39 | raise Exception("{} must have non-null parameter.".format(key)) 40 | 41 | result = self._do(api["method"], api["url"], data, headerType=api["headerType"], authorizationType=api["authorizationType"]) 42 | return result 43 | 44 | raise AttributeError("No such a method found.") 45 | 46 | return func 47 | 48 | @staticmethod 49 | def default_callback(stream) -> None: 50 | print(json.dumps(stream, indent=4)) 51 | 52 | def create_poll(self, text: str, choices: List[str], minutes=1440) -> Dict: 53 | """ 54 | Create Twitter poll tweet. CK/CS must be Twitter Official Keys. 55 | :param text: Tweet content 56 | :param choices: List[str]: 57 | :param minutes: how long this poll lasts (minute) 58 | :return: status object 59 | """ 60 | 61 | if len(choices) not in [2, 3, 4]: 62 | raise Exception("choices must has 2 to 4") 63 | 64 | params = OrderedDict() 65 | for i in range(len(choices)): 66 | params["twitter:string:choice{}_label".format(i + 1)] = choices[i] 67 | params["twitter:api:api:endpoint"] = "1" 68 | params["twitter:card"] = "poll{}choice_text_only".format(len(choices)) 69 | params["twitter:long:duration_minutes"] = minutes 70 | 71 | r = self.cards_create( 72 | card_data=json.dumps(params) 73 | ) 74 | 75 | if "card_uri" not in r: 76 | raise Exception("API returned an error.\nAPI response: {}\n".format(repr(r))) 77 | 78 | return self.statuses_update( 79 | status=text, 80 | card_uri=r["card_uri"] 81 | ) 82 | -------------------------------------------------------------------------------- /twispy/request.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import threading 3 | 4 | import requests 5 | 6 | from twispy.utils import * 7 | 8 | 9 | class Request: 10 | def __init__(self, ck, cs, at, ats, uuid=None, deviceId=None): 11 | self.ck = ck 12 | self.cs = cs 13 | self.at = at 14 | self.ats = ats 15 | self.uuid = uuid 16 | self.deviceId = deviceId 17 | 18 | def do(self, method, url, data=None, headerType=0, authorizationType=0, bearerToken=None): 19 | method = method.upper() 20 | if not data: 21 | data = {} 22 | 23 | header = makeHeader(method, url, self.uuid, self.deviceId, headerType) 24 | 25 | if authorizationType == 0: 26 | authorizationData = makeAuthorizationData(self.ck, self.at) 27 | signatureBase = makeSignatureBase(method, header, data, authorizationData, self.ck, self.at) 28 | signatureBaseString = makeSignatureBaseString(method, url, signatureBase) 29 | signingKey = makeSigningKey(self.cs, self.ats) 30 | 31 | authorizationData["oauth_signature"] = makeOAuthSignature(signingKey, signatureBaseString) 32 | header["Authorization"] = makeAuthorizationHeaderString(authorizationData) 33 | elif authorizationType == 1: 34 | header["Authorization"] = makeBasicAuthorizationHeader(self.ck, self.cs) 35 | elif authorizationType == 2: 36 | header["Authorization"] = makeBearerAuthorizationHeader(bearerToken) 37 | elif authorizationType == 3: 38 | header["Cookie"] = None 39 | raise NotImplementedError("authorizationType 3 is not implemented yet.") 40 | else: 41 | raise NotImplementedError("This authorizationType was not supported.") 42 | 43 | if method == "GET": 44 | request = requests.get(url, params=data, headers=header) 45 | elif method == "POST": 46 | postString = makePostString(data) 47 | header["Content-Length"] = str(len(postString)) 48 | request = requests.post(url, data=postString, headers=header) 49 | else: 50 | raise NotImplementedError("This method was not supported.") 51 | result = json.loads(request.text) 52 | 53 | return result 54 | 55 | def streaming(self, callback, data=None): 56 | method = "GET" 57 | url = "https://userstream.twitter.com/1.1/user.json" 58 | if not data: 59 | data = { 60 | "replies": "all", 61 | "filter_level": "none", 62 | "include_followings_activity": "True", 63 | "stall_warnings": "True", 64 | "with": "followings" 65 | } 66 | 67 | header = makeHeader(method, url, self.uuid, self.deviceId, 0) 68 | authorizationData = makeAuthorizationData(self.ck, self.at) 69 | signatureBase = makeSignatureBase(method, header, data, authorizationData, self.ck, self.at) 70 | signatureBaseString = makeSignatureBaseString(method, url, signatureBase) 71 | signingKey = makeSigningKey(self.cs, self.ats) 72 | authorizationData["oauth_signature"] = makeOAuthSignature(signingKey, signatureBaseString) 73 | header["Authorization"] = makeAuthorizationHeaderString(authorizationData) 74 | 75 | def process(line): 76 | if not line: 77 | return 78 | line = line.decode() 79 | try: 80 | jsonObj = json.loads(line) 81 | except: 82 | raise Exception("failed to decode json. reserved data was `{}`.".format(line)) 83 | 84 | callback(jsonObj) 85 | 86 | stream = requests.get(url, params=data, headers=header, stream=True) 87 | for line in stream.iter_lines(): 88 | threading.Thread(target=process, args=(line, )).start() 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twispy 2 | ## 注意: Twispyは非アクティブなプロジェクトです. 今後, Twitterの仕様変更に従うことはありません. 3 | ## 今後は [Penicillin](https://github.com/NephyProject/Penicillin) に注力していきます. 4 | 5 | --- 6 | 7 | Twispyは, **唯一**の 全Twitter APIエンドポイントに対応した Python製のTwitter APIラッパーです。 8 | 9 | ライブラリとは分離したJSONファイルにエンドポイント情報を記述して動的にラップしているので `/api.json`を更新するだけで TwitterのAPI仕様変更に対応できるという 大きな特徴があります。 10 | 11 | 12 | もし, 動作しないエンドポイントを見つけましたら Issue立てお願いします。 13 | 14 | ## Intro 15 | 16 | ### Install & Update 17 | 18 | ```bash 19 | pip3 install -e "git+https://github.com/NephyProject/Twispy.git#egg=twispy" 20 | ``` 21 | 22 | ## Get started 23 | 24 | ```python 25 | from twispy import API 26 | 27 | # Twitter公式クライアントのConsumer Key, Consumer Secretが好ましいです 28 | ck = "IQKbtAYlXLripLGPWd0HUA" 29 | cs = "GgDYlkSvaPxGxC4X8liwpUoqKwwr3lCADbz8A7ADU" 30 | # https://nephy.jp/tools/getauth 31 | at = "" 32 | ats = "" 33 | 34 | api = API(ck, cs, at, ats) 35 | 36 | 37 | # 以下は普通のAPIラッパーではアクセスできないAPIの一例です 38 | 39 | 40 | # 固定ツイートを登録する 41 | api.account_pin_tweet( 42 | id=851150296810115072 43 | ) 44 | # 固定ツイートを解除する 45 | api.account_unpin_tweet( 46 | id=851150296810115072 47 | ) 48 | 49 | # ユーザーの拡張プロフィール(誕生日, Periscope等)を取得 50 | r = api.users_extended_profile( 51 | screen_name="Twitter" 52 | ) 53 | """ 54 | { 55 | "birthdate": { 56 | "month": 3, 57 | "visibility": "public", 58 | "day": 21, 59 | "year_visibility": "self" 60 | }, 61 | "id": 783214, 62 | "periscope_profile": { 63 | "web_link": "https://www.pscp.tv/u/1JRKmoXNLWEPy", 64 | "visible": true, 65 | "app_link": "pscp://user_id/1JRKmoXNLWEPy", 66 | "periscope_id": "1JRKmoXNLWEPy" 67 | }, 68 | "id_str": "783214" 69 | } 70 | """ 71 | 72 | # 2ユーザーの関係を取得します 73 | # sourceが自分のときは 公式キー利用時に限り blockされているかorしているか の情報が返却されます 74 | r = api.friendships_show( 75 | source_screen_name="SlashNephy", 76 | target_screen_name="Twitter" 77 | ) 78 | """ 79 | { 80 | "relationship": { 81 | "source": { 82 | "blocking": false, 83 | "blocked_by": false, 84 | "all_replies": false, 85 | "id": 701282649466245120, 86 | "live_following": false, 87 | "following_received": false, 88 | "can_dm": false, 89 | "marked_spam": false, 90 | "muting": false, 91 | "id_str": "701282649466245120", 92 | "screen_name": "SlashNephy", 93 | "followed_by": false, 94 | "following_requested": false, 95 | "can_media_tag": true, 96 | "notifications_enabled": false, 97 | "want_retweets": false, 98 | "following": false 99 | }, 100 | "target": { 101 | "id_str": "783214", 102 | "screen_name": "Twitter", 103 | "followed_by": false, 104 | "following_requested": false, 105 | "id": 783214, 106 | "following": false, 107 | "following_received": false 108 | } 109 | } 110 | } 111 | """ 112 | 113 | # フォローしているユーザー限定で「上坂すみれ」を含むツイートを検索します 114 | r = api.search_universal( 115 | q="上坂すみれ", 116 | modules="tweet", 117 | result_type="follows" 118 | ) 119 | 120 | # 投票ツイートをする 121 | api.create_poll( 122 | text="おねーちゃん", 123 | choices=[ 124 | "保登心愛", "天々座理世", "桐間紗路", "宇治松千夜" 125 | ], 126 | minutes=2880 # 2日間 127 | ) 128 | 129 | # UserStreamに接続する 130 | def callback(stream): 131 | if "text" in stream: 132 | print("{} by @{}".format(stream["text"], stream["user"]["screen_name"])) 133 | 134 | if "event" in stream and "favorite" in stream["event"]: 135 | print("{} favorited @{}'s `{}`".format(stream["source"]["screen_name"], stream["target"]["screen_name"], stream["target_object"]["text"])) 136 | 137 | api.streaming(callback) 138 | 139 | # これらの他に twispy/api.json に記されているエンドポイントにすべてアクセスできます 140 | ``` 141 | -------------------------------------------------------------------------------- /twispy/utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import base64 3 | import binascii 4 | import datetime 5 | import hashlib 6 | import hmac 7 | import json 8 | import time 9 | import urllib.parse 10 | import uuid 11 | from collections import OrderedDict 12 | 13 | from typing import Dict 14 | 15 | 16 | def escape(text) -> str: 17 | if isinstance(text, dict): 18 | text = json.dumps(text, ensure_ascii=False).replace(" ", "") 19 | return urllib.parse.quote(text, safe="~") 20 | 21 | def getCurrentEpochTime() -> int: 22 | return int(time.mktime(datetime.datetime.now().timetuple())) 23 | 24 | def getUUID() -> str: 25 | return str(uuid.uuid4()).upper() 26 | 27 | def makeHeader(method, url, uuid=None, deviceId=None, headerType=None): 28 | if not headerType: 29 | headerType = 0 30 | 31 | header = OrderedDict() 32 | if headerType == 0: 33 | header["Host"] = url.replace("https://", "").split("/")[0] 34 | header["Authorization"] = None 35 | header["X-Twitter-Client-Version"] = "6.59.3" 36 | header["Accept"] = "*/*" 37 | header["X-Client-UUID"] = uuid if uuid else getUUID() 38 | header["X-Twitter-Client-Language"] = "ja" 39 | header["X-B3-TraceId"] = hashlib.md5(str(getCurrentEpochTime()).encode()).hexdigest()[0:16] 40 | # header["Proxy-Connection"] = "keep-alive" 41 | header["Accept-Language"] = "ja" 42 | header["Accept-Encoding"] = "gzip, deflate" 43 | header["X-Twitter-Client-DeviceID"] = deviceId if deviceId else getUUID() 44 | if method == "POST": 45 | header["Content-Type"] = "application/x-www-form-urlencoded" 46 | header["Content-Length"] = None 47 | header["User-Agent"] = "Twitter-iPhone/6.59.3 iOS/9.3.3 (Apple;iPhone8,2;;;;;1)" 48 | # header["Connection"] = "keep-alive" 49 | header["X-Twitter-Client-Limit-Ad-Tracking"] = "1" 50 | header["X-Twitter-API-Version"] = "5" 51 | header["X-Twitter-Client"] = "Twitter-iPhone" 52 | elif headerType == 1: 53 | header["Host"] = url.replace("https://", "").split("/")[0] 54 | header["Authorization"] = None 55 | header["X-Twitter-Client-Version"] = "6.59.3" 56 | header["X-Twitter-Polling"] = "true" 57 | header["X-Client-UUID"] = uuid if uuid else getUUID() 58 | header["X-Twitter-Client-Language"] = "ja" 59 | header["X-B3-TraceId"] = hashlib.md5(str(getCurrentEpochTime()).encode()).hexdigest()[0:16] 60 | header["x-spdy-bypass"] = "1" 61 | header["Accept"] = "*/*" 62 | header["Accept-Language"] = "ja" 63 | header["Accept-Encoding"] = "gzip, deflate" 64 | header["X-Twitter-Client-DeviceID"] = deviceId if deviceId else getUUID() 65 | header["User-Agent"] = "Twitter-iPhone/6.59.3 iOS/9.3.3 (Apple;iPhone8,2;;;;;1)" 66 | header["X-Twitter-API-Version"] = "5" 67 | header["X-Twitter-Client-Limit-Ad-Tracking"] = "1" 68 | header["X-Twitter-Client"] = "Twitter-iPhone" 69 | elif headerType == 2: 70 | header["Host"] = url.replace("https://", "").split("/")[0] 71 | header["Accept"] = "*/*" 72 | if method == "POST": 73 | header["Content-Type"] = "application/x-www-form-urlencoded" 74 | header["Content-Length"] = None 75 | # header["Connection"] = "keep-alive" 76 | # header["Proxy-Connection"] = "keep-alive" 77 | header["Cookie"] = "guest_id=v1:" 78 | header["Accept-Language"] = "ja" 79 | header["Authorization"] = None 80 | # OAuth oauth_nonce="uuid", oauth_signature_method="HMAC-SHA1", oauth_timestamp="timestamp", oauth_consumer_key="WXZE9QillkIZpTANgLNT9g", oauth_token="token", oauth_signature="signature", oauth_version="1.0" 81 | header["Accept-Encoding"] = "gzip, deflate" 82 | header["User-Agent"] = "accountsd/113 CFNetwork/758.5.3 Darwin/15.6.0" 83 | elif headerType == 3: 84 | header["Host"] = url.replace("https://", "").split("/")[0] 85 | header["X-B3-TraceId"] = hashlib.md5(str(getCurrentEpochTime()).encode()).hexdigest()[0:16] 86 | # header["Connection"] = "keep-alive" 87 | header["X-Twitter-Client-Language"] = "ja" 88 | # header["Proxy-Connection"] = "keep-alive" 89 | header["Accept"] = "*/*" 90 | header["Accept-Language"] = "ja" 91 | header["Authorization"] = None 92 | header["Accept-Encoding"] = "gzip, deflate" 93 | header["User-Agent"] = "Twitter/6.59.3 CFNetwork/758.5.3 Darwin/15.6.0" 94 | elif headerType == 4: 95 | header["Host"] = url.replace("https://", "").split("/")[0] 96 | header["X-Twitter-Client-DeviceID"] = deviceId if deviceId else getUUID() 97 | header["Authorization"] = None 98 | header["X-Twitter-Client-Version"] = "6.59.3" 99 | header["X-Guest-Token"] = None 100 | header["X-Client-UUID"] = uuid if uuid else getUUID() 101 | header["X-Twitter-Client-Language"] = "ja" 102 | header["X-B3-TraceId"] = hashlib.md5(str(getCurrentEpochTime()).encode()).hexdigest()[0:16] 103 | header["Accept"] = "*/*" 104 | # header["Proxy-Connection"] = "keep-alive" 105 | header["Accept-Language"] = "ja" 106 | header["Accept-Encoding"] = "gzip, deflate" 107 | if method == "POST": 108 | header["Content-Type"] = "application/x-www-form-urlencoded" 109 | header["Content-Length"] = None 110 | header["User-Agent"] = "Twitter-iPhone/6.59.3 iOS/9.3.3 (Apple;iPhone8,2;;;;;1)" 111 | # header["Connection"] = "keep-alive" 112 | header["X-Twitter-Client-Limit-Ad-Tracking"] = "1" 113 | header["X-Twitter-API-Version"] = "5" 114 | header["X-Twitter-Client"] = "Twitter-iPhone" 115 | elif headerType == 5: 116 | header["Cookie"] = None 117 | raise NotImplementedError("headerType 5 is not implemented yet.") 118 | else: 119 | raise NotImplementedError("No such a headerType found.") 120 | return header 121 | 122 | def makeAuthorizationData(ck, at): 123 | authorizationData = OrderedDict() 124 | authorizationData["oauth_signature"] = None 125 | authorizationData["oauth_nonce"] = getUUID() 126 | authorizationData["oauth_timestamp"] = str(getCurrentEpochTime()) 127 | authorizationData["oauth_consumer_key"] = ck 128 | authorizationData["oauth_token"] = at 129 | authorizationData["oauth_version"] = "1.0" 130 | authorizationData["oauth_signature_method"] = "HMAC-SHA1" 131 | return authorizationData 132 | 133 | def makeBasicAuthorizationHeader(ck: str, cs: str) -> str: 134 | return "Basic {}".format(base64.b64encode("{}:{}".format(ck, cs).encode()).decode()) 135 | 136 | def makeBearerAuthorizationHeader(token: str) -> str: 137 | return "Bearer {}".format(token) 138 | 139 | def makeSignatureBase(method: str, header: Dict, data: Dict, authorizationData: Dict, ck: str, at: str) -> list: 140 | signatureBase = [] 141 | if (method.upper() == "POST" and header["Content-Type"] == "application/x-www-form-urlencoded") or method.upper() != "POST": 142 | signatureBase = [[escape(key), escape(value)] for key, value in data.items()] 143 | 144 | signatureBase.append(["oauth_consumer_key", ck]) 145 | signatureBase.append(["oauth_nonce", authorizationData["oauth_nonce"]]) 146 | signatureBase.append(["oauth_signature_method", "HMAC-SHA1"]) 147 | signatureBase.append(["oauth_timestamp", authorizationData["oauth_timestamp"]]) 148 | signatureBase.append(["oauth_version", "1.0"]) 149 | signatureBase.append(["oauth_token", at]) 150 | signatureBase.sort() 151 | return signatureBase 152 | 153 | def makeSigningKey(cs: str, ats: str) -> str: 154 | return "{}&{}".format(escape(cs), escape(ats)) 155 | 156 | def makeSignatureBaseString(method: str, url: str, signatureBase: list) -> str: 157 | return "{}&{}&{}".format(method.upper(), escape(url), escape("&".join(["{}={}".format(key, value) for key, value in signatureBase]))) 158 | 159 | def makeOAuthSignature(signingKey: str, signatureBaseString: str) -> str: 160 | return escape(binascii.b2a_base64(hmac.new(signingKey.encode(), signatureBaseString.encode(), hashlib.sha1).digest())[:-1]) 161 | 162 | def makeAuthorizationHeaderString(authorizationData: Dict) -> str: 163 | return "OAuth " + ", ".join(["{key}=\"{value}\"".format(key=x, value=y) for x, y in authorizationData.items()]) 164 | 165 | def makePostString(data: Dict) -> str: 166 | return "&".join(["{key}={value}".format(key=escape(x), value=escape(y)) for x, y in data.items()]) 167 | -------------------------------------------------------------------------------- /twispy/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "news_top": { 3 | "isSecretAPI": true, 4 | "description": "ニュースタブのニュースを取得します。", 5 | "method": "GET", 6 | "headerType": 0, 7 | "authorizationType": 0, 8 | "url": "https://api.twitter.com/1.1/news/top.json", 9 | "data": [ 10 | ["country_code", "JP"], 11 | ["lang" , "ja"], 12 | ["max_results" , "30"] 13 | ] 14 | }, 15 | "conversation_show": { 16 | "isSecretAPI": true, 17 | "description": "会話を取得します。", 18 | "method": "GET", 19 | "headerType": 0, 20 | "authorizationType": 0, 21 | "url": "https://api.twitter.com/1.1/conversation/show.json", 22 | "data": [ 23 | ["cards_platform" , "iPhone-13"], 24 | ["contributor_details" , "1"], 25 | ["count" , "20"], 26 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 27 | ["id" , null], 28 | ["include_blocked_by" , "true"], 29 | ["include_blocking" , "true"], 30 | ["include_cards" , "1"], 31 | ["include_entities" , "1"], 32 | ["include_media_features", "true"], 33 | ["include_my_retweet" , "1"], 34 | ["include_user_entities" , "true"] 35 | ] 36 | }, 37 | "conversation_suggestions_show": { 38 | "isSecretAPI": true, 39 | "description": "会話を取得します。", 40 | "method": "POST", 41 | "headerType": 0, 42 | "authorizationType": 0, 43 | "url": "https://api.twitter.com/1.1/conversation/suggestions/show.json", 44 | "data": [ 45 | ["author_id" , null], 46 | ["cards_platform" , "iPhone-13"], 47 | ["contributor_details" , "1"], 48 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 49 | ["id" , null], 50 | ["include_cards" , "1"], 51 | ["include_entities" , "1"], 52 | ["include_media_features", "true"], 53 | ["include_my_retweet" , "1"], 54 | ["include_user_entities" , "true"], 55 | ["pc" , "true"] 56 | ] 57 | }, 58 | "timeline_home": { 59 | "isSecretAPI": true, 60 | "description": "タイムラインを取得します。", 61 | "method": "GET", 62 | "headerType": 0, 63 | "authorizationType": 0, 64 | "url": "https://api.twitter.com/1.1/timeline/home.json", 65 | "data": [ 66 | ["autoplay_enabled" , "true"], 67 | ["cards_platform" , "iPhone-13"], 68 | ["contributor_details" , "1"], 69 | ["count" , "100"], 70 | ["earned" , "true"], 71 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 72 | ["include_blocked_by" , "true"], 73 | ["include_blocking" , "true"], 74 | ["include_cards" , "1"], 75 | ["include_entities" , "1"], 76 | ["include_media_features" , "true"], 77 | ["include_my_retweet" , "1"], 78 | ["include_tweet_pivots" , "true"], 79 | ["include_user_entities" , "true"], 80 | ["jit_enabled" , "true"], 81 | ["lang" , "ja"], 82 | ["last_ad_pool_refresh_epoch_ms" , "0"], 83 | ["num_unfilled_ad_slots_available", "0"], 84 | ["pc" , "true"], 85 | ["request_context" , "tweet"], 86 | ["up_cursor" , false], 87 | ["user_id" , null], 88 | ["username" , null] 89 | ] 90 | }, 91 | "timeline_user": { 92 | "isSecretAPI": true, 93 | "description": "タイムラインを取得します。", 94 | "method": "GET", 95 | "headerType": 0, 96 | "authorizationType": 0, 97 | "url": "https://api.twitter.com/1.1/timeline/user.json", 98 | "data": [ 99 | ["cards_platform" , "iPhone-13"], 100 | ["contributor_details" , "1"], 101 | ["count" , "40"], 102 | ["earned" , "true"], 103 | ["exclude_pinned_tweets" , "true"], 104 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 105 | ["id" , null], 106 | ["include_blocked_by" , "true"], 107 | ["include_blocking" , "true"], 108 | ["include_cards" , "1"], 109 | ["include_entities" , "1"], 110 | ["include_media_features", "true"], 111 | ["include_my_retweet" , "1"], 112 | ["include_rts" , "true"], 113 | ["include_tweet_replies" , "true"], 114 | ["include_user_entities" , "true"], 115 | ["lang" , "ja"], 116 | ["pc" , "true"], 117 | ["request_context" , "auto"] 118 | ] 119 | }, 120 | "statuses_media_timeline": { 121 | "isSecretAPI": true, 122 | "description": "", 123 | "method": "GET", 124 | "headerType": 0, 125 | "authorizationType": 0, 126 | "url": "https://api.twitter.com/1.1/statuses/media_timeline.json", 127 | "data": [ 128 | ["cards_platform" , "iPhone-13"], 129 | ["contributor_details" , "1"], 130 | ["count" , "50"], 131 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 132 | ["include_cards" , "1"], 133 | ["include_entities" , "1"], 134 | ["include_media_features", "true"], 135 | ["include_my_retweet" , "1"], 136 | ["include_user_entities" , "true"], 137 | ["user_id" , null] 138 | ] 139 | }, 140 | "statuses_update": { 141 | "isSecretAPI": false, 142 | "description": "ツイートを投稿します。", 143 | "method": "POST", 144 | "headerType": 0, 145 | "authorizationType": 0, 146 | "url": "https://api.twitter.com/1.1/statuses/update.json", 147 | "data": [ 148 | ["card_uri" , false], 149 | ["cards_platform" , "iPhone-13"], 150 | ["include_cards" , "1"], 151 | ["include_entities" , "1"], 152 | ["include_media_features" , "true"], 153 | ["include_my_retweet" , "1"], 154 | ["include_user_entities" , "true"], 155 | ["status" , null] 156 | ] 157 | }, 158 | "cards_create": { 159 | "isSecretAPI": true, 160 | "description": "投票を発行します。", 161 | "method": "POST", 162 | "headerType": 0, 163 | "authorizationType": 0, 164 | "url": "https://caps.twitter.com/v2/cards/create.json", 165 | "data": [ 166 | ["card_data", 167 | { 168 | "twitter:string:choice1_label" : null, 169 | "twitter:string:choice2_label" : null, 170 | "twitter:string:choice3_label" : false, 171 | "twitter:string:choice4_label" : false, 172 | "twitter:api:api:endpoint" :"1", 173 | "twitter:card" :"poll2choice_text_only", 174 | "twitter:long:duration_minutes":1440 175 | } 176 | ] 177 | ] 178 | }, 179 | "push_destinations_device": { 180 | "isSecretAPI": true, 181 | "description": "プッシュ通知を購読します。", 182 | "method": "GET", 183 | "headerType": 1, 184 | "authorizationType": 0, 185 | "url": "https://api.twitter.com/1.1/push_destinations/device.json", 186 | "data": [ 187 | ["environment", "3"], 188 | ["udid" , null] 189 | ] 190 | }, 191 | "account_push_destinations_cleanup": { 192 | "isSecretAPI": true, 193 | "description": "プッシュ通知を整理します。", 194 | "method": "POST", 195 | "headerType": 2, 196 | "authorizationType": 0, 197 | "url": "https://api.twitter.com/1.1/account/push_destinations/cleanup.json", 198 | "data": [ 199 | ["token", null] 200 | ] 201 | }, 202 | "account_push_destinations_remove": { 203 | "isSecretAPI": true, 204 | "description": "プッシュ通知を解除します。", 205 | "method": "POST", 206 | "headerType": 0, 207 | "authorizationType": 0, 208 | "url": "https://api.twitter.com/1.1/account/push_destinations/remove.json", 209 | "data": [ 210 | ["client_application_id", null], 211 | ["environment", "3"], 212 | ["screen_name", null], 213 | ["send_error_codes", "1"], 214 | ["udid", null] 215 | ] 216 | }, 217 | "push_destinations": { 218 | "isSecretAPI": true, 219 | "description": "プッシュ通知を購読しているデバイスであるかを返します。", 220 | "method": "POST", 221 | "headerType": 1, 222 | "authorizationType": 0, 223 | "url": "https://api.twitter.com/1.1/push_destinations.json", 224 | "data": [ 225 | ["app_version" , "22"], 226 | ["device_model" , "iPhone"], 227 | ["device_name" , "iPhone 6s Plus"], 228 | ["enabled_for" , "41827161"], 229 | ["environment" , "3"], 230 | ["lang" , "ja"], 231 | ["system_name" , "iPhone OS"], 232 | ["system_version", "9.3.3"], 233 | ["token" , null], 234 | ["udid" , null] 235 | ] 236 | }, 237 | "promoted_content_log": { 238 | "isSecretAPI": true, 239 | "description": "プロモツイートの既読を管理します。", 240 | "method": "POST", 241 | "headerType": 0, 242 | "authorizationType": 0, 243 | "url": "https://api.twitter.com/1.1/promoted_content/log.json", 244 | "data": [ 245 | ["event" , "long_dwell_view"], 246 | ["impression_id", null] 247 | ] 248 | }, 249 | "jot_client_event": { 250 | "isSecretAPI": true, 251 | "description": "", 252 | "method": "POST", 253 | "headerType": 0, 254 | "authorizationType": 0, 255 | "url": "https://api.twitter.com/1.1/jot/client_event", 256 | "data": [ 257 | ["lang" , "ja-JP"], 258 | ["gzip_log", ""], 259 | ["log" , 260 | [{ 261 | "_category": "client_event", 262 | "client_version": "Twitter-iPhone\\/6.59.3 iOS\\/9.3.3(Apple;iPhone8,2;;;;;1)", 263 | "event_name": "iphone:api:notifications:::open_app", 264 | "ts": null, 265 | "event_value": 0, 266 | "format_version": 2, 267 | "association_id": null, 268 | "orientation": 1 269 | }] 270 | ] 271 | ] 272 | }, 273 | "jot_t": { 274 | "isSecretAPI": true, 275 | "description": "", 276 | "method": "POST", 277 | "headerType": 0, 278 | "authorizationType": 0, 279 | "url": "https://api.twitter.com/1.1/jot/t", 280 | "data": [ 281 | ] 282 | }, 283 | "blocks_ids": { 284 | "isSecretAPI": false, 285 | "description": "", 286 | "method": "GET", 287 | "headerType": 0, 288 | "authorizationType": 0, 289 | "url": "https://api.twitter.com/1.1/blocks/ids.json", 290 | "data": [ 291 | ] 292 | }, 293 | "blocks_list": { 294 | "isSecretAPI": false, 295 | "description": "", 296 | "method": "GET", 297 | "headerType": 0, 298 | "authorizationType": 0, 299 | "url": "https://api.twitter.com/1.1/blocks/list.json", 300 | "data": [ 301 | ["cursor" , "-1"], 302 | ["include_entities" , "1"], 303 | ["include_user_entities", "true"], 304 | ["skip_status" , "false"] 305 | ] 306 | }, 307 | "blocks_create": { 308 | "isSecretAPI": false, 309 | "description": "", 310 | "method": "POST", 311 | "headerType": 0, 312 | "authorizationType": 0, 313 | "url": "https://api.twitter.com/1.1/blocks/create.json", 314 | "data": [ 315 | ["include_entities" , "1"], 316 | ["include_user_entities", "true"], 317 | ["skip_status" , "false"], 318 | ["user_id" , null] 319 | ] 320 | }, 321 | "blocks_destroy": { 322 | "isSecretAPI": false, 323 | "description": "", 324 | "method": "POST", 325 | "headerType": 0, 326 | "authorizationType": 0, 327 | "url": "https://api.twitter.com/1.1/blocks/destroy.json", 328 | "data": [ 329 | ["include_entities" , "1"], 330 | ["include_user_entities", "true"], 331 | ["skip_status" , "false"], 332 | ["user_id" , null] 333 | ] 334 | }, 335 | "friendships_create": { 336 | "isSecretAPI": false, 337 | "description": "", 338 | "method": "POST", 339 | "headerType": 0, 340 | "authorizationType": 0, 341 | "url": "https://api.twitter.com/1.1/friendships/create.json", 342 | "data": [ 343 | ["handles_challenges" , "1"], 344 | ["include_entities" , "1"], 345 | ["include_user_entities", "true"], 346 | ["user_id" , null] 347 | ] 348 | }, 349 | "mutes_users_list": { 350 | "isSecretAPI": false, 351 | "description": "", 352 | "method": "GET", 353 | "headerType": 0, 354 | "authorizationType": 0, 355 | "url": "https://api.twitter.com/1.1/mutes/users/list.json", 356 | "data": [ 357 | ["cursor" , "-1"], 358 | ["include_entities" , "1"], 359 | ["include_user_entities", "true"], 360 | ["skip_status" , "false"] 361 | ] 362 | }, 363 | "mutes_users_destroy": { 364 | "isSecretAPI": false, 365 | "description": "", 366 | "method": "POST", 367 | "headerType": 0, 368 | "authorizationType": 0, 369 | "url": "https://api.twitter.com/1.1/mutes/users/destroy.json", 370 | "data": [ 371 | ["include_entities" , "1"], 372 | ["include_user_entities", "true"], 373 | ["user_id" , null] 374 | ] 375 | }, 376 | "help_settings": { 377 | "isSecretAPI": false, 378 | "description": "", 379 | "method": "GET", 380 | "headerType": 1, 381 | "authorizationType": 0, 382 | "url": "https://api.twitter.com/1.1/help/settings.json", 383 | "data": [ 384 | ["include_zero_rate", "true"], 385 | ["settings_version" , null] 386 | ] 387 | }, 388 | "account_update_profile": { 389 | "isSecretAPI": false, 390 | "description": "following,follower,mitualfollowing,mitualfollower", 391 | "method": "POST", 392 | "headerType": 0, 393 | "authorizationType": 0, 394 | "url": "https://api.twitter.com/1.1/account/update_profile.json", 395 | "data": [ 396 | ["birthdate_day" , false], 397 | ["birthdate_month" , false], 398 | ["birthdate_visibility" , "following"], 399 | ["birthdate_year" , false], 400 | ["birthdate_year_visibility", "mutualfollow"], 401 | ["description" , false], 402 | ["include_entities" , "1"], 403 | ["include_user_entities" , "true"], 404 | ["location" , false], 405 | ["name" , false], 406 | ["url" , false] 407 | ] 408 | }, 409 | "mutes_users_ids": { 410 | "isSecretAPI": false, 411 | "description": "", 412 | "method": "GET", 413 | "headerType": 0, 414 | "authorizationType": 0, 415 | "url": "https://api.twitter.com/1.1/mutes/users/ids.json", 416 | "data": [ 417 | ] 418 | }, 419 | "friendships_incoming": { 420 | "isSecretAPI": false, 421 | "description": "", 422 | "method": "GET", 423 | "headerType": 0, 424 | "authorizationType": 0, 425 | "url": "https://api.twitter.com/1.1/friendships/incoming.json", 426 | "data": [ 427 | ] 428 | }, 429 | "friendships_outgoing": { 430 | "isSecretAPI": false, 431 | "description": "", 432 | "method": "GET", 433 | "headerType": 0, 434 | "authorizationType": 0, 435 | "url": "https://api.twitter.com/1.1/friendships/outgoing.json", 436 | "data": [ 437 | ] 438 | }, 439 | "activity_about_me": { 440 | "isSecretAPI": true, 441 | "description": "", 442 | "method": "GET", 443 | "headerType": 0, 444 | "authorizationType": 0, 445 | "url": "https://api.twitter.com/1.1/activity/about_me.json", 446 | "data": [ 447 | ["cards_platform" , "iPhone-13"], 448 | ["contributor_details" , "1"], 449 | ["count" , "50"], 450 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 451 | ["filters" , ""], 452 | ["include_cards" , "1"], 453 | ["include_entities" , "1"], 454 | ["include_media_features", "true"], 455 | ["include_my_retweet" , "1"], 456 | ["include_user_entities" , "true"], 457 | ["latest_results" , "true"], 458 | ["model_version" , "7"], 459 | ["since_id" , false] 460 | ] 461 | }, 462 | "activity_about_me_unread": { 463 | "isSecretAPI": true, 464 | "description": "cursor=true or lastcursor", 465 | "method": "GET", 466 | "headerType": 0, 467 | "authorizationType": 0, 468 | "url": "https://api.twitter.com/1.1/activity/about_me/unread.json", 469 | "data": [ 470 | ["cursor", "true"] 471 | ] 472 | }, 473 | "account_settings": { 474 | "isSecretAPI": false, 475 | "description": "", 476 | "method": "GET", 477 | "headerType": 0, 478 | "authorizationType": 0, 479 | "url": "https://api.twitter.com/1.1/account/settings.json", 480 | "data": [ 481 | ["include_alt_text_compose" , "true"], 482 | ["include_mention_filter" , "true"], 483 | ["include_ranked_timeline" , "true"], 484 | ["include_universal_quality_filtering", "true"] 485 | ] 486 | }, 487 | "search_universal": { 488 | "isSecretAPI": true, 489 | "description": "result_type=recent,follows , filter=images", 490 | "method": "GET", 491 | "headerType": 0, 492 | "authorizationType": 0, 493 | "url": "https://api.twitter.com/1.1/search/universal.json", 494 | "data": [ 495 | ["cards_platform" , "iPhone-13"], 496 | ["contributor_details" , "1"], 497 | ["enabled_verticals" , "cricket,soccer"], 498 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 499 | ["filter" , false], 500 | ["get_clusters" , "true"], 501 | ["include_cards" , "1"], 502 | ["include_entities" , "1"], 503 | ["include_media_features", "true"], 504 | ["include_my_retweet" , "1"], 505 | ["include_user_entities" , "true"], 506 | ["modules" , "user"], 507 | ["pc" , "true"], 508 | ["q" , null], 509 | ["query_source" , "typed_query"], 510 | ["result_type" , "top"], 511 | ["timezone" , "Asia/Tokyo"], 512 | ["ui_lang" , "ja"] 513 | ] 514 | }, 515 | "account_verify_credentials": { 516 | "isSecretAPI": true, 517 | "description": "", 518 | "method": "GET", 519 | "headerType": 0, 520 | "authorizationType": 0, 521 | "url": "https://api.twitter.com/1.1/account/verify_credentials.json", 522 | "data": [ 523 | ["include_entities" , "1"], 524 | ["include_user_entities", "true"] 525 | ] 526 | }, 527 | "statuses_show": { 528 | "isSecretAPI": false, 529 | "description": "/show/[id].json", 530 | "method": "GET", 531 | "headerType": 0, 532 | "authorizationType": 0, 533 | "url": "https://api.twitter.com/1.1/statuses/show.json", 534 | "data": [ 535 | ["cards_platform" ,"iPhone-13"], 536 | ["contributor_details" ,"1"], 537 | ["ext" ,"altText,stickerInfo,mediaRestrictions"], 538 | ["id" , null], 539 | ["include_cards" ,"1"], 540 | ["include_entities" ,"1"], 541 | ["include_media_features","true"], 542 | ["include_my_retweet" ,"1"], 543 | ["include_user_entities" ,"true"] 544 | ] 545 | }, 546 | "foundmedia_categories": { 547 | "isSecretAPI": true, 548 | "description": "", 549 | "method": "GET", 550 | "headerType": 0, 551 | "authorizationType": 0, 552 | "url": "https://api.twitter.com/1.1/foundmedia/categories.json", 553 | "data": [ 554 | ] 555 | }, 556 | "friendships_list": { 557 | "isSecretAPI": false, 558 | "description": "", 559 | "method": "GET", 560 | "headerType": 0, 561 | "authorizationType": 0, 562 | "url": "https://api.twitter.com/1.1/friendships/list.json", 563 | "data": [ 564 | ["user_id", null] 565 | ] 566 | }, 567 | "friends_ids": { 568 | "isSecretAPI": false, 569 | "description": "", 570 | "method": "GET", 571 | "headerType": 0, 572 | "authorizationType": 0, 573 | "url": "https://api.twitter.com/1.1/friends/ids.json", 574 | "data": [ 575 | ["type", "sms"] 576 | ] 577 | }, 578 | "search_typeahead": { 579 | "isSecretAPI": true, 580 | "description": "", 581 | "method": "GET", 582 | "headerType": 0, 583 | "authorizationType": 0, 584 | "url": "https://api.twitter.com/1.1/search/typeahead.json", 585 | "data": [ 586 | ["cards_platform" , "iPhone-13"], 587 | ["contributor_details" , "1"], 588 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 589 | ["filters" , "true"], 590 | ["include_cards" , "1"], 591 | ["include_entities" , "1"], 592 | ["include_media_features", "true"], 593 | ["include_my_retweet" , "1"], 594 | ["include_user_entities" , "true"], 595 | ["q" , null], 596 | ["result_type" , "users"], 597 | ["src" , "search_box"] 598 | ] 599 | }, 600 | "saved_searches_list": { 601 | "isSecretAPI": false, 602 | "description": "", 603 | "method": "GET", 604 | "headerType": 0, 605 | "authorizationType": 0, 606 | "url": "https://api.twitter.com/1.1/saved_searches/list.json", 607 | "data": [ 608 | ] 609 | }, 610 | "cards_preview": { 611 | "isSecretAPI": true, 612 | "description": "", 613 | "method": "GET", 614 | "headerType": 0, 615 | "authorizationType": 0, 616 | "url": "https://caps.twitter.com/v2/cards/preview.json", 617 | "data": [ 618 | ["cards_platform" , "iPhone-13"], 619 | ["include_blocked_by", "true"], 620 | ["include_blocking" , "true"], 621 | ["include_cards" , "1"], 622 | ["status" , null] 623 | ] 624 | }, 625 | "prompts_record_event": { 626 | "isSecretAPI": true, 627 | "description": "", 628 | "method": "POST", 629 | "headerType": 0, 630 | "authorizationType": 0, 631 | "url": "https://api.twitter.com/1.1/prompts/record_event.json", 632 | "data": [ 633 | ["action" , "shown"], 634 | ["prompt_id", null], 635 | ["user_id" , null] 636 | ] 637 | }, 638 | "prompts_suggest": { 639 | "isSecretAPI": true, 640 | "description": "", 641 | "method": "GET", 642 | "headerType": 0, 643 | "authorizationType": 0, 644 | "url": "https://api.twitter.com/1.1/prompts/suggest.json", 645 | "data": [ 646 | ["client_namespace" , "native"], 647 | ["force_fatigue_on_override", "false"], 648 | ["format" , "home_timeline"], 649 | ["has_unknown_phone_number" , "true"], 650 | ["lang" , "ja"], 651 | ["notifications_device" , "true"] 652 | ] 653 | }, 654 | "ads_campaigns_account_permissions": { 655 | "isSecretAPI": true, 656 | "description": "", 657 | "method": "GET", 658 | "headerType": 0, 659 | "authorizationType": 0, 660 | "url": "https://api.twitter.com/1.1/ads/campaigns/account_permissions.json", 661 | "data": [ 662 | ] 663 | }, 664 | "users_recommendations": { 665 | "isSecretAPI": false, 666 | "description": "", 667 | "method": "GET", 668 | "headerType": 0, 669 | "authorizationType": 0, 670 | "url": "https://api.twitter.com/1.1/users/recommendations.json", 671 | "data": [ 672 | ["connections" , "true"], 673 | ["display_location" , "st-component"], 674 | ["include_entities" , "1"], 675 | ["include_user_entities", "true"], 676 | ["limit" , "3"], 677 | ["pc" , "true"], 678 | ["screen_name" , null] 679 | ] 680 | }, 681 | "users_lookup": { 682 | "isSecretAPI": false, 683 | "description": "", 684 | "method": "GET", 685 | "headerType": 0, 686 | "authorizationType": 0, 687 | "url": "https://api.twitter.com/1.1/users/lookup.json", 688 | "data": [ 689 | ["include_entities" , "1"], 690 | ["include_user_entities", "true"], 691 | ["user_id" , null] 692 | ] 693 | }, 694 | "people_discovery_modules": { 695 | "isSecretAPI": true, 696 | "description": "", 697 | "method": "GET", 698 | "headerType": 0, 699 | "authorizationType": 0, 700 | "url": "https://api.twitter.com/1.1/people_discovery/modules.json", 701 | "data": [ 702 | ["display_location" , "1000"], 703 | ["first_time" , "0"], 704 | ["has_ab_permission", "0"], 705 | ["layout_version" , "2"], 706 | ["supported_layouts", "badge-carousel,featured-users-carousel,picker,address-book-prompt,prompt,user-tweet-carousel,user-bio-list,user-media-list,user-profile-carousel,address-book-contacts"] 707 | ] 708 | }, 709 | "account_pin_tweet": { 710 | "isSecretAPI": true, 711 | "description": "", 712 | "method": "POST", 713 | "headerType": 0, 714 | "authorizationType": 0, 715 | "url": "https://api.twitter.com/1.1/account/pin_tweet.json", 716 | "data": [ 717 | ["id", null] 718 | ] 719 | }, 720 | "account_unpin_tweet": { 721 | "isSecretAPI": true, 722 | "description": "", 723 | "method": "POST", 724 | "headerType": 0, 725 | "authorizationType": 0, 726 | "url": "https://api.twitter.com/1.1/account/unpin_tweet.json", 727 | "data": [ 728 | ["id", null] 729 | ] 730 | }, 731 | "favorites_list": { 732 | "isSecretAPI": true, 733 | "description": "", 734 | "method": "GET", 735 | "headerType": 0, 736 | "authorizationType": 0, 737 | "url": "https://api.twitter.com/1.1/favorites/list.json", 738 | "data": [ 739 | ["cards_platform" , "iPhone-13"], 740 | ["contributor_details" , "1"], 741 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 742 | ["include_cards" , "1"], 743 | ["include_entities" , "1"], 744 | ["include_media_features", "true"], 745 | ["include_my_retweet" , "1"], 746 | ["include_user_entities" , "true"], 747 | ["user_id" , null] 748 | ] 749 | }, 750 | "users_extended_profile": { 751 | "isSecretAPI": true, 752 | "description": "", 753 | "method": "GET", 754 | "headerType": 0, 755 | "authorizationType": 0, 756 | "url": "https://api.twitter.com/1.1/users/extended_profile.json", 757 | "data": [ 758 | ["include_birthdate", "true"], 759 | ["screen_name" , null] 760 | ] 761 | }, 762 | "users_email_phone_info": { 763 | "isSecretAPI": true, 764 | "description": "", 765 | "method": "GET", 766 | "headerType": 0, 767 | "authorizationType": 0, 768 | "url": "https://api.twitter.com/1.1/users/email_phone_info.json", 769 | "data": [ 770 | ["include_pending_email", "true"] 771 | ] 772 | }, 773 | "users_show": { 774 | "isSecretAPI": true, 775 | "description": "", 776 | "method": "GET", 777 | "headerType": 0, 778 | "authorizationType": 0, 779 | "url": "https://api.twitter.com/1.1/users/show.json", 780 | "data": [ 781 | ["include_entities" , "1"], 782 | ["include_user_entities", "true"], 783 | ["user_id" , null] 784 | ] 785 | }, 786 | "dm_conversation_mark_read": { 787 | "isSecretAPI": true, 788 | "description": "", 789 | "method": "POST", 790 | "headerType": 0, 791 | "authorizationType": 0, 792 | "url": "https://api.twitter.com/1.1/dm/conversation/mark_read.json", 793 | "data": [ 794 | ["cards_platform" , "iPhone-13"], 795 | ["contributor_details" , "1"], 796 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 797 | ["include_cards" , "1"], 798 | ["include_entities" , "1"], 799 | ["include_media_features", "true"], 800 | ["include_my_retweet" , "1"], 801 | ["include_user_entities" , "true"], 802 | ["last_read_event_id" , null] 803 | ] 804 | }, 805 | "dm_update_last_seen_event_id": { 806 | "isSecretAPI": true, 807 | "description": "", 808 | "method": "POST", 809 | "headerType": 0, 810 | "authorizationType": 0, 811 | "url": "https://api.twitter.com/1.1/dm/update_last_seen_event_id.json", 812 | "data": [ 813 | ["cards_platform" , "iPhone-13"], 814 | ["contributor_details" , "1"], 815 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 816 | ["include_cards" , "1"], 817 | ["include_entities" , "1"], 818 | ["include_media_features", "true"], 819 | ["include_my_retweet" , "1"], 820 | ["include_user_entities" , "true"], 821 | ["last_seen_event_id" , null] 822 | ] 823 | }, 824 | "friendships_accept": { 825 | "isSecretAPI": true, 826 | "description": "", 827 | "method": "POST", 828 | "headerType": 0, 829 | "authorizationType": 0, 830 | "url": "https://api.twitter.com/1.1/friendships/accept.json", 831 | "data": [ 832 | ["include_entities" , "1"], 833 | ["include_user_entities" , "true"], 834 | ["user_id" , null] 835 | ] 836 | }, 837 | "friendships_refuse": { 838 | "isSecretAPI": true, 839 | "description": "", 840 | "method": "POST", 841 | "headerType": 0, 842 | "authorizationType": 0, 843 | "url": "https://api.twitter.com/1.1/friendships/refuse.json", 844 | "data": [ 845 | ["include_entities" , "1"], 846 | ["include_user_entities" , "true"], 847 | ["user_id" , null] 848 | ] 849 | }, 850 | "friendships_show": { 851 | "isSecretAPI": true, 852 | "description": "", 853 | "method": "GET", 854 | "headerType": 0, 855 | "authorizationType": 0, 856 | "url": "https://api.twitter.com/1.1/friendships/show.json", 857 | "data": [ 858 | ["source_screen_name", null], 859 | ["target_screen_name", null] 860 | ] 861 | }, 862 | "trends_plus": { 863 | "isSecretAPI": true, 864 | "description": "", 865 | "method": "GET", 866 | "headerType": 0, 867 | "authorizationType": 0, 868 | "url": "https://api.twitter.com/1.1/trends/plus.json", 869 | "data": [ 870 | ["cards_platform" , "iPhone-13"], 871 | ["contributor_details" , "1"], 872 | ["ext" , "altText,stickerInfo,mediaRestrictions"], 873 | ["include_cards" , "1"], 874 | ["include_entities" , "1"], 875 | ["include_media_features", "true"], 876 | ["include_my_retweet" , "1"], 877 | ["include_user_entities" , "true"], 878 | ["pc" , "true"] 879 | ] 880 | }, 881 | "capi_passthrough": { 882 | "isSecretAPI": true, 883 | "description": "", 884 | "method": "GET", 885 | "headerType": 3, 886 | "authorizationType": 0, 887 | "url": "https://caps.twitter.com/v2/capi/passthrough/1", 888 | "data": [ 889 | ["twitter:string:card_uri" , "card://null"], 890 | ["twitter:string:cards_platform" , "iPhone-13"], 891 | ["twitter:string:response_card_name", "poll2choice_text_only"] 892 | ] 893 | }, 894 | "statuses_favorited_by": { 895 | "isSecretAPI": true, 896 | "description": "", 897 | "method": "GET", 898 | "headerType": 0, 899 | "authorizationType": 0, 900 | "url": "https://api.twitter.com/1.1/statuses/favorited_by.json", 901 | "data": [ 902 | ["cursor" , "-1"], 903 | ["id" , null], 904 | ["include_entities" , "1"], 905 | ["include_user_entities", "true"] 906 | ] 907 | }, 908 | "statuses_retweeted_by": { 909 | "isSecretAPI": true, 910 | "description": "", 911 | "method": "GET", 912 | "headerType": 0, 913 | "authorizationType": 0, 914 | "url": "https://api.twitter.com/1.1/statuses/retweeted_by.json", 915 | "data": [ 916 | ["cursor" , "-1"], 917 | ["id" , null], 918 | ["include_entities" , "1"], 919 | ["include_user_entities", "true"] 920 | ] 921 | }, 922 | "geo_places": { 923 | "isSecretAPI": true, 924 | "description": "", 925 | "method": "GET", 926 | "headerType": 0, 927 | "authorizationType": 0, 928 | "url": "https://api.twitter.com/1.1/geo/places.json", 929 | "data": [ 930 | ["device_type", "iPhone8,2"], 931 | ["hacc" , "0"], 932 | ["os" , "iPhone OS 9.3.3"], 933 | ["query_type" , "profile_location"], 934 | ["wifi_on" , "true"] 935 | ] 936 | }, 937 | "dm_user_updates": { 938 | "isSecretAPI": true, 939 | "description": "", 940 | "method": "GET", 941 | "headerType": 0, 942 | "authorizationType": 0, 943 | "url": "https://api.twitter.com/1.1/dm/user_updates.json", 944 | "data": [ 945 | ["cards_platform", "iPhone-13"], 946 | ["cursor" , null], 947 | ["dm_users" , "true"], 948 | ["include_cards" , "1"], 949 | ["include_groups", "true"] 950 | ] 951 | }, 952 | "oauth_token": { 953 | "isSecretAPI": false, 954 | "description": "", 955 | "method": "POST", 956 | "headerType": 0, 957 | "authorizationType": 0, 958 | "url": "https://api.twitter.com/oauth/access_token", 959 | "data": [ 960 | ["adc" , "phone"], 961 | ["x_auth_access_secret", null], 962 | ["x_auth_access_token" , null], 963 | ["x_auth_mode" , "exchange_auth"] 964 | ] 965 | }, 966 | "oauth2_token": { 967 | "isSecretAPI": false, 968 | "description": "basic", 969 | "method": "POST", 970 | "headerType": 0, 971 | "authorizationType": 1, 972 | "url": "https://api.twitter.com/oauth2/token", 973 | "data": [ 974 | ["grant_type" , "client_credentials"], 975 | ["send_error_codes", "1"] 976 | ] 977 | }, 978 | "guest_activate": { 979 | "isSecretAPI": true, 980 | "description": "bearer", 981 | "method": "POST", 982 | "headerType": 0, 983 | "authorizationType": 2, 984 | "url": "https://api.twitter.com/1.1/guest/activate.json", 985 | "data": [ 986 | ] 987 | }, 988 | "post_account_settings": { 989 | "isSecretAPI": false, 990 | "description": "", 991 | "method": "POST", 992 | "headerType": 0, 993 | "authorizationType": 0, 994 | "url": "https://api.twitter.com/1.1/account/settings.json", 995 | "data": [ 996 | ["include_alt_text_compose", "true"], 997 | ["include_mention_filter", "true"], 998 | ["include_ranked_timeline", "true"], 999 | ["include_universal_quality_filtering", "true"], 1000 | ["protected", null] 1001 | ] 1002 | }, 1003 | "mob_idsync_generate": { 1004 | "isSecretAPI": true, 1005 | "description": "", 1006 | "method": "GET", 1007 | "headerType": 0, 1008 | "authorizationType": 0, 1009 | "url": "https://analytics.twitter.com/mob_idsync_generate.json", 1010 | "data": [ 1011 | ["ad_id" , null], 1012 | ["user_id", null] 1013 | ] 1014 | }, 1015 | "i_anonymize": { 1016 | "isSecretAPI": true, 1017 | "description": "", 1018 | "method": "POST", 1019 | "headerType": 1, 1020 | "authorizationType": 0, 1021 | "url": "https://twitter.com/i/anonymize", 1022 | "data": [ 1023 | ["data", 1024 | [{"guest_id":"","limit_ad_tracking":1,"ifa":"","dev_model":"iPhone8,2","os_name":"iPhone OS","dev_carrier":"","ifv":"","user_id":"","dev_brand":"apple","action":"signin","lang":"ja","os_ver":"9.3.3","integration":"hasOffersEvent","country_code":"JP","ts":"1474029738.960609","user_agent":"Twitter-iPhone\/6.59.3 iOS\/9.3.3 (Apple;iPhone8,2;;;;;1)"}] 1025 | ], 1026 | ["send_error_codes", "1"] 1027 | ] 1028 | }, 1029 | "xauth_password": { 1030 | "isSecretAPI": true, 1031 | "description": "x_auth_identifier=sn", 1032 | "method": "POST", 1033 | "headerType": 4, 1034 | "authorizationType": 0, 1035 | "url": "https://api.twitter.com/auth/1/xauth_password.json", 1036 | "data": [ 1037 | ["kdt" , null], 1038 | ["send_error_codes" , "1"], 1039 | ["x_auth_identifier" , null], 1040 | ["x_auth_login_verification", "true"], 1041 | ["x_auth_password" , null], 1042 | ["x_auth_supports_1fa" , "true"] 1043 | ] 1044 | }, 1045 | "moments_pivot": { 1046 | "isSecretAPI": true, 1047 | "description": "v=userid", 1048 | "method": "GET", 1049 | "headerType": 0, 1050 | "authorizationType": 0, 1051 | "url": "https://api.twitter.com/1.1/moments/pivot.json", 1052 | "data": [ 1053 | ["cards_platform", "iPhone-13"], 1054 | ["ext", "stickerInfo"], 1055 | ["include_blocked_by", "1"], 1056 | ["include_blocking", "1"], 1057 | ["include_cards", "1"], 1058 | ["moment_id", null], 1059 | ["v", 1469210400] 1060 | ] 1061 | }, 1062 | "moments_capsule": { 1063 | "isSecretAPI": true, 1064 | "description": "", 1065 | "method": "GET", 1066 | "headerType": 0, 1067 | "authorizationType": 0, 1068 | "url": "https://api.twitter.com/1.1/moments/capsule/.json", 1069 | "data": [ 1070 | ["cards_platform", "iPhone-13"], 1071 | ["ext", "stickerInfo"], 1072 | ["hydration_count", "100"], 1073 | ["include_blocked_by", "1"], 1074 | ["include_blocking", "1"], 1075 | ["include_cards", "1"], 1076 | ["v", 1469210400] 1077 | ] 1078 | }, 1079 | "moments_create": { 1080 | "isSecretAPI": true, 1081 | "description": "", 1082 | "method": "POST", 1083 | "headerType": 5, 1084 | "authorizationType": 3, 1085 | "url": "https://twitter.com/i/moments/create", 1086 | "data": [ 1087 | ] 1088 | }, 1089 | "moments_edit": { 1090 | "isSecretAPI": true, 1091 | "description": "", 1092 | "method": "POST", 1093 | "headerType": 5, 1094 | "authorizationType": 3, 1095 | "url": "https://twitter.com/i/moments/edit//meta", 1096 | "data": [ 1097 | ] 1098 | }, 1099 | "moments_add": { 1100 | "isSecretAPI": true, 1101 | "description": "", 1102 | "method": "POST", 1103 | "headerType": 5, 1104 | "authorizationType": 3, 1105 | "url": "https://twitter.com/i/moments/edit//add", 1106 | "data": [ 1107 | ] 1108 | } 1109 | } --------------------------------------------------------------------------------