From 3821e95ba0719d84facb6d95d8087751c334e75b Mon Sep 17 00:00:00 2001 From: Tom Bulled <26026015+tombulled@users.noreply.github.com> Date: Sun, 25 Feb 2024 13:27:19 +0000 Subject: [PATCH] :sparkles: [#60] Add examples for listing comments for a video using /next endpoint (#66) --- examples/list-video-comments-highlighted.py | 102 ++++++++++++++++++++ examples/list-video-comments.py | 74 ++++++++++++++ pyproject.toml | 3 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 examples/list-video-comments-highlighted.py create mode 100644 examples/list-video-comments.py diff --git a/examples/list-video-comments-highlighted.py b/examples/list-video-comments-highlighted.py new file mode 100644 index 0000000..f242177 --- /dev/null +++ b/examples/list-video-comments-highlighted.py @@ -0,0 +1,102 @@ +from innertube import InnerTube + +# YouTube Web CLient +CLIENT = InnerTube("WEB", "2.20240105.01.00") + + +def parse_text(text): + return "".join(run["text"] for run in text["runs"]) + + +def flatten(items): + flat_items = {} + + for item in items: + key = next(iter(item)) + val = item[key] + + flat_items.setdefault(key, []).append(val) + + return flat_items + + +def flatten_item_sections(item_sections): + return { + item_section["sectionIdentifier"]: item_section + for item_section in item_sections + } + + +def extract_comments(next_continuation_data): + return [ + continuation_item["commentThreadRenderer"] + for continuation_item in next_continuation_data["onResponseReceivedEndpoints"][ + 1 + ]["reloadContinuationItemsCommand"]["continuationItems"][:-1] + ] + + +def extract_comments_continuation_token(next_data): + contents = flatten( + next_data["contents"]["twoColumnWatchNextResults"]["results"]["results"][ + "contents" + ] + ) + item_sections = flatten_item_sections(contents["itemSectionRenderer"]) + comment_item_section_content = item_sections["comment-item-section"]["contents"][0] + comments_continuation_token = comment_item_section_content[ + "continuationItemRenderer" + ]["continuationEndpoint"]["continuationCommand"]["token"] + + return comments_continuation_token + + +def get_comments(video_id, params=None): + video = CLIENT.next(video_id, params=params) + + continuation_token = extract_comments_continuation_token(video) + + comments_continuation = CLIENT.next(continuation=continuation_token) + + return extract_comments(comments_continuation) + + +def print_comment(comment): + comment_renderer = comment["comment"]["commentRenderer"] + + comment_author = comment_renderer["authorText"]["simpleText"] + comment_content = parse_text(comment_renderer["contentText"]) + + print(f"[{comment_author}]") + print(comment_content) + print() + + +video_id = "BV1O7RR-VoA" + +# Get comments for a given video +comments = get_comments(video_id) + +# Select a comment to highlight (in this case the 3rd one) +comment = comments[2] + +# Print the comment we're going to highlight +print("### Highlighting Comment: ###") +print() +print_comment(comment) +print("---------------------") +print() + +# Extract the 'params' to highlight this comment +params = comment["comment"]["commentRenderer"]["publishedTimeText"]["runs"][0][ + "navigationEndpoint" +]["watchEndpoint"]["params"] + +# Get comments, but highlighting the selected comment +highlighted_comments = get_comments(video_id, params=params) + +print("### Comments: ###") +print() + +for comment in highlighted_comments: + print_comment(comment) diff --git a/examples/list-video-comments.py b/examples/list-video-comments.py new file mode 100644 index 0000000..45849cc --- /dev/null +++ b/examples/list-video-comments.py @@ -0,0 +1,74 @@ +from innertube import InnerTube + +ENGAGEMENT_SECTION_COMMENTS = "engagement-panel-comments-section" +C0MMENTS_TOP = "Top comments" +COMMENTS_NEWEST = "Newest first" + + +def parse_text(text): + return "".join(run["text"] for run in text["runs"]) + + +def extract_engagement_panels(next_data): + engagement_panels = {} + raw_engagement_panels = next_data.get("engagementPanels", []) + + for raw_engagement_panel in raw_engagement_panels: + engagement_panel = raw_engagement_panel.get( + "engagementPanelSectionListRenderer", {} + ) + target_id = engagement_panel.get("targetId") + + engagement_panels[target_id] = engagement_panel + + return engagement_panels + + +def parse_sort_filter_sub_menu(menu): + menu_items = menu["sortFilterSubMenuRenderer"]["subMenuItems"] + + return {menu_item["title"]: menu_item for menu_item in menu_items} + + +def extract_comments(next_continuation_data): + return [ + continuation_item["commentThreadRenderer"] + for continuation_item in next_continuation_data["onResponseReceivedEndpoints"][ + 1 + ]["reloadContinuationItemsCommand"]["continuationItems"][:-1] + ] + + +# YouTube Web CLient +client = InnerTube("WEB", "2.20240105.01.00") + +# ShortCircuit - Dell just DESTROYED the Surface Pro! - Dell XPS 13 2-in-1 +video = client.next("BV1O7RR-VoA") + +engagement_panels = extract_engagement_panels(video) +comments = engagement_panels[ENGAGEMENT_SECTION_COMMENTS] +comments_header = comments["header"]["engagementPanelTitleHeaderRenderer"] +comments_title = parse_text(comments_header["title"]) +comments_context = parse_text(comments_header["contextualInfo"]) +comments_menu_items = parse_sort_filter_sub_menu(comments_header["menu"]) +comments_top = comments_menu_items[C0MMENTS_TOP] +comments_top_continuation = comments_top["serviceEndpoint"]["continuationCommand"][ + "token" +] + +print(f"{comments_title} ({comments_context})...") +print() + +comments_continuation = client.next(continuation=comments_top_continuation) + +comments = extract_comments(comments_continuation) + +for comment in comments: + comment_renderer = comment["comment"]["commentRenderer"] + + comment_author = comment_renderer["authorText"]["simpleText"] + comment_content = parse_text(comment_renderer["contentText"]) + + print(f"[{comment_author}]") + print(comment_content) + print() diff --git a/pyproject.toml b/pyproject.toml index 1f4a6ca..19e70db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,4 +30,5 @@ ignore_missing_imports = true show_error_codes = true warn_redundant_casts = true warn_unused_configs = true -warn_unused_ignores = true \ No newline at end of file +warn_unused_ignores = true +exclude = ["examples/"]