diff options
author | garret1317 <garret@airmail.cc> | 2025-07-11 07:41:06 +0100 |
---|---|---|
committer | garret1317 <garret@airmail.cc> | 2025-07-11 07:41:06 +0100 |
commit | 489e31032bb62a690a4d3b8f029259a3e7e051f9 (patch) | |
tree | 1fa965152ed8a2ef7d22537e672e7b1a12140618 | |
parent | fad5614bf07dae50be5850f28e845af7893967dc (diff) | |
download | yt-dlp-rajiko-489e31032bb62a690a4d3b8f029259a3e7e051f9.tar.gz yt-dlp-rajiko-489e31032bb62a690a4d3b8f029259a3e7e051f9.tar.bz2 yt-dlp-rajiko-489e31032bb62a690a4d3b8f029259a3e7e051f9.zip |
add Podcast protobuf test/proof-of-concept
not going to add to the extractor just yet because its really complicated
-rwxr-xr-x | misc/protostuff.py | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/misc/protostuff.py b/misc/protostuff.py new file mode 100755 index 0000000..a461c02 --- /dev/null +++ b/misc/protostuff.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +import protobug +import base64 +import struct + +import random +import requests + +@protobug.message +class SignInRequest: + lsid: protobug.String = protobug.field(2) + area: protobug.String = protobug.field(3) + +@protobug.message +class SignInResponse: + jwt: protobug.String = protobug.field(1) + + + +@protobug.message +class ListPodcastEpisodesRequest: + channel_id: protobug.String = protobug.field(1) + dontknow: protobug.Int32 = protobug.field(2) + page_length: protobug.Int32 = protobug.field(4) + cursor: protobug.String = protobug.field(5, default=None) + +def add_grpc_header(protobuf_data): + compression_flag = 0 + message_length = len(protobuf_data) + header = struct.pack('>BI', compression_flag, message_length) + return header + protobuf_data + +def strip_grpc_response(response): + return response[5:].rpartition(b"grpc-status:")[0] + +print("SIGNUP") +# why do they have to make it so bloody complicated + +lsid = ''.join(random.choices('0123456789abcdef', k=32)) +big_funny = ("\n " + lsid).encode() + +signup = requests.post("https://api.annex.radiko.jp/radiko.UserService/SignUp", headers={ + 'Origin': 'https://radiko.jp', + 'Content-Type': 'application/grpc-web+proto', + 'X-User-Agent': 'grpc-web-javascript/0.1', + 'X-Grpc-Web': '1', + }, data=( add_grpc_header(big_funny)), +) + +print(signup.content) + +# youre meant to only do the sign up ^ once and then keep your id for later +# so that you can V sign in and get the token for the API to work + +print("SIGNIN") + +si=add_grpc_header(protobug.dumps(SignInRequest( + lsid=lsid, + area="JP13", +))) + +print(si) +print(base64.b64encode(si)) + +signin = requests.post("https://api.annex.radiko.jp/radiko.UserService/SignIn", headers={ + 'Origin': 'https://radiko.jp', + 'Content-Type': 'application/grpc-web+proto', + 'X-User-Agent': 'grpc-web-javascript/0.1', + 'X-Grpc-Web': '1', +}, data=si) + +print(signin.content) + +signin_result = protobug.loads(strip_grpc_response(signin.content), SignInResponse) + + +headers = { + 'Origin': 'https://radiko.jp', + 'Authorization': f'Bearer {signin_result.jwt}', + 'x-annex-proto-version': '1.0.0', + 'Content-Type': 'application/grpc-web+proto', + 'X-User-Agent': 'grpc-web-javascript/0.1', + 'X-Grpc-Web': '1', +} + +response = requests.post('https://api.annex.radiko.jp/radiko.PodcastService/ListPodcastEpisodes', headers=headers, + data=add_grpc_header(protobug.dumps(ListPodcastEpisodesRequest( + channel_id="09f27a48-ae04-4ce7-a024-572460e46eb7", + dontknow=1, + page_length=100, # site uses 20 +# cursor="ef693874-0ad2-48cc-8c52-ac4de31cbf54" # here you put the id of the last episode you've seen in the list + ))) +) + +print(response) + +episodes = strip_grpc_response(response.content) + + +#with open("ListPodcastEpisodes.bin", "rb") as f: +# response = strip_grpc(f.read()) + + +@protobug.message +class Audio: + revision: protobug.Int32 = protobug.field(1) + url: protobug.String = protobug.field(2) + fileSize: protobug.Int64 = protobug.field(3) + durationSec: protobug.Int64 = protobug.field(4) + transcoded: protobug.Bool = protobug.field(5) + +@protobug.message +class EpisodeStartAt: + seconds: protobug.UInt64 = protobug.field(1) + nanos: protobug.UInt64 = protobug.field(2, default=0) + + +@protobug.message +class PodcastEpisode: + id: protobug.String = protobug.field(1) + workspaceId: protobug.String = protobug.field(2) + channelId: protobug.String = protobug.field(3) + title: protobug.String = protobug.field(4) + description: protobug.String = protobug.field(5) + audio: Audio = protobug.field(8) + channelImageUrl: protobug.String = protobug.field(16) + channelTitle: protobug.String = protobug.field(17) + channelStationName: protobug.String = protobug.field(18) + channelAuthor: protobug.String = protobug.field(19) + channelThumbnailImageUrl: protobug.String = protobug.field(21) + channelStationType: protobug.UInt32 = protobug.field(22) + startAt: EpisodeStartAt = protobug.field(27) + isEnabled: protobug.Bool = protobug.field(29) + hasTranscription: protobug.Bool = protobug.field(32) + +@protobug.message +class ListPodcastEpisodesResponse: + episodes: list[PodcastEpisode] = protobug.field(1) + hasNextPage: protobug.Bool | None = protobug.field(2, default=None) + + +episodes_response = protobug.loads(episodes, ListPodcastEpisodesResponse) + +for e in episodes_response.episodes: + print(e.title, e.id) |