Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Retrieve votes of a community post poll #258

Open
Benjamin-Loison opened this issue Mar 26, 2024 · 15 comments
Open

Retrieve votes of a community post poll #258

Benjamin-Loison opened this issue Mar 26, 2024 · 15 comments
Assignees
Labels
enhancement New feature or request medium priority A high-priority issue noticeable by the user but he can still work around it. medium A task that should take less than a day to complete. private data Requires authentication to grab such data. Might be treated in `Webscrap_any_website` repository.

Comments

@Benjamin-Loison
Copy link
Owner

Benjamin-Loison commented Mar 26, 2024

Would solve the Stack Overflow question 78221750, also asked on Discord.

Let us first ensure that without being authenticated it is not doable. I confirm that without being authenticated from YouTube UI point of view there does not seem to be any button etc and the retrieve data as JSON do not contain the results. Note that it seems possible to withdraw a vote.

Then can assign an account to the official instance leveraging it to see votes and if necessary vote to see them.

Maybe as a first step can provide the feature only to community endpoint and not in channels?part=community to ease the implementation while providing the feature. Related to #69.

minimizeCURL curl.sh 'voteRatioIfSelected'
curl https://www.youtube.com/youtubei/v1/browse -H 'Content-Type: application/json' -H 'Origin: https://www.youtube.com' -H 'Authorization: SAPISIDHASH CENSORED' -H 'Cookie: __Secure-1PSIDTS=sidts-CENSORED; __Secure-1PSID=CENSORED; __Secure-1PAPISID=CENSORED' --data-raw '{"context": {"client": {"clientName": "WEB", "clientVersion": "2.20240325.01.00"}}, "browseId": "UCQxJsAlqmBPAbR_0syDi9mg", "params": "CENSORED"}'
Python script:
import requests
import json

SAPISIDHASH = 'CENSORED'
__Secure_1PSIDTS = 'sidts-CENSORED'
__Secure_1PSID = 'CENSORED'
__Secure_1PAPISID = 'CENSORED'

url = 'https://www.youtube.com/youtubei/v1/browse'

headers = {
    'Origin': 'https://www.youtube.com',
    'Authorization': f'SAPISIDHASH {SAPISIDHASH}',
}

cookies = {
    '__Secure-1PSIDTS': __Secure_1PSIDTS,
    '__Secure-1PSID': __Secure_1PSID,
    '__Secure-1PAPISID': __Secure_1PAPISID,
}

data = {
    'context': {
        'client': {
            'clientName': 'WEB',
            'clientVersion': '2.20240325.01.00'
        }
    },
    'browseId': 'UCQxJsAlqmBPAbR_0syDi9mg',
    'params': 'CENSORED'
}

data = requests.post(url, headers = headers, cookies = cookies, json = data).json()
#print(json.dumps(data, indent = 4))
print('voteRatioIfSelected' in str(data))
Python script:
import requests
import json
import blackboxprotobuf
import base64

SAPISIDHASH = 'CENSORED'
__Secure_1PSIDTS = 'sidts-CENSORED'
__Secure_1PSID = 'CENSORED'
__Secure_1PAPISID = 'CENSORED'

def getBase64Protobuf(message, typedef):
    data = blackboxprotobuf.encode_message(message, typedef)
    return base64.b64encode(data).decode('ascii')

message = {
    '2': 'community',
    '25': {
        '22': 'UgwSoAm2bGLaJM44UTZ4AaABCQ'
    },
    '45': {
        '2': 1,
        '3': 1
    }
}

typedef = {
    '2': {
        'type': 'string'
    },
    '25': {
        'type': 'message',
        'message_typedef': {
            '22': {
                'type': 'string'
            }
        },
        'field_order': [
            '22'
        ]
    },
    '45': {
        'type': 'message',
        'message_typedef': {
            '2': {
                'type': 'int'
            },
            '3': {
                'type': 'int'
            }
        },
        'field_order': [
            '2',
            '3'
        ]
    }
}

params = getBase64Protobuf(message, typedef)

url = 'https://www.youtube.com/youtubei/v1/browse'

headers = {
    'Origin': 'https://www.youtube.com',
    'Authorization': f'SAPISIDHASH {SAPISIDHASH}',
}

cookies = {
    '__Secure-1PSIDTS': __Secure_1PSIDTS,
    '__Secure-1PSID': __Secure_1PSID,
    '__Secure-1PAPISID': __Secure_1PAPISID,
}

data = {
    'context': {
        'client': {
            'clientName': 'WEB',
            'clientVersion': '2.20240325.01.00',
        }
    },
    'browseId': 'UCQxJsAlqmBPAbR_0syDi9mg',
    'params': params,
}

data = requests.post(url, headers = headers, cookies = cookies, json = data).json()
print('voteRatioIfSelected' in str(data))
Python script:
import requests
import json
import blackboxprotobuf
import base64

SAPISIDHASH = 'CENSORED'
__Secure_1PSIDTS = 'sidts-CENSORED'
__Secure_1PSID = 'CENSORED'
__Secure_1PAPISID = 'CENSORED'

def getBase64Protobuf(message, typedef):
    data = blackboxprotobuf.encode_message(message, typedef)
    return base64.b64encode(data).decode('ascii')

message = {
    '2': 'community',
    '25': {
        '22': 'UgwSoAm2bGLaJM44UTZ4AaABCQ'
    },
}

typedef = {
    '2': {
        'type': 'string'
    },
    '25': {
        'type': 'message',
        'message_typedef': {
            '22': {
                'type': 'string'
            }
        },
        'field_order': [
            '22'
        ]
    },
}

params = getBase64Protobuf(message, typedef)

url = 'https://www.youtube.com/youtubei/v1/browse'

headers = {
    'Origin': 'https://www.youtube.com',
    'Authorization': f'SAPISIDHASH {SAPISIDHASH}',
}

cookies = {
    '__Secure-1PSIDTS': __Secure_1PSIDTS,
    '__Secure-1PSID': __Secure_1PSID,
    '__Secure-1PAPISID': __Secure_1PAPISID,
}

data = {
    'context': {
        'client': {
            'clientName': 'WEB',
            'clientVersion': '2.20240325.01.00',
        }
    },
    'browseId': 'UCQxJsAlqmBPAbR_0syDi9mg',
    'params': params,
}

data = requests.post(url, headers = headers, cookies = cookies, json = data).json()
print('voteRatioIfSelected' in str(data))

Related to #251.

@Benjamin-Loison Benjamin-Loison added enhancement New feature or request medium priority A high-priority issue noticeable by the user but he can still work around it. quick A task that should take less than two hours to complete. private data Requires authentication to grab such data. Might be treated in `Webscrap_any_website` repository. labels Mar 26, 2024
@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 26, 2024

Python script:
import requests
import json
import blackboxprotobuf
import base64
import hashlib
import time

# Both kept timestamp from actual request and current timestamp work.
currentTime = 1711466739#int(time.time())
SAPISID = 'CENSORED'
__Secure_1PSIDTS = 'sidts-CENSORED'
__Secure_1PSID = 'CENSORED'
__Secure_1PAPISID = 'CENSORED'

SAPISIDHASH = f'{currentTime}_' + hashlib.sha1(f'{currentTime} {SAPISID} https://www.youtube.com'.encode('ascii')).digest().hex()

def getBase64Protobuf(message, typedef):
    data = blackboxprotobuf.encode_message(message, typedef)
    return base64.b64encode(data).decode('ascii')

message = {
    '2': 'community',
    '25': {
        '22': 'UgwSoAm2bGLaJM44UTZ4AaABCQ'
    },
}

typedef = {
    '2': {
        'type': 'string'
    },
    '25': {
        'type': 'message',
        'message_typedef': {
            '22': {
                'type': 'string'
            }
        },
        'field_order': [
            '22'
        ]
    },
}

params = getBase64Protobuf(message, typedef)

url = 'https://www.youtube.com/youtubei/v1/browse'

headers = {
    'Origin': 'https://www.youtube.com',
    'Authorization': f'SAPISIDHASH {SAPISIDHASH}',
}

cookies = {
    '__Secure-1PSIDTS': __Secure_1PSIDTS,
    '__Secure-1PSID': __Secure_1PSID,
    '__Secure-1PAPISID': __Secure_1PAPISID,
}

data = {
    'context': {
        'client': {
            'clientName': 'WEB',
            'clientVersion': '2.20240325.01.00',
        }
    },
    'browseId': 'UCQxJsAlqmBPAbR_0syDi9mg',
    'params': params,
}

data = requests.post(url, headers = headers, cookies = cookies, json = data).json()
print('voteRatioIfSelected' in str(data))

@Benjamin-Loison
Copy link
Owner Author

To add this feature it requires a different method than I usually used. I currently have a quite promising approach but to make that it will work for a long period of time, I will wait one day to make sure that my prototype still works in case there is no temporary authorization process that I am not aware of. The currently working script with both hardcoded and current timestamps work in my VirtualBox Linux Mint (trust) virtual machine in /home/benjamin/Desktop/260324_YouTube-operational-API_issues_258.py.

@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 28, 2024

With both hardcoded and current timestamps it does not work anymore. It seems that only changing SAPISID, __Secure_1PSIDTS, __Secure_1PSID and __Secure_1PAPISID work. Should algorithmitically properly verify that, I restored initial script state.

@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 30, 2024

https://discord.com/channels/933841502155706418/933841503103627316/1216147048219414559

https://discord.com/channels/933841502155706418/933841503103627316/1217157740787663019

I indeed remember having used my personal YouTube account not in an incognito window, so let us try doing so this time and pay attention not keeping the web-browser open too long to have the cookie rotation. Pay attention to actual requests to make sure such one is not performed in this short time frame.

For security and possibly ease the process, I use a not 2FA account:
-----BEGIN PGP MESSAGE-----

hF4DTQa9Wom5MBgSAQdAR4Xg4n8T5rVeQ+dt10iv+FSJ2wTz+3VIVYH7Gszug3Yw
kYrEHPbY/I0zuHKWyWdxhQuAYKNPfWOAhFLH0hh12LLVMi+kPXdXRVt+BQkU84o2
0lgB9jaamfo8DAEf0QQHy3lzA2NJMW3phApIzBZdWdvbvkVMlg7Lj+HseSTZ7rUA
YjXGBW9bN6fr9CtjnO1igdhJqGU4XjtpY6+MwJnS5E1v3UA9+RbD28JZ
=oIZQ
-----END PGP MESSAGE-----

@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 30, 2024

https://blog.lepine.pro/protobuf-standard-pour-echanger-des-donnes-php-go

Both following work:
import requests
import json
import blackboxprotobuf
import base64
import hashlib
import time

# Both kept timestamp from actual request and current timestamp work.
currentTime = 1711824127#int(time.time())
SAPISID = 'CENSORED'
__Secure_3PSID = 'CENSORED'
__Secure_3PAPISID = 'CENSORED'

SAPISIDHASH = f'{currentTime}_' + hashlib.sha1(f'{currentTime} {SAPISID} https://www.youtube.com'.encode('ascii')).digest().hex()

def getBase64Protobuf(message, typedef):
    data = blackboxprotobuf.encode_message(message, typedef)
    return base64.b64encode(data).decode('ascii')

message = {
    '2': 'community',
    '25': {
        '22': 'UgwSoAm2bGLaJM44UTZ4AaABCQ'
    },
}

typedef = {
    '2': {
        'type': 'string'
    },
    '25': {
        'type': 'message',
        'message_typedef': {
            '22': {
                'type': 'string'
            }
        },
        'field_order': [
            '22'
        ]
    },
}

params = getBase64Protobuf(message, typedef)

url = 'https://www.youtube.com/youtubei/v1/browse'

headers = {
    'Origin': 'https://www.youtube.com',
    'Authorization': f'SAPISIDHASH {SAPISIDHASH}',
}

cookies = {
    '__Secure-3PSID': __Secure_3PSID,
    '__Secure-3PAPISID': __Secure_3PAPISID,
}

data = {
    'context': {
        'client': {
            'clientName': 'WEB',
            'clientVersion': '2.20240325.01.00',
        }
    },
    'browseId': 'UCQxJsAlqmBPAbR_0syDi9mg',
    'params': params,
}

data = requests.post(url, headers = headers, cookies = cookies, json = data).json()
print('voteRatioIfSelected' in str(data))

Let us wait an additional 24 hours.

@Benjamin-Loison Benjamin-Loison self-assigned this Mar 31, 2024
@Benjamin-Loison Benjamin-Loison added medium A task that should take less than a day to complete. and removed quick A task that should take less than two hours to complete. labels Mar 31, 2024
@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 31, 2024

Still working after 24 hours, so I am implementing a PHP equivalent to integrate to the API:
<?php

$myArray = [
    '__Secure-3PSID' => '__Secure-3PSID_VALUE',
    '__Secure-3PAPISID' => '__Secure-3PAPISID_VALUE',
];

//$result = array_map(function($k, $v) { return "$k=$v"; }, array_keys($myArray), array_values($myArray));
$result = array_map(fn($k, $v) => "$k=$v", array_keys($myArray), array_values($myArray));
print_r($result);

@Benjamin-Loison
Copy link
Owner Author

Related to #190.

@Benjamin-Loison
Copy link
Owner Author

Screenshot from 2024-03-31 21-34-54

@Benjamin-Loison
Copy link
Owner Author

Benjamin-Loison commented Mar 31, 2024

PHP:
<?php

header('Content-Type: application/json; charset=UTF-8');

require_once __DIR__ . '/vendor/autoload.php';

include_once 'proto/php/Browse.php';
include_once 'proto/php/GPBMetadata/Browse.php';
include_once 'proto/php/SubBrowse.php';
include_once 'proto/php/GPBMetadata/SubBrowse.php';

$channelId = 'UCQxJsAlqmBPAbR_0syDi9mg';
$postId = 'UgwSoAm2bGLaJM44UTZ4AaABCQ';

$currentTime = 1711824127;//time()
$SAPISID = 'CENSORED';
$__Secure_3PSID = 'CENSORED';
$__Secure_3PAPISID = 'CENSORED';
$ORIGIN = 'https://www.youtube.com';
$SAPISIDHASH = "${currentTime}_" . sha1("$currentTime $SAPISID $ORIGIN");

$url = 'https://www.youtube.com/youtubei/v1/browse';

$subBrowse = new \SubBrowse();
$subBrowse->setPostId($postId);

$browse = new \Browse();
$browse->setEndpoint('community');
$browse->setSubBrowse($subBrowse);

$params = base64_encode($browse->serializeToString());

define('MUSIC_VERSION', '2.9999099');

$rawData = [
	'context' => [
		'client' => [
			'clientName' => 'WEB',
			'clientVersion' => MUSIC_VERSION
		]
	],
	'browseId' => $channelId,
	'params' => $params,
];

function implodeArray($anArray, $separator)
{
	return array_map(fn($k, $v) => "${k}${separator}${v}", array_keys($anArray), array_values($anArray));
}

$opts = [
	'http' => [
		'method' => 'POST',
		'header' => implodeArray([
			'Content-Type' => 'application/json',
			'Origin' => $ORIGIN,
			'Authorization' => "SAPISIDHASH $SAPISIDHASH",
			'Cookie' => implode('; ', implodeArray([
				'__Secure-3PSID' => $__Secure_3PSID,
				'__Secure-3PAPISID' => $__Secure_3PAPISID,
            ], '=')),
		], ': '),
		'content' => json_encode($rawData),
	]
];

$context = stream_context_create($opts);

$result = json_decode(file_get_contents($url, false, $context), true);
// TODO: pay attention to tab
$result = $result['contents']['twoColumnBrowseResultsRenderer']['tabs'][5]['tabRenderer']['content']['sectionListRenderer']['contents'][0]['itemSectionRenderer']['contents'][0]['backstagePostThreadRenderer']['post'];
//die('isIn: ' . str_contains($result, 'voteRatioIfSelected'));
die(json_encode($result));

There is a design choice here, to propose a version without Protobuf and a version with Protobuf, or one of both. I choose only a version with Protobuf to make clearer what is going on in the code, despite making hosting our own instance more complex.

Also note that not linking a YouTube account version is wanted.

An interesting aspect is that if provide incorrect credentials, for instance the CENSORED ones, then only voteRatio is not retrievable but otherwise things work as expected.

However the downside is that have to precise the channel id which is potentially unknown to the end-user.

@Benjamin-Loison
Copy link
Owner Author

curl https://www.youtube.com/youtubei/v1/browse -H 'Content-Type: application/json' --data-raw '{"context": {"client": {"clientName": "WEB", "clientVersion": "2.20240327.00.00"}}, "browseId": "UCQxJsAlqmBPAbR_0syDi9mg", "params": "Egljb21tdW5pdHnKAR2yARpVZ3dTb0FtMmJHTGFKTTQ0VVRaNEFhQUJDUeoCBBABGAE%3D"}'

@Benjamin-Loison
Copy link
Owner Author

I just realized that SAPISID is identical to __Secure-3PAPISID.

@Benjamin-Loison
Copy link
Owner Author

https://www.youtube.com/post/UgwSoAm2bGLaJM44UTZ4AaABCQ

minimizeCURL curl.sh 'voteRatioIfSelected'
...
curl https://www.youtube.com/post/UgwSoAm2bGLaJM44UTZ4AaABCQ -H 'Cookie: __Secure-1PSIDTS=sidts-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX; __Secure-1PSID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

@Benjamin-Loison
Copy link
Owner Author

grep -ri 'https://www.youtube.com/post/'

does not return anything.

https://www.youtube.com/channel/UCQxJsAlqmBPAbR_0syDi9mg/community?lb=UgwSoAm2bGLaJM44UTZ4AaABCQ

grep -rwI lb

does not return anything.

@Benjamin-Loison
Copy link
Owner Author

It works currently fine for a Discord user, see https://discord.com/channels/933841502155706418/933841503103627316/1330954213160915015.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request medium priority A high-priority issue noticeable by the user but he can still work around it. medium A task that should take less than a day to complete. private data Requires authentication to grab such data. Might be treated in `Webscrap_any_website` repository.
Projects
None yet
Development

No branches or pull requests

1 participant