BANKA2017 / twitter-monitor

Twitter Crawler Core and some based apps
https://tmapi.nest.moe
MIT License
143 stars 15 forks source link

Signature builder returns wrong signature. #14

Closed GirlBoomer closed 9 months ago

GirlBoomer commented 9 months ago

你好 BANKA, I am pretty sure that your signature builder (https://banka2017.github.io/twitter-monitor/apps/online_tools/oauth_signature_builder.html) generates wrong signatures when using GraphQL links.

import asyncio

import aiohttp
import orjson

async def test() -> None:
    """Inputs used in signature builder.

    1:
        oauth_consumer_key = "3nVuSoBZnx6U4vzUxf5w"
        oauth_consumer_secret = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys"
        oauth_token = "1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD",
        oauth_token_secret = "TmgPWdJomJWfXZKKpMkKCFjCDk8LFrTcyaqEtKe4NYNzv"
        method = "GET"
        url = "https://api.twitter.com/1.1/users/show.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&skip_status=1&user_id=783214"
        body = ""
        timestamp = "1706110770"
        nonce = "OWZiNWZiZjY4OTkxNDdkZmIyMWY5ZjU2YTJhODZhZWM"

        Generated signature: 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD", oauth_nonce="OWZiNWZiZjY4OTkxNDdkZmIyMWY5ZjU2YTJhODZhZWM", oauth_timestamp="1706110770", oauth_signature="GZ3cEyTVhLp9NtSQYW%2BebDCH%2BaU%3D", oauth_consumer_key="3nVuSoBZnx6U4vzUxf5w", oauth_signature_method="HMAC-SHA1"'

    2:
        oauth_consumer_key = "3nVuSoBZnx6U4vzUxf5w"
        oauth_consumer_secret = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys"
        oauth_token = "1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD",
        oauth_token_secret = "TmgPWdJomJWfXZKKpMkKCFjCDk8LFrTcyaqEtKe4NYNzv"
        method = "GET"
        url = "https://api.twitter.com/graphql/CO4_gU4G_MRREoqfiTh6Hg/UserByRestId?variables=%7B%22withSuperFollowsUserFields%22%3Atrue%2C%22withSafetyModeUserFields%22%3Atrue%2C%22userId%22%3A%22783214%22%7D&features=%7B%22hidden_profile_likes_enabled%22%3Atrue%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D"
        body = ""
        timestamp = "1706110880"
        nonce = "YzI3YzQxMjBjYzlkNDE0OTliNGMxM2YxNDEzZWEwMDU"

        Generated signature: 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD", oauth_nonce="YzI3YzQxMjBjYzlkNDE0OTliNGMxM2YxNDEzZWEwMDU", oauth_timestamp="1706110880", oauth_signature="9miWy0rpZbYibURJTM4zKBasPVc%3D", oauth_consumer_key="3nVuSoBZnx6U4vzUxf5w", oauth_signature_method="HMAC-SHA1"'

    """

    test_cases = [
        {   
            "name": "restful",

            "url": "https://api.twitter.com/1.1/users/show.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&skip_status=1&user_id=783214",

            "authorization": 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD", oauth_nonce="OWZiNWZiZjY4OTkxNDdkZmIyMWY5ZjU2YTJhODZhZWM", oauth_timestamp="1706110770", oauth_signature="GZ3cEyTVhLp9NtSQYW%2BebDCH%2BaU%3D", oauth_consumer_key="3nVuSoBZnx6U4vzUxf5w", oauth_signature_method="HMAC-SHA1"'
        },
        {

            "name": "graphql",

            "url": "https://api.twitter.com/graphql/CO4_gU4G_MRREoqfiTh6Hg/UserByRestId?variables=%7B%22withSuperFollowsUserFields%22%3Atrue%2C%22withSafetyModeUserFields%22%3Atrue%2C%22userId%22%3A%22783214%22%7D&features=%7B%22hidden_profile_likes_enabled%22%3Atrue%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D",

            "authorization": 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD", oauth_nonce="YzI3YzQxMjBjYzlkNDE0OTliNGMxM2YxNDEzZWEwMDU", oauth_timestamp="1706110880", oauth_signature="9miWy0rpZbYibURJTM4zKBasPVc%3D", oauth_consumer_key="3nVuSoBZnx6U4vzUxf5w", oauth_signature_method="HMAC-SHA1"'
        }
    ]

    for case in test_cases:

        headers = {
            "Authorization": case["authorization"],
            "Content-Type": "application/json",
            "User-Agent": "TwitterAndroid/10.24.0-release.0 (310240000-r-0) sdk_gphone64_x86_64/12 (Google;sdk_gphone64_x86_64;google;sdk_gphone64_x86_64;0;;1;2013)",
            "X-Twitter-API-Version": "11",
            "X-Twitter-Client": "TwitterAndroid",
            "X-Twitter-Client-Version": "10.24.0-release.0",
            "OS-Version": "31",
            "System-User-Agent": "Dalvik/2.1.0 (Linux; U; Android 12; sdk_gphone64_x86_64 Build/SE1A.220826.008)",
            "X-Twitter-Active-User": "yes",
        }

        async with aiohttp.ClientSession() as session:
            async with session.get(
                    url=case["url"], 
                    headers=headers
                    ) as response:
                resp = await response.content.read(-1)
                print(f'[{case["name"]}] Response:\n {orjson.loads(resp)}\n')

                try:
                    print(f'Rate limits:\n{response.headers["x-rate-limit-limit"]}:{response.headers["x-rate-limit-remaining"]}\n')
                except KeyError:
                    print("Failed to get ratelimits.\n")

    await asyncio.Future()

if __name__ == "__main__":
    asyncio.run(test())

As a a result, response for restful will return expected info in json as expexcted:

[restful] Response:
 {'id': 783214, 'id_str': '783214', 'name': 'X', 'screen_name': 'X', 'location': 'everywhere', 'profile_location': None, 'description': "what's happening?!", 'url': 'https://t.co/bGcvaMApJO', 'entities': {'url': {'urls': [{'url': 'https://t.co/bGcvaMApJO', 'expanded_url': 'https://about.x.com/', 'display_url': 'about.x.com', 'indices': [0, 23]}]}, 'description': {'urls': []}}, 'protected': False, 'followers_count': 67436194, 'fast_followers_count': 0, 'normal_followers_count': 67436194, 'friends_count': 0, 'listed_count': 88670, 'created_at': 'Tue Feb 20 14:35:54 +0000 2007', 'favourites_count': 5909, 'utc_offset': None, 'time_zone': None, 'geo_enabled': True, 'verified': False, 'statuses_count': 15124, 'media_count': 2413, 'lang': None, 'contributors_enabled': False, 'is_translator': False, 'is_translation_enabled': False, 'profile_background_color': 'ACDED6', 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme18/bg.gif', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme18/bg.gif', 'profile_background_tile': True, 'profile_image_url': 
'http://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_normal.jpg', 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/783214/1690175171', 'profile_link_color': '1B95E0', 'profile_sidebar_border_color': 'FFFFFF', 'profile_sidebar_fill_color': 'F6F6F6', 'profile_text_color': '333333', 'profile_use_background_image': True, 'has_extended_profile': True, 'default_profile': False, 'default_profile_image': False, 'pinned_tweet_ids': [], 'pinned_tweet_ids_str': [], 'has_custom_timelines': True, 'can_dm': False, 'can_media_tag': True, 'following': False, 'follow_request_sent': False, 'notifications': False, 'muting': False, 'blocking': False, 'blocked_by': False, 'want_retweets': False, 'advertiser_account_type': 'promotable_user', 'advertiser_account_service_levels': ['dso', 'dso', 'dso', 'analytics', 'analytics', 'media_studio'], 'analytics_type': 'enabled', 'profile_interstitial_type': '', 'business_profile_state': 'none', 'translator_type': 'regular', 'withheld_in_countries': [], 'followed_by': False, 'require_some_consent': False}

Rate limits:
900:895

where GraphQL will return:

 [graphql] Response:
 {'errors': [{'message': 'Could not authenticate you', 'code': 32}]}

Failed to get ratelimits.
BANKA2017 commented 9 months ago

I guess it's because the two requests use the same oauth_nonce, please click the Random button next to the input box to generate a new oauth_nonce

The oauth_nonce parameter is a unique token your application should generate for each unique request. Twitter will use this value to determine whether a request has been submitted multiple times.

GirlBoomer commented 9 months ago

Even when generated fresh timestamp and nonce it will not authorize. I have updated example code to represent that this is the case.

BANKA2017 commented 9 months ago

It's weird, because it works in node.js

console.log(await (await fetch('https://api.twitter.com/graphql/CO4_gU4G_MRREoqfiTh6Hg/UserByRestId?variables=%7B%22withSuperFollowsUserFields%22%3Atrue%2C%22withSafetyModeUserFields%22%3Atrue%2C%22userId%22%3A%22783214%22%7D&features=%7B%22hidden_profile_likes_enabled%22%3Atrue%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D', {
    headers: {
        authorization: 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="1749847962413617152-wZGYi1AGGPFWpq5mxU767lMKn6WrCD", oauth_nonce="YzI3YzQxMjBjYzlkNDE0OTliNGMxM2YxNDEzZWEwMDU", oauth_timestamp="1706110880", oauth_signature="9miWy0rpZbYibURJTM4zKBasPVc%3D", oauth_consumer_key="3nVuSoBZnx6U4vzUxf5w", oauth_signature_method="HMAC-SHA1"'
    }
})).text())

// response
// {"data":{"user":{"result":{"__typename":"User","id":"VXNlcjo3ODMyMTQ=","rest_id":"783214","affiliates_highlighted_label":{},"has_graduated_access":true,"is_blue_verified":true,"profile_image_shape":"Square","legacy":{"can_dm":false,"can_media_tag":true,"created_at":"Tue Feb 20 14:35:54 +0000 2007","default_profile":false,"default_profile_image":false,"description":"what's happening?!","entities":{"description":{"urls":[]},"url":{"urls":[{"display_url":"about.x.com","expanded_url":"https://about.x.com/","url":"https://t.co/bGcvaMApJO","indices":[0,23]}]}},"fast_followers_count":0,"favourites_count":5908,"followers_count":67438322,"friends_count":0,"has_custom_timelines":true,"is_translator":false,"listed_count":88672,"location":"everywhere","media_count":2413,"name":"X","normal_followers_count":67438322,"pinned_tweet_ids_str":[],"possibly_sensitive":false,"profile_banner_url":"https://pbs.twimg.com/profile_banners/783214/1690175171","profile_image_url_https":"https://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_normal.jpg","profile_interstitial_type":"","screen_name":"X","statuses_count":15124,"translator_type":"regular","url":"https://t.co/bGcvaMApJO","verified":false,"verified_type":"Business","want_retweets":false,"withheld_in_countries":[]},"smart_blocked_by":false,"smart_blocking":false,"business_account":{"affiliates_count":100},"highlights_info":{"can_highlight_tweets":true,"highlighted_tweets":"3"},"creator_subscriptions_count":0,"has_hidden_likes_on_profile":false,"has_hidden_subscriptions_on_profile":false}}}}
BANKA2017 commented 9 months ago

I don't know how to solve this problem. But there should be no problem with the signature builder of that page.

GirlBoomer commented 9 months ago

This has something to do with aiohttp, I am not going to investigate. Problem was solved by switching to different client.