]> git.gir.st - subscriptionfeed.git/blob - app/dangerous/protobuf.py
implement channel and playlist endpoints using innertube
[subscriptionfeed.git] / app / dangerous / protobuf.py
1 import base64
2 from dataclasses import dataclass
3 from typing import Optional
4
5 from pure_protobuf.dataclasses_ import field, message
6 from pure_protobuf.types import int64
7
8 @message
9 @dataclass
10 class Filters: # adapted from invidious
11 date: Optional[int64] = field(1, default=None)
12 type: Optional[int64] = field(2, default=None)
13 length: Optional[int64] = field(3, default=None)
14 is_hd: Optional[bool] = field(4, default=None)
15 subtitles: Optional[bool] = field(5, default=None)
16 ccommons: Optional[bool] = field(6, default=None)
17 is_3d: Optional[bool] = field(7, default=None)
18 live: Optional[bool] = field(8, default=None)
19 purchased: Optional[bool] = field(9, default=None)
20 is_4k: Optional[bool] = field(14, default=None)
21 is_360: Optional[bool] = field(15, default=None)
22 location: Optional[bool] = field(23, default=None)
23 is_hdr: Optional[bool] = field(25, default=None)
24 @message
25 @dataclass
26 class SearchRequest:
27 sorted: Optional[int64] = field(1, default=None)
28 filter: Optional[Filters] = field(2, default=None)
29
30 def b64e(b, padding=True):
31 return base64.urlsafe_b64encode(b).decode('ascii') \
32 .replace("=", "%3D" if padding else "")
33
34 def make_sp(sort=None, date=None, type=None, len=None, features=[]):
35 sortorder = dict(relevance=0, rating=1, date=2, views=3)
36 datefilter = dict(hour=1, day=2, week=3, month=4, year=5)
37 typefilter = dict(video=1, channel=2, playlist=3, movie=4, show=5)
38 lenfilter = dict(short=1, long=2)
39
40 return b64e(SearchRequest(
41 sorted=sortorder.get(sort),
42 filter=Filters(
43 date=datefilter.get(date),
44 type=typefilter.get(type),
45 length=lenfilter.get(len),
46 **{f:True for f in features},
47 ) if date or type or len or features else None
48 ).dumps())
49
50 @message
51 @dataclass
52 class PlaylistData:
53 offset: int64 = field(1)
54 @message
55 @dataclass
56 class PlaylistSubparams:
57 data: str = field(15)
58 @message
59 @dataclass
60 class Subparams:
61 type_s: str = field(2)
62 type_i: int64 = field(4)
63 page: Optional[str] = field(15)
64 sort: Optional[int64] = field(3, default=None)
65 unknown_const1: int64 = field(7, default=1)
66 unknown_const2: int64 = field(23, default=0)
67 list_or_grid: Optional[int64] = field(6,default=2) # 2=list, None/1=grid
68 # usually returns gridResponses. to switch to listResponses:
69 #list_or_grid:int64 = field(6,default=2) # 2==list, undef=grid (playlist continuations require list)
70 #field12:int64 = field(12,default=1)
71 #field13:str = field(13,default="") # playlists in list mode don't work without this
72 @message
73 @dataclass
74 class Params:
75 subject: str = field(2) # ucid/plid
76 params: str = field(3) # b64e encoded
77 @message
78 @dataclass
79 class Continuation:
80 params: Params = field(80226972)
81 def make_channel_params(subject, typ="videos", page=1, sort=None):
82 typestr = dict(videos="videos", playlists="playlists")
83 typeint = dict(videos=0, playlists=1) # not supporting autogen'd
84 sortorder = dict(newest=None, popular=1, oldest=2)
85 if typ == "playlists":
86 sortorder = dict(oldest=2, newest=3, modified=4)
87
88 return b64e(Continuation(
89 params=Params(
90 subject=subject,
91 params=b64e(Subparams(
92 type_s=typestr.get(typ),
93 type_i=typeint.get(typ),
94 sort=sortorder.get(sort),
95 page=str(page) if page else None # Note: ucid/playlists doesn't support pagination
96 ).dumps()),
97 ),
98 ).dumps())
99
100 def make_playlist_params(playlist_id, offset):
101 if playlist_id.startswith("UC"):
102 playlist_id = f"UU{playlist_id[2:]}"
103
104 return b64e(Continuation(
105 params=Params(
106 subject="VL" + playlist_id,
107 params=b64e(PlaylistSubparams(
108 data="PT:" + b64e(PlaylistData(
109 offset=offset,
110 ).dumps(), padding=False)
111 ).dumps()),
112 ),
113 ).dumps())
Imprint / Impressum