]> git.gir.st - subscriptionfeed.git/blob - app/browse/protobuf.py
protobuf: replace make_playlist_params with new library
[subscriptionfeed.git] / app / browse / protobuf.py
1 import base64
2 from . import pyproto
3
4 from dataclasses import dataclass
5 from typing import Optional
6
7 from pure_protobuf.dataclasses_ import field, message
8 from pure_protobuf.types import int64
9
10 def b64e(b, padding=True):
11 return base64.urlsafe_b64encode(b).decode('ascii') \
12 .replace("=", "%3D" if padding else "")
13
14 def proto(d, padding=False):
15 return base64.urlsafe_b64encode(pyproto.ProtoBuf(d).toBuf()) \
16 .decode('ascii').replace("=", "%3D" if padding else "")
17
18 def continuation(subject, params, query=None):
19 return proto({
20 80226972: {
21 2: subject,
22 3: proto(params, padding=True),
23 11: query,
24 }
25 }, padding=True)
26
27 # SEARCH {{{
28 @message
29 @dataclass
30 class Extras:
31 verbatim: Optional[bool] = field(1, default=None) # don't fix spelling
32 @message
33 @dataclass
34 class Filters: # adapted from invidious
35 date: Optional[int64] = field(1, default=None)
36 type: Optional[int64] = field(2, default=None)
37 length: Optional[int64] = field(3, default=None)
38 is_hd: Optional[bool] = field(4, default=None)
39 subtitles: Optional[bool] = field(5, default=None)
40 ccommons: Optional[bool] = field(6, default=None)
41 is_3d: Optional[bool] = field(7, default=None)
42 live: Optional[bool] = field(8, default=None)
43 purchased: Optional[bool] = field(9, default=None)
44 is_4k: Optional[bool] = field(14, default=None)
45 is_360: Optional[bool] = field(15, default=None)
46 location: Optional[bool] = field(23, default=None)
47 is_hdr: Optional[bool] = field(25, default=None)
48 @message
49 @dataclass
50 class SearchRequest:
51 sorted: Optional[int64] = field(1, default=None)
52 filter: Optional[Filters] = field(2, default=None)
53 extras: Optional[Extras] = field(8, default=None)
54
55 def make_sp(sort=None, date=None, type=None, len=None, features=[], extras=[]):
56 sortorder = dict(relevance=0, rating=1, date=2, views=3)
57 datefilter = dict(hour=1, day=2, week=3, month=4, year=5)
58 typefilter = dict(video=1, channel=2, playlist=3, movie=4, show=5)
59 lenfilter = dict(short=1, long=2)
60
61 return b64e(SearchRequest(
62 sorted=sortorder.get(sort),
63 filter=Filters(
64 date=datefilter.get(date),
65 type=typefilter.get(type),
66 length=lenfilter.get(len),
67 **{f:True for f in features},
68 ) if date or type or len or features else None,
69 extras=Extras(**{f:True for f in extras}),
70 ).dumps())
71 # }}} SEARCH
72
73 # CHANNEL {{{
74 def make_channel_params(subject, typ="videos", sort=None, query=None):
75 if typ in ("playlists",):
76 sortorder = dict(newest=3, modified=4)
77 return continuation(subject, {
78 2: "playlists", #type_s
79 4: 1, #type_i
80 3: sortorder.get(sort),#sort optional
81 7: 1, #unknown_const1
82 23: 0, #unknown_const2
83 6: 2, #list_or_grid (2=list, None/1=grid)
84 61: proto({1: proto({1: 0})}) #offset
85 })
86 elif typ in ("search",):
87 return continuation(subject, {
88 2: "search",#type_s
89 7: 1, #unknown_const1
90 23: 0, #unknown_const2
91 6: 2, #list_or_grid (2=list, None/1=grid)
92 }, query)
93 elif typ in ("videos", "streams", "shorts"):
94 sortorder = dict(newest=1, popular=2)
95 typekey = dict(videos=15, streams=14, shorts=10)
96 return continuation(subject, { 110: { 3: {
97 typekey[typ]: {
98 2: { 1: "00000000-0000-0000-0000-000000000000" },
99 3: sortorder.get(sort,1)
100 }
101 }}})
102 else:
103 raise NotImplementedError
104 # }}} CHANNEL
105
106 # PLAYLIST {{{
107 def make_playlist_params(playlist_id, offset):
108 # mix takes video id instead of offset. an id not in the playlist requests the beginning.
109 mix_playlist = playlist_id.startswith("RD")
110 return continuation("VL" + playlist_id, {
111 15: "PT:" + proto(
112 { 2: '___________' } if mix_playlist else { 1: offset }
113 )
114 })
115 # }}} PLAYLIST
Imprint / Impressum