]>
git.gir.st - subscriptionfeed.git/blob - app/browse/lib.py
3 from datetime
import datetime
, timezone
5 from .. common
. common
import fetch_xml
, parse_xml
6 from .. common
. innertube
import G
8 def fetch_searchresults ( q
= None , sp
= None ):
10 today
= datetime
. now ( timezone
. utc
). strftime ( "%Y%m %d " )
11 r
= requests
. get ( f
"https://www.youtube.com/results" , {
13 'pbj' : 1 , # makes youtube return a json-response
17 'x-youtube-client-name' : '1' ,
18 'x-youtube-client-version' : f
'2. {today} .01.01' , # the version is parsed as a date, and if it's invalid (e.g. month>12 or even feb>=30), youtube throws an encrypted stacktrace :D (but any random date >= 20160323 as of 20200802 works (even year 3000)
23 # Sometimes, youtube throws an exception after the response already begun.
24 # This can manifest in two ways:
25 # 1) So the status code is 200, begins with JSON and switches to HTML half
26 # way through. WTF?! (This should be "fixed" by retrying, though)
27 # 2) The response just stopping mid-way through like this: response.text ==
28 # '[\r\n{"page": "search","rootVe": "4724"},\r\n{"page": "search",'
29 # hence, just try-catching the decoding step is the easiest way out.
33 continue # will return None once we break out of the loop
35 def fetch_ajax ( params
):
37 fetch data using a continuation protobuf
39 # TODO: handle auto_generated!
40 today
= datetime
. now ( timezone
. utc
). strftime ( "%Y%m %d " )
42 # TODO: this is not cached any more! -> https://github.com/reclosedev/requests-cache/issues/154
43 # Note: this 'innertube' API key exists since at least 2015: https://stackoverflow.com/q/33511165
44 r
= requests
. post ( f
"https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" , json
={
45 'continuation' : params
,
46 'context' : { 'client' : {
50 'clientVersion' : f
'2. {today} .01.01' ,
59 def canonicalize_channel ( name
):
60 if re
. fullmatch ( r
"(UC[A-Za-z0-9_-] {22} )" , name
):
63 # try /user/ (legacy URLs):
64 xmlfeed
= fetch_xml ( "user" , name
)
66 _
, _
, _
, channel_id
, _
= parse_xml ( xmlfeed
)
69 # get UCID of /c/ (vanity URLs):
70 today
= datetime
. now ( timezone
. utc
). strftime ( "%Y%m %d " )
71 r
= requests
. get ( f
'https://www.youtube.com/c/ {name} /about?pbj=1&hl=en_US' , headers
={
72 'x-youtube-client-name' : '1' ,
73 'x-youtube-client-version' : f
'2. {today} .01.01' , # see fetch_searchresults()
76 return r
. json ()[ 1 ][ 'response' ][ 'metadata' ][ 'channelMetadataRenderer' ][ 'rssUrl' ]. split ( "=" )[ 1 ]
83 def find_and_parse_error ( result
):
85 result|
G ( 'responseContext' ) |
G ( 'errors' ) |
G ( 'error' ) |
G ( 0 )
86 or result|
G ( 'alerts' ) |
G ( 0 ) |
G ( 'alertRenderer' )
92 error_type
= error_obj|
G ( 'code' , 'type' , 'status' ) or 'Error'
94 error_obj|
G ( 'debugInfo' , 'externalErrorMessage' )
95 or error_obj|
G ( 'text' ) |G
. text
96 or error_obj|
G ( 'message' )
99 return f
" {error_type} : {error.rstrip('.')}"