From 75e8930958ea305a26a4652ef88639f7ad5db356 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 19 Oct 2020 12:55:03 -0700 Subject: yt_data_extract: normalize thumbnail and author urls for instance, urls that start with // become https:// adjustment required in comments.py because the url was left as a relative url in yt_data_extract by mistake and was using URL_ORIGIN prefix as fix. see #31 --- youtube/comments.py | 2 +- youtube/yt_data_extract/common.py | 17 +++++++++++------ youtube/yt_data_extract/everything_else.py | 12 ++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) (limited to 'youtube') diff --git a/youtube/comments.py b/youtube/comments.py index a7a93cc..b3f1a90 100644 --- a/youtube/comments.py +++ b/youtube/comments.py @@ -90,7 +90,7 @@ def single_comment_ctoken(video_id, comment_id): def post_process_comments_info(comments_info): for comment in comments_info['comments']: comment['author_url'] = concat_or_none( - util.URL_ORIGIN, comment['author_url']) + '/', comment['author_url']) comment['author_avatar'] = concat_or_none( settings.img_prefix, comment['author_avatar']) diff --git a/youtube/yt_data_extract/common.py b/youtube/yt_data_extract/common.py index 2d3b637..9610479 100644 --- a/youtube/yt_data_extract/common.py +++ b/youtube/yt_data_extract/common.py @@ -90,15 +90,20 @@ def remove_redirect(url): return urllib.parse.parse_qs(query_string)['q'][0] return url -youtube_url_re = re.compile(r'^(?:(?:(?:https?:)?//)?(?:www\.)?youtube\.com)?(/.*)$') +norm_url_re = re.compile(r'^(?:(?:https?:)?//)?((?:[\w-]+\.)+[\w-]+)?(/.*)$') def normalize_url(url): + '''Insert https, resolve relative paths for youtube.com, and put www. infront of youtube.com''' if url is None: return None - match = youtube_url_re.fullmatch(url) + match = norm_url_re.fullmatch(url) if match is None: - raise Exception() + raise Exception(url) - return 'https://www.youtube.com' + match.group(1) + domain = match.group(1) or 'www.youtube.com' + if domain == 'youtube.com': + domain = 'www.youtube.com' + + return 'https://' + domain + match.group(2) def _recover_urls(runs): for run in runs: @@ -240,11 +245,11 @@ def extract_item_info(item, additional_info={}): )) info['author_url'] = ('https://www.youtube.com/channel/' + info['author_id']) if info['author_id'] else None info['description'] = extract_formatted_text(multi_get(item, 'descriptionSnippet', 'descriptionText')) - info['thumbnail'] = multi_deep_get(item, + info['thumbnail'] = normalize_url(multi_deep_get(item, ['thumbnail', 'thumbnails', 0, 'url'], # videos ['thumbnails', 0, 'thumbnails', 0, 'url'], # playlists ['thumbnailRenderer', 'showCustomThumbnailRenderer', 'thumbnail', 'thumbnails', 0, 'url'], # shows - ) + )) info['badges'] = [] for badge_node in multi_get(item, 'badges', 'ownerBadges', default=()): diff --git a/youtube/yt_data_extract/everything_else.py b/youtube/yt_data_extract/everything_else.py index 5bb8709..d91dad5 100644 --- a/youtube/yt_data_extract/everything_else.py +++ b/youtube/yt_data_extract/everything_else.py @@ -49,10 +49,10 @@ def extract_channel_info(polymer_json, tab): if info['short_description'] and len(info['short_description']) > 730: info['short_description'] = info['short_description'][0:730] + '...' info['channel_name'] = metadata.get('title') - info['avatar'] = multi_deep_get(metadata, + info['avatar'] = normalize_url(multi_deep_get(metadata, ['avatar', 'thumbnails', 0, 'url'], ['thumbnail', 'thumbnails', 0, 'url'], - ) + )) channel_url = multi_get(metadata, 'urlCanonical', 'channelUrl') if channel_url: channel_id = get(channel_url.rstrip('/').split('/'), -1) @@ -263,13 +263,13 @@ def extract_comments_info(polymer_json): # These 3 are sometimes absent, likely because the channel was deleted comment_info['author'] = extract_str(comment_renderer.get('authorText')) - comment_info['author_url'] = deep_get(comment_renderer, - 'authorEndpoint', 'commandMetadata', 'webCommandMetadata', 'url') + comment_info['author_url'] = normalize_url(deep_get(comment_renderer, + 'authorEndpoint', 'commandMetadata', 'webCommandMetadata', 'url')) comment_info['author_id'] = deep_get(comment_renderer, 'authorEndpoint', 'browseEndpoint', 'browseId') - comment_info['author_avatar'] = deep_get(comment_renderer, - 'authorThumbnail', 'thumbnails', 0, 'url') + comment_info['author_avatar'] = normalize_url(deep_get( + comment_renderer, 'authorThumbnail', 'thumbnails', 0, 'url')) comment_info['id'] = comment_renderer.get('commentId') comment_info['text'] = extract_formatted_text(comment_renderer.get('contentText')) comment_info['time_published'] = extract_str(comment_renderer.get('publishedTimeText')) -- cgit v1.2.3 From b35afb7cf6c7640380c650ca60c8150bb743eb4b Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 19 Oct 2020 13:31:32 -0700 Subject: Add 'self' directive for img to CSP when proxy_images is off The default directive has self, but the img directive overrides that completely. Need this for local image requests such as subscriptions closes #31 --- youtube/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'youtube') diff --git a/youtube/templates/base.html b/youtube/templates/base.html index f325f86..379419c 100644 --- a/youtube/templates/base.html +++ b/youtube/templates/base.html @@ -4,7 +4,7 @@ {{ page_title }} + {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"> -- cgit v1.2.3 From c9d0f685a43d95d653db56a00efe520e3a04d0d2 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 19 Oct 2020 13:53:57 -0700 Subject: Use get_video_info to get video urls if player response missing Fixes failure mode 1 in #22 --- youtube/watch.py | 12 ++++++++---- youtube/yt_data_extract/watch_extraction.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'youtube') diff --git a/youtube/watch.py b/youtube/watch.py index 1a9e6c4..b1d4665 100644 --- a/youtube/watch.py +++ b/youtube/watch.py @@ -226,15 +226,19 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None): return {'error': 'Failed to parse json response'} info = yt_data_extract.extract_watch_info(polymer_json) - # age restriction bypass - if info['age_restricted']: - print('Fetching age restriction bypass page') + # request player if it's missing + # see https://github.com/user234683/youtube-local/issues/22#issuecomment-706395160 + if info['age_restricted'] or info['player_response_missing']: + if info['age_restricted']: + print('Age restricted video. Fetching get_video_info page') + else: + print('Missing player. Fetching get_video_info page') data = { 'video_id': video_id, 'eurl': 'https://youtube.googleapis.com/v/' + video_id, } url = 'https://www.youtube.com/get_video_info?' + urllib.parse.urlencode(data) - video_info_page = util.fetch_url(url, debug_name='get_video_info', report_text='Fetched age restriction bypass page').decode('utf-8') + video_info_page = util.fetch_url(url, debug_name='get_video_info', report_text='Fetched get_video_info page').decode('utf-8') yt_data_extract.update_with_age_restricted_info(info, video_info_page) # signature decryption diff --git a/youtube/yt_data_extract/watch_extraction.py b/youtube/yt_data_extract/watch_extraction.py index 340a367..f89cec1 100644 --- a/youtube/yt_data_extract/watch_extraction.py +++ b/youtube/yt_data_extract/watch_extraction.py @@ -447,7 +447,8 @@ def _extract_playability_error(info, player_response, error_prefix=''): SUBTITLE_FORMATS = ('srv1', 'srv2', 'srv3', 'ttml', 'vtt') def extract_watch_info(polymer_json): - info = {'playability_error': None, 'error': None} + info = {'playability_error': None, 'error': None, + 'player_response_missing': None} if isinstance(polymer_json, dict): top_level = polymer_json @@ -477,6 +478,10 @@ def extract_watch_info(polymer_json): else: embedded_player_response = {} + # see https://github.com/user234683/youtube-local/issues/22#issuecomment-706395160 + info['player_response_missing'] = not ( + player_response or embedded_player_response) + # captions info['automatic_caption_languages'] = [] info['manual_caption_languages'] = [] @@ -580,7 +585,8 @@ def get_caption_url(info, language, format, automatic=False, translation_languag return url def update_with_age_restricted_info(info, video_info_page): - ERROR_PREFIX = 'Error bypassing age-restriction: ' + '''Inserts urls from 'player_response' in get_video_info page''' + ERROR_PREFIX = 'Error getting missing player or bypassing age-restriction: ' video_info = urllib.parse.parse_qs(video_info_page) player_response = deep_get(video_info, 'player_response', 0) -- cgit v1.2.3 From 125ddaa8da8dad8f3e8eeb54f79a775b865c58bf Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 20 Oct 2020 15:38:00 -0700 Subject: Add setting to change font. Change default to arial Closes #33 --- youtube/__init__.py | 17 +++ youtube/static/shared.css | 336 ------------------------------------------- youtube/templates/base.html | 2 +- youtube/templates/shared.css | 336 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+), 337 deletions(-) delete mode 100644 youtube/static/shared.css create mode 100644 youtube/templates/shared.css (limited to 'youtube') diff --git a/youtube/__init__.py b/youtube/__init__.py index 3c271f1..70ed5b2 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -66,3 +66,20 @@ def error_page(e): error_message += ' Exit node IP address: ' + exc_info()[1].ip return flask.render_template('error.html', error_message=error_message), 502 return flask.render_template('error.html', traceback=traceback.format_exc()), 500 + +font_choices = { + 0: 'initial', + 1: 'arial, "liberation sans", sans-serif', + 2: '"liberation serif", "times new roman", calibri, carlito, serif', + 3: 'verdana, sans-serif', + 4: 'tahoma, sans-serif', +} + +@yt_app.route('/shared.css') +def get_css(): + return flask.Response( + flask.render_template('shared.css', + font_family = font_choices[settings.font] + ), + mimetype='text/css', + ) diff --git a/youtube/static/shared.css b/youtube/static/shared.css deleted file mode 100644 index 7dd16e2..0000000 --- a/youtube/static/shared.css +++ /dev/null @@ -1,336 +0,0 @@ -* { - box-sizing: border-box; -} - -h1, h2, h3, h4, h5, h6, div, button{ - margin:0; - padding:0; -} - -address{ - font-style:normal; -} - -html{ - font-family: "liberation serif", "times new roman", calibri, carlito, serif; -} - -body{ - margin:0; - padding: 0; - color:var(--text-color); - - - background-color:var(--background-color); - - min-height:100vh; - display: flex; - flex-direction: column; -} - - header{ - background-color:#333333; - height: 50px; - - display: flex; - justify-content: center; - } - - #home-link{ - align-self: center; - margin-left:10px; - color: #ffffff; - } - - - #site-search{ - max-width: 600px; - margin-left:10px; - display: flex; - flex-grow: 1; - } - - #site-search .search-box{ - align-self:center; - height:25px; - border:0; - - flex-grow: 1; - } - #site-search .search-button{ - align-self:center; - height:25px; - - border-style:solid; - border-width:1px; - } - #site-search .dropdown{ - margin-left:5px; - align-self:center; - height:25px; - } - #site-search .dropdown button{ - align-self:center; - height:25px; - - border-style:solid; - border-width:1px; - } - #site-search .css-sucks{ - width:0px; - height:0px; - } - #site-search .dropdown-content{ - grid-template-columns: auto auto; - white-space: nowrap; - } - #site-search .dropdown-content h3{ - grid-column:1 / span 2; - } - - #playlist-edit{ - margin-left: 10px; - align-self: center; - } - #local-playlists{ - margin-right:5px; - color: #ffffff; - } - #playlist-name-selection{ - } - #playlist-add-button{ - padding-left: 10px; - padding-right: 10px; - } - #item-selection-reset{ - padding-left: 10px; - padding-right: 10px; - } - - main{ - flex-grow: 1; - padding-bottom: 20px; - } - #message-box{ - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border-style: outset; - padding: 20px; - background-color: var(--interface-color); - opacity: 0; - transition-property: opacity; - transition-duration: 0.3s; - } - - -.dropdown{ - z-index:1; -} - .dropdown-content{ - display:none; - background-color: var(--interface-color); - } - .dropdown:hover .dropdown-content{ - /* For some reason, if this is just grid, it will insist on being 0px wide just like its 0px by 0px parent */ - /* making it inline-grid happened to fix it */ - display:inline-grid; - } - -.item-list{ - display: grid; - grid-row-gap: 10px; - -} - - -.item-grid{ - display: flex; - flex-wrap: wrap; -} - .item-grid > .playlist-item-box{ - margin-right: 10px; - } - .item-grid > * { - margin-bottom: 10px; - } - .item-grid .horizontal-item-box .item{ - width:370px; - } - .item-grid .vertical-item-box .item{ - } - -.item-box{ - display: inline-flex; - flex-direction: row; - /* prevent overflow due to long titles with no spaces: - https://stackoverflow.com/a/43312314 */ - min-width: 0; -} -.vertical-item-box{ -} -.horizontal-item-box{ -} - .item{ - background-color:var(--interface-color); - text-decoration:none; - font-size: 0.8125rem; - color: #767676; - } - - .horizontal-item-box .item { - flex-grow: 1; - display: grid; - align-content: start; - grid-template-columns: auto 1fr; - grid-template-rows: auto auto auto auto 1fr; - /* prevent overflow due to long titles with no spaces: - https://stackoverflow.com/a/43312314 */ - min-width: 0; - } - .vertical-item-box .item{ - width: 168px; - } - .thumbnail-box{ - font-size: 0px; /* prevent newlines and blank space from creating gaps */ - position: relative; - display: block; - } - .horizontal-item-box .thumbnail-box{ - grid-row: 1 / span 5; - margin-right: 4px; - } - .no-description .thumbnail-box{ - width: 168px; - height:94px; - } - .has-description .thumbnail-box{ - width: 246px; - height:138px; - } - .video-item .thumbnail-info{ - position: absolute; - bottom: 2px; - right: 2px; - opacity: .8; - color: #ffffff; - font-size: 0.8125rem; - background-color: #000000; - } - .playlist-item .thumbnail-info{ - position: absolute; - right: 0px; - bottom: 0px; - height: 100%; - width: 50%; - text-align:center; - white-space: pre-line; - opacity: .8; - color: #cfcfcf; - font-size: 0.8125rem; - background-color: #000000; - } - .playlist-item .thumbnail-info span{ /* trick to vertically center the text */ - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - } - .thumbnail-img{ /* center it */ - margin: auto; - display: block; - max-height: 100%; - max-width: 100%; - } - .horizontal-item-box .thumbnail-img{ - height: 100%; - } - - .item .title{ - min-width: 0; - line-height:1.25em; - max-height:3.75em; - overflow-y: hidden; - overflow-wrap: break-word; - - color: var(--text-color); - font-size: 1rem; - font-weight: 500; - text-decoration:initial; - } - - .stats{ - list-style: none; - padding: 0px; - margin: 0px; - } - .horizontal-stats{ - max-height:2.4em; - overflow:hidden; - } - .horizontal-stats > li{ - display: inline; - } - - .horizontal-stats > li::after{ - content: " | "; - } - .horizontal-stats > li:last-child::after{ - content: ""; - } - - .vertical-stats{ - display: flex; - flex-direction: column; - } - .stats address{ - display: inline; - } - .vertical-stats li{ - max-height: 1.3em; - overflow: hidden; - } - - .item-checkbox{ - justify-self:start; - align-self:center; - height:30px; - width:30px; - min-width:30px; - margin: 0px; - } - - -.page-button-row{ - margin-top: 10px; - margin-bottom: 10px; - justify-self:center; - justify-content: center; - display: grid; - grid-auto-columns: 40px; - grid-auto-flow: column; - height: 40px; -} -.next-previous-button-row{ - margin: 10px 0px; - display: flex; - justify-self:center; - justify-content: center; - height: 40px; -} - .page-button{ - background-color: var(--interface-color); - border-style: outset; - border-width: 2px; - font-weight: bold; - text-align: center; - padding: 5px; - } - .next-page:nth-child(2){ /* only if there's also a previous page button */ - margin-left: 10px; - } -.sort-button{ - background-color: var(--interface-color); - padding: 2px; - justify-self: start; -} diff --git a/youtube/templates/base.html b/youtube/templates/base.html index 379419c..3d9f1e9 100644 --- a/youtube/templates/base.html +++ b/youtube/templates/base.html @@ -6,7 +6,7 @@ - + diff --git a/youtube/templates/shared.css b/youtube/templates/shared.css new file mode 100644 index 0000000..141465a --- /dev/null +++ b/youtube/templates/shared.css @@ -0,0 +1,336 @@ +* { + box-sizing: border-box; +} + +h1, h2, h3, h4, h5, h6, div, button{ + margin:0; + padding:0; +} + +address{ + font-style:normal; +} + +html{ + font-family: {{ font_family }}; +} + +body{ + margin:0; + padding: 0; + color:var(--text-color); + + + background-color:var(--background-color); + + min-height:100vh; + display: flex; + flex-direction: column; +} + + header{ + background-color:#333333; + height: 50px; + + display: flex; + justify-content: center; + } + + #home-link{ + align-self: center; + margin-left:10px; + color: #ffffff; + } + + + #site-search{ + max-width: 600px; + margin-left:10px; + display: flex; + flex-grow: 1; + } + + #site-search .search-box{ + align-self:center; + height:25px; + border:0; + + flex-grow: 1; + } + #site-search .search-button{ + align-self:center; + height:25px; + + border-style:solid; + border-width:1px; + } + #site-search .dropdown{ + margin-left:5px; + align-self:center; + height:25px; + } + #site-search .dropdown button{ + align-self:center; + height:25px; + + border-style:solid; + border-width:1px; + } + #site-search .css-sucks{ + width:0px; + height:0px; + } + #site-search .dropdown-content{ + grid-template-columns: auto auto; + white-space: nowrap; + } + #site-search .dropdown-content h3{ + grid-column:1 / span 2; + } + + #playlist-edit{ + margin-left: 10px; + align-self: center; + } + #local-playlists{ + margin-right:5px; + color: #ffffff; + } + #playlist-name-selection{ + } + #playlist-add-button{ + padding-left: 10px; + padding-right: 10px; + } + #item-selection-reset{ + padding-left: 10px; + padding-right: 10px; + } + + main{ + flex-grow: 1; + padding-bottom: 20px; + } + #message-box{ + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-style: outset; + padding: 20px; + background-color: var(--interface-color); + opacity: 0; + transition-property: opacity; + transition-duration: 0.3s; + } + + +.dropdown{ + z-index:1; +} + .dropdown-content{ + display:none; + background-color: var(--interface-color); + } + .dropdown:hover .dropdown-content{ + /* For some reason, if this is just grid, it will insist on being 0px wide just like its 0px by 0px parent */ + /* making it inline-grid happened to fix it */ + display:inline-grid; + } + +.item-list{ + display: grid; + grid-row-gap: 10px; + +} + + +.item-grid{ + display: flex; + flex-wrap: wrap; +} + .item-grid > .playlist-item-box{ + margin-right: 10px; + } + .item-grid > * { + margin-bottom: 10px; + } + .item-grid .horizontal-item-box .item{ + width:370px; + } + .item-grid .vertical-item-box .item{ + } + +.item-box{ + display: inline-flex; + flex-direction: row; + /* prevent overflow due to long titles with no spaces: + https://stackoverflow.com/a/43312314 */ + min-width: 0; +} +.vertical-item-box{ +} +.horizontal-item-box{ +} + .item{ + background-color:var(--interface-color); + text-decoration:none; + font-size: 0.8125rem; + color: #767676; + } + + .horizontal-item-box .item { + flex-grow: 1; + display: grid; + align-content: start; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto auto auto 1fr; + /* prevent overflow due to long titles with no spaces: + https://stackoverflow.com/a/43312314 */ + min-width: 0; + } + .vertical-item-box .item{ + width: 168px; + } + .thumbnail-box{ + font-size: 0px; /* prevent newlines and blank space from creating gaps */ + position: relative; + display: block; + } + .horizontal-item-box .thumbnail-box{ + grid-row: 1 / span 5; + margin-right: 4px; + } + .no-description .thumbnail-box{ + width: 168px; + height:94px; + } + .has-description .thumbnail-box{ + width: 246px; + height:138px; + } + .video-item .thumbnail-info{ + position: absolute; + bottom: 2px; + right: 2px; + opacity: .8; + color: #ffffff; + font-size: 0.8125rem; + background-color: #000000; + } + .playlist-item .thumbnail-info{ + position: absolute; + right: 0px; + bottom: 0px; + height: 100%; + width: 50%; + text-align:center; + white-space: pre-line; + opacity: .8; + color: #cfcfcf; + font-size: 0.8125rem; + background-color: #000000; + } + .playlist-item .thumbnail-info span{ /* trick to vertically center the text */ + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + } + .thumbnail-img{ /* center it */ + margin: auto; + display: block; + max-height: 100%; + max-width: 100%; + } + .horizontal-item-box .thumbnail-img{ + height: 100%; + } + + .item .title{ + min-width: 0; + line-height:1.25em; + max-height:3.75em; + overflow-y: hidden; + overflow-wrap: break-word; + + color: var(--text-color); + font-size: 1rem; + font-weight: 500; + text-decoration:initial; + } + + .stats{ + list-style: none; + padding: 0px; + margin: 0px; + } + .horizontal-stats{ + max-height:2.4em; + overflow:hidden; + } + .horizontal-stats > li{ + display: inline; + } + + .horizontal-stats > li::after{ + content: " | "; + } + .horizontal-stats > li:last-child::after{ + content: ""; + } + + .vertical-stats{ + display: flex; + flex-direction: column; + } + .stats address{ + display: inline; + } + .vertical-stats li{ + max-height: 1.3em; + overflow: hidden; + } + + .item-checkbox{ + justify-self:start; + align-self:center; + height:30px; + width:30px; + min-width:30px; + margin: 0px; + } + + +.page-button-row{ + margin-top: 10px; + margin-bottom: 10px; + justify-self:center; + justify-content: center; + display: grid; + grid-auto-columns: 40px; + grid-auto-flow: column; + height: 40px; +} +.next-previous-button-row{ + margin: 10px 0px; + display: flex; + justify-self:center; + justify-content: center; + height: 40px; +} + .page-button{ + background-color: var(--interface-color); + border-style: outset; + border-width: 2px; + font-weight: bold; + text-align: center; + padding: 5px; + } + .next-page:nth-child(2){ /* only if there's also a previous page button */ + margin-left: 10px; + } +.sort-button{ + background-color: var(--interface-color); + padding: 2px; + justify-self: start; +} -- cgit v1.2.3 From f100685d64f5e93f1ec2a58379a986beb012b482 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 20 Oct 2020 17:19:45 -0700 Subject: Move comment.js reply button styling to comments.css So that the style will also be present on the /comments pages --- youtube/static/comments.css | 12 ++++++++++++ youtube/templates/watch.html | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'youtube') diff --git a/youtube/static/comments.css b/youtube/static/comments.css index 65e2cbe..1c21e73 100644 --- a/youtube/static/comments.css +++ b/youtube/static/comments.css @@ -124,6 +124,18 @@ grid-column-gap: 10px; } +details.replies > summary{ + background-color: var(--interface-color); + border-style: outset; + border-width: 1px; + font-weight: bold; + padding-bottom: 0px; +} + +details.replies .comment{ + width: 600px; +} + .more-comments{ justify-self:center; margin-top:10px; diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 86644ea..3542d94 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -14,18 +14,6 @@ text-decoration: underline; } - details.replies > summary{ - background-color: var(--interface-color); - border-style: outset; - border-width: 1px; - font-weight: bold; - padding-bottom: 0px; - } - - details.replies .comment{ - width: 600px; - } - .playability-error{ height: 360px; width: 640px; -- cgit v1.2.3 From 95f2f027eabdcc4dc3c1a77829bf2bf503ed2939 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 20 Oct 2020 17:48:18 -0700 Subject: Comments.js: Add open in new tab button under replies as fallback if xhr request fails --- youtube/static/comments.css | 5 +++++ youtube/templates/comments.html | 1 + 2 files changed, 6 insertions(+) (limited to 'youtube') diff --git a/youtube/static/comments.css b/youtube/static/comments.css index 1c21e73..8509e5d 100644 --- a/youtube/static/comments.css +++ b/youtube/static/comments.css @@ -132,6 +132,11 @@ details.replies > summary{ padding-bottom: 0px; } +.replies-open-new-tab{ + display: inline-block; + margin-top: 5px; +} + details.replies .comment{ width: 600px; } diff --git a/youtube/templates/comments.html b/youtube/templates/comments.html index 9d93b8c..32a67ad 100644 --- a/youtube/templates/comments.html +++ b/youtube/templates/comments.html @@ -25,6 +25,7 @@ {% if settings.use_comments_js and comment['reply_count'] %}
{{ comment['view_replies_text'] }} + Open in new tab
loading..
{% else %} -- cgit v1.2.3 From c696db3e84d91092182adbeb7eef6126fad6be5d Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 20 Oct 2020 18:24:41 -0700 Subject: comments.js: include error in reply html rather than using an alert --- youtube/__init__.py | 6 ++++-- youtube/static/js/common.js | 4 +--- youtube/templates/error.html | 27 +++------------------------ youtube/templates/shared.css | 23 +++++++++++++++++++++++ 4 files changed, 31 insertions(+), 29 deletions(-) (limited to 'youtube') diff --git a/youtube/__init__.py b/youtube/__init__.py index 70ed5b2..61039d3 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -1,5 +1,6 @@ from youtube import util import flask +from flask import request import settings import traceback import re @@ -55,6 +56,7 @@ def timestamps(text): @yt_app.errorhandler(500) def error_page(e): + slim = request.args.get('slim', False) # whether it was an ajax request if (exc_info()[0] == util.FetchError and exc_info()[1].code == '429' and settings.route_tor @@ -64,8 +66,8 @@ def error_page(e): ' using the New Identity button in the Tor Browser.') if exc_info()[1].ip: error_message += ' Exit node IP address: ' + exc_info()[1].ip - return flask.render_template('error.html', error_message=error_message), 502 - return flask.render_template('error.html', traceback=traceback.format_exc()), 500 + return flask.render_template('error.html', error_message=error_message, slim=slim), 502 + return flask.render_template('error.html', traceback=traceback.format_exc(), slim=slim), 500 font_choices = { 0: 'initial', diff --git a/youtube/static/js/common.js b/youtube/static/js/common.js index 2db4390..20cfddd 100644 --- a/youtube/static/js/common.js +++ b/youtube/static/js/common.js @@ -41,9 +41,7 @@ function doXhr(url, callback=null) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = (e) => { - let ok = xhr.status >= 200 && xhr.status < 300; - if (ok) callback(e.currentTarget.response); - else alert(`${xhr.responseURL} status code: ${xhr.status}`); + callback(e.currentTarget.response); } xhr.send(); return xhr; diff --git a/youtube/templates/error.html b/youtube/templates/error.html index 2f94afa..c3f58b0 100644 --- a/youtube/templates/error.html +++ b/youtube/templates/error.html @@ -1,29 +1,8 @@ {% set page_title = 'Error' %} -{% extends "base.html" %} -{% block style %} - h1{ - font-size: 2rem; - font-weight: normal; - } - #error-box, #error-message{ - background-color: var(--interface-color); - width: 80%; - margin: auto; - margin-top: 20px; - padding: 5px; - } - #error-box > div, #error-box > p, #error-box > h1{ - white-space: pre-wrap; - margin-bottom: 10px; - } - .code-box{ - padding: 5px; - border-style:solid; - border-width:1px; - border-radius:5px; - } -{% endblock style %} +{% if not slim %} + {% extends "base.html" %} +{% endif %} {% block main %} {% if traceback %} diff --git a/youtube/templates/shared.css b/youtube/templates/shared.css index 141465a..8aaa706 100644 --- a/youtube/templates/shared.css +++ b/youtube/templates/shared.css @@ -334,3 +334,26 @@ body{ padding: 2px; justify-self: start; } + +/* error page stuff */ +h1{ + font-size: 2rem; + font-weight: normal; +} +#error-box, #error-message{ + background-color: var(--interface-color); + width: 80%; + margin: auto; + margin-top: 20px; + padding: 5px; +} +#error-box > div, #error-box > p, #error-box > h1{ + white-space: pre-wrap; + margin-bottom: 10px; +} +.code-box{ + padding: 5px; + border-style:solid; + border-width:1px; + border-radius:5px; +} -- cgit v1.2.3 From a27b575380378f1b490dcabb8cc67f05adee5daa Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Wed, 21 Oct 2020 10:35:01 +0200 Subject: remove trailing whitespaces --- youtube/post_comment.py | 2 +- youtube/proto.py | 28 ++++++++++++++-------------- youtube/static/comments.css | 2 +- youtube/templates/channel.html | 6 +++--- youtube/templates/local_playlist.html | 2 +- youtube/templates/playlist.html | 6 +++--- youtube/templates/shared.css | 14 +++++++------- youtube/yt_data_extract/common.py | 2 +- youtube/yt_data_extract/everything_else.py | 2 +- youtube/yt_data_extract/watch_extraction.py | 2 +- 10 files changed, 33 insertions(+), 33 deletions(-) (limited to 'youtube') diff --git a/youtube/post_comment.py b/youtube/post_comment.py index 78f080f..0bf0cf5 100644 --- a/youtube/post_comment.py +++ b/youtube/post_comment.py @@ -155,7 +155,7 @@ def get_delete_comment_page(): def get_post_comment_page(): video_id = request.args['video_id'] parent_id = request.args.get('parent_id', '') - + if parent_id: # comment reply form_action = util.URL_ORIGIN + '/comments?parent_id=' + parent_id + "&video_id=" + video_id replying = True diff --git a/youtube/proto.py b/youtube/proto.py index d966455..5fd16d5 100644 --- a/youtube/proto.py +++ b/youtube/proto.py @@ -5,13 +5,13 @@ import io def byte(n): return bytes((n,)) - + def varint_encode(offset): '''In this encoding system, for each 8-bit byte, the first bit is 1 if there are more bytes, and 0 is this is the last one. The next 7 bits are data. These 7-bit sections represent the data in Little endian order. For example, suppose the data is aaaaaaabbbbbbbccccccc (each of these sections is 7 bits). It will be encoded as: 1ccccccc 1bbbbbbb 0aaaaaaa - + This encoding is used in youtube parameters to encode offsets and to encode the length for length-prefixed data. See https://developers.google.com/protocol-buffers/docs/encoding#varints for more info.''' needed_bytes = ceil(offset.bit_length()/7) or 1 # (0).bit_length() returns 0, but we need 1 in that case. @@ -20,20 +20,20 @@ def varint_encode(offset): encoded_bytes[i] = (offset & 127) | 128 # 7 least significant bits offset = offset >> 7 encoded_bytes[-1] = offset & 127 # leave first bit as zero for last byte - + return bytes(encoded_bytes) - + def varint_decode(encoded): decoded = 0 for i, byte in enumerate(encoded): decoded |= (byte & 127) << 7*i - + if not (byte & 128): break return decoded - + def string(field_number, data): data = as_bytes(data) return _proto_field(2, field_number, varint_encode(len(data)) + data) @@ -41,20 +41,20 @@ nested = string def uint(field_number, value): return _proto_field(0, field_number, varint_encode(value)) - - - + + + def _proto_field(wire_type, field_number, data): ''' See https://developers.google.com/protocol-buffers/docs/encoding#structure ''' return varint_encode( (field_number << 3) | wire_type) + data - + def percent_b64encode(data): return base64.urlsafe_b64encode(data).replace(b'=', b'%3D') - - + + def unpadded_b64encode(data): return base64.urlsafe_b64encode(data).replace(b'=', b'') @@ -81,7 +81,7 @@ def read_varint(data): i += 1 return result - + def read_group(data, end_sequence): start = data.tell() index = data.original.find(end_sequence, start) @@ -101,7 +101,7 @@ def read_protobuf(data): break wire_type = tag & 7 field_number = tag >> 3 - + if wire_type == 0: value = read_varint(data) elif wire_type == 1: diff --git a/youtube/static/comments.css b/youtube/static/comments.css index 8509e5d..b177a4f 100644 --- a/youtube/static/comments.css +++ b/youtube/static/comments.css @@ -1,4 +1,4 @@ -.video-metadata{ +.video-metadata{ display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto 1fr auto; diff --git a/youtube/templates/channel.html b/youtube/templates/channel.html index a0cdff9..dddbff1 100644 --- a/youtube/templates/channel.html +++ b/youtube/templates/channel.html @@ -34,11 +34,11 @@ main .channel-tabs{ grid-row:2; grid-column: 1 / span 2; - + display:grid; grid-auto-flow: column; justify-content:start; - + background-color: var(--interface-color); padding: 3px; padding-left: 6px; @@ -103,7 +103,7 @@ } {% endblock style %} -{% block main %} +{% block main %}

{{ channel_name }}

diff --git a/youtube/templates/local_playlist.html b/youtube/templates/local_playlist.html index 7ba0642..803c4dc 100644 --- a/youtube/templates/local_playlist.html +++ b/youtube/templates/local_playlist.html @@ -25,7 +25,7 @@ } {% endblock style %} -{% block main %} +{% block main %} -
+
{% for info in video_list %} {{ common_elements.item(info) }} {% endfor %} diff --git a/youtube/templates/shared.css b/youtube/templates/shared.css index 8aaa706..2288a34 100644 --- a/youtube/templates/shared.css +++ b/youtube/templates/shared.css @@ -19,10 +19,10 @@ body{ margin:0; padding: 0; color:var(--text-color); - - + + background-color:var(--background-color); - + min-height:100vh; display: flex; flex-direction: column; @@ -141,7 +141,7 @@ body{ .item-list{ display: grid; grid-row-gap: 10px; - + } @@ -164,7 +164,7 @@ body{ .item-box{ display: inline-flex; flex-direction: row; - /* prevent overflow due to long titles with no spaces: + /* prevent overflow due to long titles with no spaces: https://stackoverflow.com/a/43312314 */ min-width: 0; } @@ -185,7 +185,7 @@ body{ align-content: start; grid-template-columns: auto 1fr; grid-template-rows: auto auto auto auto 1fr; - /* prevent overflow due to long titles with no spaces: + /* prevent overflow due to long titles with no spaces: https://stackoverflow.com/a/43312314 */ min-width: 0; } @@ -308,7 +308,7 @@ body{ justify-content: center; display: grid; grid-auto-columns: 40px; - grid-auto-flow: column; + grid-auto-flow: column; height: 40px; } .next-previous-button-row{ diff --git a/youtube/yt_data_extract/common.py b/youtube/yt_data_extract/common.py index 9610479..683b1c6 100644 --- a/youtube/yt_data_extract/common.py +++ b/youtube/yt_data_extract/common.py @@ -295,7 +295,7 @@ def extract_item_info(item, additional_info={}): info['duration'] = extract_str(item.get('lengthText')) # if it's an item in a playlist, get its index - if 'index' in item: # url has wrong index on playlist page + if 'index' in item: # url has wrong index on playlist page info['index'] = extract_int(item.get('index')) elif 'indexText' in item: # Current item in playlist has ▶ instead of the actual index, must diff --git a/youtube/yt_data_extract/everything_else.py b/youtube/yt_data_extract/everything_else.py index d91dad5..b4b612d 100644 --- a/youtube/yt_data_extract/everything_else.py +++ b/youtube/yt_data_extract/everything_else.py @@ -164,7 +164,7 @@ def extract_playlist_metadata(polymer_json): metadata['video_count'] = extract_int(header.get('numVideosText')) metadata['description'] = extract_str(header.get('descriptionText'), default='') metadata['author'] = extract_str(header.get('ownerText')) - metadata['author_id'] = multi_deep_get(header, + metadata['author_id'] = multi_deep_get(header, ['ownerText', 'runs', 0, 'navigationEndpoint', 'browseEndpoint', 'browseId'], ['ownerEndpoint', 'browseEndpoint', 'browseId']) if metadata['author_id']: diff --git a/youtube/yt_data_extract/watch_extraction.py b/youtube/yt_data_extract/watch_extraction.py index f89cec1..5e57c15 100644 --- a/youtube/yt_data_extract/watch_extraction.py +++ b/youtube/yt_data_extract/watch_extraction.py @@ -172,7 +172,7 @@ def _extract_watch_info_mobile(top_level): else: info['playlist'] = {} info['playlist']['title'] = playlist.get('title') - info['playlist']['author'] = extract_str(multi_get(playlist, + info['playlist']['author'] = extract_str(multi_get(playlist, 'ownerName', 'longBylineText', 'shortBylineText', 'ownerText')) author_id = deep_get(playlist, 'longBylineText', 'runs', 0, 'navigationEndpoint', 'browseEndpoint', 'browseId') -- cgit v1.2.3