From 9a607a3906c584ef0a51a6002f1d93645df4c4b7 Mon Sep 17 00:00:00 2001 From: Benjamin Loison <12752145+Benjamin-Loison@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:20:20 +0200 Subject: [PATCH] Add Protobuf dependency and authentication to retrieve community post poll vote details [As requested on Discord](https://discord.com/channels/933841502155706418/933841503103627316/1222103337105883177). --- README.md | 32 +++++++++++++++++++++++- common.php | 4 +++ community.php | 68 +++++++++++++++++++++++++++++++++++++++++++++++---- index.php | 1 + 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3917f11..a54205c 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,37 @@ brew services start httpd git clone https://github.com/Benjamin-Loison/YouTube-operational-API ``` -4. Verify that your API instance is reachable by trying to access: +4. Install Protobuf dependency: + +### On Linux (Ubuntu, Debian and Mint): + +```sh +sudo apt install composer +``` + +### On Windows: + +TODO + +### On MacOS: + +```sh +brew install composer +``` + +In `YouTube-operational-API/` clone folder: + +```sh +composer require google/protobuf +``` + +Generate code of PHP objects from `.proto` prototypes: + +```sh +protoc --php_out=proto/php/ --proto_path=proto/prototypes/ $(find proto/prototypes/ -type f) +``` + +5. Verify that your API instance is reachable by trying to access: - On Linux and Windows: http://localhost/YouTube-operational-API/ - On MacOS: http://localhost:8080/YouTube-operational-API/ diff --git a/common.php b/common.php index c1cfc0f..d74359b 100644 --- a/common.php +++ b/common.php @@ -373,6 +373,7 @@ function getCommunityPostFromContent($content) foreach ($pollRenderer['choices'] as $choice) { $returnedChoice = $choice['text']['runs'][0]; $returnedChoice['image'] = $choice['image']; + $returnedChoice['voteRatio'] = $choice['voteRatioIfNotSelected']; array_push($choices, $returnedChoice); } $totalVotesStr = $pollRenderer['totalVotes']['simpleText']; @@ -394,6 +395,9 @@ function getCommunityPostFromContent($content) $post = [ 'id' => $id, 'channelId' => $channelId, + 'channelName' => $common['authorText']['runs'][0]['text'], + 'channelHandle' => substr($common['authorEndpoint']['browseEndpoint']['canonicalBaseUrl'], 1), + 'channelThumbnails' => $common['authorThumbnail']['thumbnails'], 'date' => $date, 'contentText' => $contentText, 'likes' => $likes, diff --git a/community.php b/community.php index f16e44a..c85371f 100644 --- a/community.php +++ b/community.php @@ -2,6 +2,13 @@ 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'; + include_once 'common.php'; $realOptions = [ @@ -12,7 +19,7 @@ $options[$realOption] = false; } -if (isset($_GET['part'], $_GET['id'])) { +if (isset($_GET['part'], $_GET['id'], $_GET['channelId'])) { $part = $_GET['part']; $parts = explode(',', $part, count($realOptions)); foreach ($parts as $part) { @@ -28,20 +35,71 @@ dieWithJsonMessage('Invalid postId'); } + $channelId = $_GET['channelId']; + if (!isChannelId($channelId)) { + dieWithJsonMessage('Invalid channelId'); + } + $order = isset($_GET['order']) ? $_GET['order'] : 'relevance'; if (!in_array($order, ['relevance', 'time'])) { dieWithJsonMessage('Invalid order'); } - echo getAPI($postId, $order); + echo getAPI($postId, $channelId, $order); } else if(!test()) { dieWithJsonMessage('Required parameters not provided'); } -function getAPI($postId, $order) +function implodeArray($anArray, $separator) +{ + return array_map(fn($k, $v) => "${k}${separator}${v}", array_keys($anArray), array_values($anArray)); +} + +function getAPI($postId, $channelId, $order) { - $result = getJSONFromHTMLForcingLanguage("https://www.youtube.com/post/$postId"); - $contents = getTabs($result)[0]['tabRenderer']['content']['sectionListRenderer']['contents']; + $currentTime = time(); + $SAPISID = 'CENSORED'; + $__Secure_3PSID = 'CENSORED'; + $ORIGIN = 'https://www.youtube.com'; + $SAPISIDHASH = "${currentTime}_" . sha1("$currentTime $SAPISID $ORIGIN"); + + $subBrowse = new \SubBrowse(); + $subBrowse->setPostId($postId); + + $browse = new \Browse(); + $browse->setEndpoint('community'); + $browse->setSubBrowse($subBrowse); + + $params = base64_encode($browse->serializeToString()); + + $rawData = [ + 'context' => [ + 'client' => [ + 'clientName' => 'WEB', + 'clientVersion' => MUSIC_VERSION + ] + ], + 'browseId' => $channelId, + 'params' => $params, + ]; + + $opts = [ + 'http' => [ + 'method' => 'POST', + 'header' => implodeArray([ + 'Content-Type' => 'application/json', + 'Origin' => $ORIGIN, + 'Authorization' => "SAPISIDHASH $SAPISIDHASH", + 'Cookie' => implode('; ', implodeArray([ + '__Secure-3PSID' => $__Secure_3PSID, + '__Secure-3PAPISID' => $SAPISID, + ], '=')), + ], ': '), + 'content' => json_encode($rawData), + ] + ]; + $result = getJSON('https://www.youtube.com/youtubei/v1/browse', $opts); + $contents = getTabByName($result, 'Community')['tabRenderer']['content']['sectionListRenderer']['contents']; $content = $contents[0]['itemSectionRenderer']['contents'][0]; $post = getCommunityPostFromContent($content); $continuationToken = urldecode($contents[1]['itemSectionRenderer']['contents'][0]['continuationItemRenderer']['continuationEndpoint']['continuationCommand']['token']); diff --git a/index.php b/index.php index 6259937..c431d4e 100644 --- a/index.php +++ b/index.php @@ -209,6 +209,7 @@ function feature($feature) 'snippet', ], 'id' => 'POST_ID', + 'channelId' => 'CHANNEL_ID', 'order' => [ 'relevance', 'time',